Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Always trigger layout instead of trying to skip it based on item changes #240

Merged
merged 12 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@
.cxx
local.properties
/test_coverage
/logs
/logs
/.kotlin
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class DpadComposeFocusViewHolder<T>(
parent: ViewGroup,
compositionStrategy: ViewCompositionStrategy = RecyclerViewCompositionStrategy.DisposeOnRecycled,
isFocusable: Boolean = true,
private val content: @Composable (item: T) -> Unit = {}
private val content: @Composable (item: T) -> Unit = {},
) : RecyclerView.ViewHolder(ComposeView(parent.context)) {

private val itemState = mutableStateOf<T?>(null)
Expand All @@ -71,6 +71,10 @@ class DpadComposeFocusViewHolder<T>(

fun setFocusable(focusable: Boolean) {
composeView.apply {
if (!isFocusable) {
isFocusable = false
isFocusableInTouchMode = false
}
descendantFocusability = if (focusable) {
ViewGroup.FOCUS_AFTER_DESCENDANTS
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ object DpadRecyclerViewAssertions {

private class SelectionAssertion(
private val position: Int,
private val subPosition: Int = 0
private val subPosition: Int = 0,
) : DpadRecyclerViewAssertion() {

override fun check(view: DpadRecyclerView) {
Expand All @@ -52,21 +52,12 @@ object DpadRecyclerViewAssertions {

private class FocusAssertion(
private val focusedPosition: Int,
private val focusedSubPosition: Int = 0
private val focusedSubPosition: Int = 0,
) : DpadRecyclerViewAssertion() {

override fun check(view: DpadRecyclerView) {
val focusedView = view.findFocus() ?: throw AssertionFailedError(
"DpadRecyclerView didn't have focus: ${HumanReadables.describe(view)}"
)

val viewHolder = view.findContainingViewHolder(focusedView)
?: throw AssertionFailedError(
"ViewHolder not found for position " +
"$focusedPosition and sub position $focusedSubPosition"
)

assertThat(viewHolder.absoluteAdapterPosition).isEqualTo(focusedPosition)
val viewHolder = view.findViewHolderForAdapterPosition(focusedPosition)
?: throw AssertionFailedError("ViewHolder not found for position $focusedPosition")

val alignments = getAlignments(viewHolder)
if (alignments.isEmpty() && focusedSubPosition > 0) {
Expand All @@ -75,11 +66,23 @@ object DpadRecyclerViewAssertions {
"View: ${HumanReadables.describe(view)}"
)
} else if (alignments.isNotEmpty()) {
val focusedView = viewHolder.itemView.findFocus()
?: throw AssertionFailedError(
"ViewHolder at $focusedPosition didn't have focus: " +
HumanReadables.describe(viewHolder.itemView)
)
val alignment = alignments[focusedSubPosition]
val expectedView = viewHolder.itemView.findViewById<View>(
alignment.getFocusViewId()
)
assertThat(focusedView).isEqualTo(expectedView)
} else {
if (!viewHolder.itemView.hasFocus()) {
throw AssertionFailedError(
"ViewHolder at $focusedPosition didn't have focus: " +
HumanReadables.describe(viewHolder.itemView)
)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.rubensousa.dpadrecyclerview.test.TestLayoutConfiguration
import com.rubensousa.dpadrecyclerview.test.helpers.assertFocusAndSelection
import com.rubensousa.dpadrecyclerview.test.helpers.onRecyclerView
import com.rubensousa.dpadrecyclerview.test.helpers.waitForIdleScrollState
import com.rubensousa.dpadrecyclerview.test.helpers.waitForLayout
import com.rubensousa.dpadrecyclerview.test.tests.DpadRecyclerViewTest
import com.rubensousa.dpadrecyclerview.testing.KeyEvents
import com.rubensousa.dpadrecyclerview.testing.R
Expand All @@ -28,10 +29,10 @@ class PendingAlignmentTest : DpadRecyclerViewTest() {
spans = 1,
orientation = RecyclerView.HORIZONTAL,
parentAlignment = ParentAlignment(
edge = ParentAlignment.Edge.MIN_MAX,
fraction = 0.0f
edge = ParentAlignment.Edge.NONE,
fraction = 0.5f
),
childAlignment = ChildAlignment(offset = 0, fraction = 0.0f)
childAlignment = ChildAlignment(offset = 0, fraction = 0.5f)
)
}

Expand Down Expand Up @@ -109,5 +110,6 @@ class PendingAlignmentTest : DpadRecyclerViewTest() {
}
})
}
waitForLayout()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager(),

override fun generateLayoutParams(
context: Context,
attrs: AttributeSet
attrs: AttributeSet,
): RecyclerView.LayoutParams {
return DpadLayoutParams(context, attrs)
}
Expand Down Expand Up @@ -138,32 +138,34 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager(),
override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
// If we have focus, save it temporarily since the views will change and we might lose it
hadFocusBeforeLayout = hasFocus()
scroller.cancelSmoothScroller()
pivotLayout.onLayoutChildren(recycler, state)
layoutCompletedListener?.onLayoutCompleted(state)
if (hadFocusBeforeLayout) {
focusDispatcher.focusSelectedView()
}
if (layoutInfo.isScrollingToTarget) {
scroller.cancelScrollToTarget()
}
hadFocusBeforeLayout = false
}

override fun onLayoutCompleted(state: RecyclerView.State) {
layoutCompletedListener?.onLayoutCompleted(state)
pivotLayout.onLayoutCompleted(state)
if (hadFocusBeforeLayout) {
focusDispatcher.focusSelectedView(recyclerView)
}
pivotSelector.onLayoutCompleted()
hadFocusBeforeLayout = false
}

override fun collectAdjacentPrefetchPositions(
dx: Int,
dy: Int,
state: RecyclerView.State,
layoutPrefetchRegistry: LayoutPrefetchRegistry
layoutPrefetchRegistry: LayoutPrefetchRegistry,
) {
prefetchCollector.collectAdjacentPrefetchPositions(dx, dy, state, layoutPrefetchRegistry)
}

override fun collectInitialPrefetchPositions(
adapterItemCount: Int,
layoutPrefetchRegistry: LayoutPrefetchRegistry
layoutPrefetchRegistry: LayoutPrefetchRegistry,
) {
prefetchCollector.collectInitialPrefetchPositions(
adapterItemCount = adapterItemCount,
Expand All @@ -176,13 +178,13 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager(),
override fun scrollHorizontallyBy(
dx: Int,
recycler: RecyclerView.Recycler,
state: RecyclerView.State
state: RecyclerView.State,
): Int = pivotLayout.scrollHorizontallyBy(dx, recycler, state)

override fun scrollVerticallyBy(
dy: Int,
recycler: RecyclerView.Recycler,
state: RecyclerView.State
state: RecyclerView.State,
): Int = pivotLayout.scrollVerticallyBy(dy, recycler, state)

override fun computeHorizontalScrollOffset(state: RecyclerView.State): Int {
Expand Down Expand Up @@ -277,7 +279,7 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager(),
override fun smoothScrollToPosition(
recyclerView: RecyclerView,
state: RecyclerView.State,
position: Int
position: Int,
) {
scroller.scrollToPosition(position, subPosition = 0, smooth = true)
}
Expand All @@ -290,7 +292,6 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager(),

override fun onItemsAdded(recyclerView: RecyclerView, positionStart: Int, itemCount: Int) {
layoutInfo.invalidateSpanCache()
pivotLayout.onItemsAdded(positionStart, itemCount)
pivotSelector.onItemsAdded(positionStart, itemCount)
}

Expand All @@ -301,19 +302,17 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager(),

override fun onItemsRemoved(recyclerView: RecyclerView, positionStart: Int, itemCount: Int) {
layoutInfo.invalidateSpanCache()
pivotLayout.onItemsRemoved(positionStart, itemCount)
pivotSelector.onItemsRemoved(positionStart, itemCount)
}

override fun onItemsMoved(recyclerView: RecyclerView, from: Int, to: Int, itemCount: Int) {
layoutInfo.invalidateSpanCache()
pivotLayout.onItemsMoved(from, to, itemCount)
pivotSelector.onItemsMoved(from, to, itemCount)
}

override fun onAdapterChanged(
oldAdapter: RecyclerView.Adapter<*>?,
newAdapter: RecyclerView.Adapter<*>?
newAdapter: RecyclerView.Adapter<*>?,
) {
if (oldAdapter != null) {
pivotLayout.reset()
Expand All @@ -336,14 +335,14 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager(),
recyclerView: RecyclerView,
views: ArrayList<View>,
direction: Int,
focusableMode: Int
focusableMode: Int,
): Boolean {
return focusDispatcher.onAddFocusables(recyclerView, views, direction, focusableMode)
}

fun onRequestFocusInDescendants(
direction: Int,
previouslyFocusedRect: Rect?
previouslyFocusedRect: Rect?,
): Boolean {
return focusDispatcher.onRequestFocusInDescendants(direction, previouslyFocusedRect)
}
Expand All @@ -352,7 +351,7 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager(),
parent: RecyclerView,
state: RecyclerView.State,
child: View,
focused: View?
focused: View?,
): Boolean {
focusDispatcher.onRequestChildFocus(parent, child, focused)
return true
Expand All @@ -363,7 +362,7 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager(),
parent: RecyclerView,
child: View,
rect: Rect,
immediate: Boolean
immediate: Boolean,
): Boolean = false

override fun onAttachedToWindow(view: RecyclerView) {
Expand All @@ -385,22 +384,22 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager(),

override fun getRowCountForAccessibility(
recycler: RecyclerView.Recycler,
state: RecyclerView.State
state: RecyclerView.State,
): Int {
return accessibilityHelper.getRowCountForAccessibility(state)
}

override fun getColumnCountForAccessibility(
recycler: RecyclerView.Recycler,
state: RecyclerView.State
state: RecyclerView.State,
): Int {
return accessibilityHelper.getColumnCountForAccessibility(state)
}

override fun onInitializeAccessibilityNodeInfo(
recycler: RecyclerView.Recycler,
state: RecyclerView.State,
info: AccessibilityNodeInfoCompat
info: AccessibilityNodeInfoCompat,
) {
accessibilityHelper.onInitializeAccessibilityNodeInfo(recycler, state, info)
}
Expand All @@ -409,7 +408,7 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager(),
recycler: RecyclerView.Recycler,
state: RecyclerView.State,
host: View,
info: AccessibilityNodeInfoCompat
info: AccessibilityNodeInfoCompat,
) {
accessibilityHelper.onInitializeAccessibilityNodeInfoForItem(host, info)
}
Expand All @@ -418,7 +417,7 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager(),
recycler: RecyclerView.Recycler,
state: RecyclerView.State,
action: Int,
args: Bundle?
args: Bundle?,
): Boolean = accessibilityHelper.performAccessibilityAction(recyclerView, state, action)

override fun onSaveInstanceState(): Parcelable {
Expand Down Expand Up @@ -694,13 +693,13 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager(),
}

fun addOnLayoutCompletedListener(
listener: DpadRecyclerView.OnLayoutCompletedListener
listener: DpadRecyclerView.OnLayoutCompletedListener,
) {
pivotLayout.addOnLayoutCompletedListener(listener)
}

fun removeOnLayoutCompletedListener(
listener: DpadRecyclerView.OnLayoutCompletedListener
listener: DpadRecyclerView.OnLayoutCompletedListener,
) {
pivotLayout.removeOnLayoutCompletedListener(listener)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import kotlin.math.min
*/
internal class PivotSelector(
private val layoutManager: LayoutManager,
private val layoutInfo: LayoutInfo
private val layoutInfo: LayoutInfo,
) {

companion object {
Expand Down
Loading
Loading