Skip to content

Commit

Permalink
Merge pull request #219 from rubensousa/span_size_focus
Browse files Browse the repository at this point in the history
Fix focus issues with different span sizes
  • Loading branch information
rubensousa authored Jun 4, 2024
2 parents fbeacc6 + 61fddb8 commit 30cf370
Show file tree
Hide file tree
Showing 22 changed files with 848 additions and 79 deletions.
15 changes: 15 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,21 @@

## Version 1.3.0

### 1.3.0-alpha04

2024-06-04

#### New Features

- Added `DpadSelectionSnapHelper` to improve selection on touch events. ([#215](https://github.com/rubensousa/DpadRecyclerView/pull/215/files))
- Added `isFocusable` to `DpadComposeFocusViewHolder` to allow disabling focus for some items.

#### Bug fixes

- Fixed initial selection being always at position 0, even when that view is not focusable.
- Fixed some rows not receiving focus in grids using `DpadSpanSizeLookup`. ([#217](https://github.com/rubensousa/DpadRecyclerView/issues/217))
- Fixed fast scrolling not working correctly in some grids using `DpadSpanSizeLookup`. ([#218](https://github.com/rubensousa/DpadRecyclerView/issues/218))

### 1.3.0-alpha03

2024-05-31
Expand Down
5 changes: 3 additions & 2 deletions dpadrecyclerview-compose/api/dpadrecyclerview-compose.api
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ public final class com/rubensousa/dpadrecyclerview/compose/DpadComposeExtensions

public final class com/rubensousa/dpadrecyclerview/compose/DpadComposeFocusViewHolder : androidx/recyclerview/widget/RecyclerView$ViewHolder {
public static final field $stable I
public fun <init> (Landroid/view/ViewGroup;Landroidx/compose/ui/platform/ViewCompositionStrategy;Lkotlin/jvm/functions/Function3;)V
public synthetic fun <init> (Landroid/view/ViewGroup;Landroidx/compose/ui/platform/ViewCompositionStrategy;Lkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Landroid/view/ViewGroup;Landroidx/compose/ui/platform/ViewCompositionStrategy;ZLkotlin/jvm/functions/Function3;)V
public synthetic fun <init> (Landroid/view/ViewGroup;Landroidx/compose/ui/platform/ViewCompositionStrategy;ZLkotlin/jvm/functions/Function3;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun getItem ()Ljava/lang/Object;
public final fun setFocusable (Z)V
public final fun setItemState (Ljava/lang/Object;)V
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,16 @@ import androidx.recyclerview.widget.RecyclerView
class DpadComposeFocusViewHolder<T>(
parent: ViewGroup,
compositionStrategy: ViewCompositionStrategy = RecyclerViewCompositionStrategy.DisposeOnRecycled,
isFocusable: Boolean = true,
private val content: @Composable (item: T) -> Unit
) : RecyclerView.ViewHolder(ComposeView(parent.context)) {

private val itemState = mutableStateOf<T?>(null)
private val composeView = itemView as ComposeView

init {
val composeView = itemView as ComposeView
composeView.apply {
isFocusable = true
isFocusableInTouchMode = true
descendantFocusability = ViewGroup.FOCUS_AFTER_DESCENDANTS
this@DpadComposeFocusViewHolder.setFocusable(isFocusable)
setViewCompositionStrategy(compositionStrategy)
setContent {
itemState.value?.let { item ->
Expand All @@ -66,6 +65,18 @@ class DpadComposeFocusViewHolder<T>(
}
}

fun setFocusable(focusable: Boolean) {
composeView.apply {
isFocusable = focusable
isFocusableInTouchMode = focusable
descendantFocusability = if (focusable) {
ViewGroup.FOCUS_AFTER_DESCENDANTS
} else {
ViewGroup.FOCUS_BLOCK_DESCENDANTS
}
}
}

fun setItemState(item: T?) {
itemState.value = item
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright 2024 Rúben Sousa
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.rubensousa.dpadrecyclerview.test

import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import com.rubensousa.dpadrecyclerview.DpadRecyclerView
import com.rubensousa.dpadrecyclerview.testing.R

class RecyclerViewFragment : Fragment(R.layout.dpadrecyclerview_test_container) {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<DpadRecyclerView>(R.id.recyclerView).requestFocus()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ fun waitForCondition(
.perform(DpadViewActions.waitForCondition(description, condition))
}

fun waitForLayout() {
waitForCondition("Waiting for layout") { recyclerView -> !recyclerView.isLayoutRequested }
}

fun onRecyclerView(
description: String,
id: Int = R.id.recyclerView,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright 2024 Rúben Sousa
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.rubensousa.dpadrecyclerview.test.helpers

import android.annotation.SuppressLint
import android.os.Handler
import android.os.Looper
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import java.util.Collections
import java.util.concurrent.Executors

abstract class TestAdapter<VH : RecyclerView.ViewHolder>: RecyclerView.Adapter<VH>() {

companion object {
private val BACKGROUND_EXECUTOR = Executors.newFixedThreadPool(4)
private val MAIN_THREAD_HANDLER = Handler(Looper.getMainLooper())
}

private var items: MutableList<Int> = Collections.emptyList()
private var currentVersion = 0

@SuppressLint("NotifyDataSetChanged")
fun replaceList(items: MutableList<Int>) {
this.items = items
currentVersion++
notifyDataSetChanged()
}

fun submitList(newList: MutableList<Int>, commitCallback: Runnable? = null) {
val version = ++currentVersion
if (items === newList) {
commitCallback?.run()
return
}

if (newList.isEmpty()) {
val removed = items.size
items = Collections.emptyList()
notifyItemRangeRemoved(0, removed)
return
}

if (items.isEmpty()) {
items = newList
notifyItemRangeInserted(0, newList.size)
return
}

val oldList = items
BACKGROUND_EXECUTOR.execute {
val diffResult = calculateDiff(oldList, newList)
MAIN_THREAD_HANDLER.post {
if (version == currentVersion) {
latchList(newList, diffResult, commitCallback)
}
}
}
}

private fun calculateDiff(
oldList: List<Int>,
newList: List<Int>
): DiffUtil.DiffResult {
return DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize(): Int = oldList.size
override fun getNewListSize(): Int = newList.size

override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldList[oldItemPosition]
val newItem = newList[newItemPosition]
return oldItem == newItem
}

override fun areContentsTheSame(
oldItemPosition: Int,
newItemPosition: Int
): Boolean {
val oldItem = oldList[oldItemPosition]
val newItem = newList[newItemPosition]
return oldItem == newItem
}
})
}

private fun latchList(
newList: MutableList<Int>,
result: DiffUtil.DiffResult,
commitCallback: Runnable?
) {
items = newList
result.dispatchUpdatesTo(this)
commitCallback?.run()
}

fun removeAt(index: Int) {
if (index >= 0 && index < items.size) {
currentVersion++
items.removeAt(index)
notifyItemRemoved(index)
}

}

fun move(from: Int, to: Int) {
currentVersion++
Collections.swap(items, from, to)
notifyItemMoved(from, to)
}

fun addAt(index: Int, item: Int) {
currentVersion++
items.add(index, item)
notifyItemInserted(index)
}

override fun getItemCount(): Int = items.size

fun getItem(position: Int) = items[position]
}
Loading

0 comments on commit 30cf370

Please sign in to comment.