From f19a0851746879e7e1600a0baa49f04b433dbca4 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 30 May 2024 00:14:38 +0200 Subject: [PATCH 01/11] Disable layout during scrolling by default --- .../test/tests/layout/LayoutWhileScrollingTest.kt | 2 -- .../java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/LayoutWhileScrollingTest.kt b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/LayoutWhileScrollingTest.kt index af424ec0..dfa1b299 100644 --- a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/LayoutWhileScrollingTest.kt +++ b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/LayoutWhileScrollingTest.kt @@ -66,7 +66,6 @@ class LayoutWhileScrollingTest : DpadRecyclerViewTest() { // given var layoutCompleted = 0 onRecyclerView("Disable layout during scroll") { recyclerView -> - recyclerView.setLayoutWhileScrollingEnabled(false) recyclerView.addOnLayoutCompletedListener( object : DpadRecyclerView.OnLayoutCompletedListener { override fun onLayoutCompleted(state: RecyclerView.State) { @@ -95,7 +94,6 @@ class LayoutWhileScrollingTest : DpadRecyclerViewTest() { // given var layoutCompleted = 0 onRecyclerView("Disable layout during scroll") { recyclerView -> - recyclerView.setLayoutWhileScrollingEnabled(false) recyclerView.addOnLayoutCompletedListener( object : DpadRecyclerView.OnLayoutCompletedListener { override fun onLayoutCompleted(state: RecyclerView.State) { diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt index 23720a20..591f790f 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt @@ -71,7 +71,7 @@ open class DpadRecyclerView @JvmOverloads constructor( private var isOverlappingRenderingEnabled = true private var isRetainingFocus = false private var startedTouchScroll = false - private var layoutWhileScrollingEnabled = true + private var layoutWhileScrollingEnabled = false private var hasPendingLayout = false private var touchInterceptListener: OnTouchInterceptListener? = null private var smoothScrollByBehavior: SmoothScrollByBehavior? = null From b8dadee053d117e3c9538dad7da84b716a28700f Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 30 May 2024 02:46:53 +0200 Subject: [PATCH 02/11] Handle special case of requesting selections while another layout was postponed --- .../testing/ScrollToActionsTest.kt | 11 ++++--- .../dpadrecyclerview/DpadRecyclerView.kt | 33 ++++++++++++++++--- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/dpadrecyclerview-testing/src/androidTest/java/com/rubensousa/dpadrecyclerview/testing/ScrollToActionsTest.kt b/dpadrecyclerview-testing/src/androidTest/java/com/rubensousa/dpadrecyclerview/testing/ScrollToActionsTest.kt index 9bacbf97..34915263 100644 --- a/dpadrecyclerview-testing/src/androidTest/java/com/rubensousa/dpadrecyclerview/testing/ScrollToActionsTest.kt +++ b/dpadrecyclerview-testing/src/androidTest/java/com/rubensousa/dpadrecyclerview/testing/ScrollToActionsTest.kt @@ -36,7 +36,7 @@ class ScrollToActionsTest : RecyclerViewTest() { fun testScrollDownToItem() { launchVerticalFragment() - val position = 15 + val position = 5 performActions( DpadRecyclerViewActions.scrollTo( hasDescendant(withText(position.toString())) @@ -51,14 +51,17 @@ class ScrollToActionsTest : RecyclerViewTest() { fun testScrollUpToItem() { launchVerticalFragment() - performActions(DpadRecyclerViewActions.selectPosition(15, smooth = false)) + performActions( + DpadRecyclerViewActions.scrollTo( + hasDescendant(withText("5")) + ) + ) val position = 0 performActions( - DpadRecyclerViewActions.waitForIdleScroll(), DpadRecyclerViewActions.scrollTo( hasDescendant(withText(position.toString())) - ) + ), ) assert(DpadRecyclerViewAssertions.isSelected(position)) diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt index 591f790f..ae4ebaa9 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt @@ -21,6 +21,7 @@ import android.content.res.TypedArray import android.graphics.Canvas import android.graphics.Rect import android.util.AttributeSet +import android.util.Log import android.view.Gravity import android.view.KeyEvent import android.view.MotionEvent @@ -30,6 +31,7 @@ import android.view.animation.Interpolator import androidx.annotation.Px import androidx.annotation.VisibleForTesting import androidx.core.view.ViewCompat +import androidx.core.view.postDelayed import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.SimpleItemAnimator @@ -229,12 +231,22 @@ open class DpadRecyclerView @JvmOverloads constructor( } final override fun requestLayout() { - if (layoutWhileScrollingEnabled || scrollState == SCROLL_STATE_IDLE) { + if (isRequestLayoutAllowed()) { hasPendingLayout = false + if (DEBUG) { + Log.i(TAG, "Layout Requested") + } super.requestLayout() - return + } else { + hasPendingLayout = true + if (DEBUG) { + Log.i(TAG, "Layout suppressed until scroll is idle") + } } - hasPendingLayout = true + } + + private fun isRequestLayoutAllowed(): Boolean { + return scrollState == SCROLL_STATE_IDLE || layoutWhileScrollingEnabled } // Overriding to prevent WRAP_CONTENT behavior by replacing it @@ -431,14 +443,25 @@ open class DpadRecyclerView @JvmOverloads constructor( startedTouchScroll = false pivotLayoutManager?.setScrollingFromTouchEvent(false) if (hasPendingLayout) { - hasPendingLayout = false - requestLayout() + scheduleLayout() } } else if (startedTouchScroll) { pivotLayoutManager?.setScrollingFromTouchEvent(true) } } + private fun scheduleLayout() { + if (DEBUG) { + Log.i(TAG, "Scheduling pending layout request") + } + /** + * The delay here is intended because users can request selections + * while the layout was locked and in that case, we should honor those requests instead + * of just performing a full layout + */ + postDelayed(500L) { requestLayout() } + } + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { super.onSizeChanged(w, h, oldw, oldh) fadingEdge.onSizeChanged(w, h, oldw, oldh, this) From 48a39066550c08e143b5658c11309122004e39c8 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 30 May 2024 02:54:33 +0200 Subject: [PATCH 03/11] Do not use extension function for post --- .../java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt index ae4ebaa9..b30d6561 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt @@ -31,7 +31,6 @@ import android.view.animation.Interpolator import androidx.annotation.Px import androidx.annotation.VisibleForTesting import androidx.core.view.ViewCompat -import androidx.core.view.postDelayed import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.SimpleItemAnimator @@ -459,7 +458,7 @@ open class DpadRecyclerView @JvmOverloads constructor( * while the layout was locked and in that case, we should honor those requests instead * of just performing a full layout */ - postDelayed(500L) { requestLayout() } + postDelayed({ requestLayout() }, 500L) } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { From 1f8e4edf9aeaf5a05c521b8928422fb5ed5e2294 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 30 May 2024 02:58:24 +0200 Subject: [PATCH 04/11] Improve test stability --- .../test/tests/layout/LayoutWhileScrollingTest.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/LayoutWhileScrollingTest.kt b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/LayoutWhileScrollingTest.kt index dfa1b299..f25c276e 100644 --- a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/LayoutWhileScrollingTest.kt +++ b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/LayoutWhileScrollingTest.kt @@ -17,6 +17,7 @@ package com.rubensousa.dpadrecyclerview.test.tests.layout import androidx.recyclerview.widget.RecyclerView +import androidx.test.espresso.Espresso import com.google.common.truth.Truth.assertThat import com.rubensousa.dpadrecyclerview.ChildAlignment import com.rubensousa.dpadrecyclerview.DpadRecyclerView @@ -25,6 +26,7 @@ import com.rubensousa.dpadrecyclerview.test.TestAdapterConfiguration import com.rubensousa.dpadrecyclerview.test.TestLayoutConfiguration import com.rubensousa.dpadrecyclerview.test.helpers.onRecyclerView import com.rubensousa.dpadrecyclerview.test.helpers.selectLastPosition +import com.rubensousa.dpadrecyclerview.test.helpers.waitForCondition import com.rubensousa.dpadrecyclerview.test.helpers.waitForIdleScrollState import com.rubensousa.dpadrecyclerview.test.tests.DpadRecyclerViewTest import com.rubensousa.dpadrecyclerview.testing.KeyEvents @@ -84,6 +86,9 @@ class LayoutWhileScrollingTest : DpadRecyclerViewTest() { } } waitForIdleScrollState() + waitForCondition("Wait for layout complete") { recyclerView -> + !recyclerView.isLayoutRequested + } // then assertThat(layoutCompleted).isEqualTo(1) @@ -112,6 +117,10 @@ class LayoutWhileScrollingTest : DpadRecyclerViewTest() { } } waitForIdleScrollState() + Espresso.onIdle() + waitForCondition("Wait for layout complete") { recyclerView -> + !recyclerView.isLayoutRequested + } // then assertThat(layoutCompleted).isEqualTo(1) From 730335baa50a837e35bd6215b811c03813b6e154 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 30 May 2024 03:08:56 +0200 Subject: [PATCH 05/11] Fix affected tests --- .../test/helpers/RecyclerViewTestExtensions.kt | 16 ++++++++++++++++ .../test/tests/layout/HorizontalRowTest.kt | 2 ++ .../test/tests/layout/VerticalColumnTest.kt | 2 ++ 3 files changed, 20 insertions(+) diff --git a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/helpers/RecyclerViewTestExtensions.kt b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/helpers/RecyclerViewTestExtensions.kt index 53ebee4b..8e4b5ac1 100644 --- a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/helpers/RecyclerViewTestExtensions.kt +++ b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/helpers/RecyclerViewTestExtensions.kt @@ -186,6 +186,22 @@ fun waitForIdleScrollState(id: Int = R.id.recyclerView) { Espresso.onView(withId(id)).perform(DpadRecyclerViewActions.waitForIdleScroll()) } +fun waitForLayout() { + var completedLayout = false + onRecyclerView("Add listener") { recyclerView -> + recyclerView.addOnLayoutCompletedListener(object : + DpadRecyclerView.OnLayoutCompletedListener { + override fun onLayoutCompleted(state: RecyclerView.State) { + completedLayout = true + } + }) + } + waitForCondition("Waiting for layout") { recyclerView -> + !recyclerView.isLayoutRequested && completedLayout + } +} + + fun waitForAdapterUpdate(id: Int = R.id.recyclerView) { Espresso.onView(withId(id)).perform(DpadRecyclerViewActions.waitForAdapterUpdate()) } diff --git a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/HorizontalRowTest.kt b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/HorizontalRowTest.kt index b816893e..31ff0946 100644 --- a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/HorizontalRowTest.kt +++ b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/HorizontalRowTest.kt @@ -27,6 +27,7 @@ import com.rubensousa.dpadrecyclerview.test.helpers.getRecyclerViewBounds import com.rubensousa.dpadrecyclerview.test.helpers.getRelativeItemViewBounds 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.testfixtures.LayoutConfig import com.rubensousa.dpadrecyclerview.testfixtures.LayoutManagerAssertions @@ -132,6 +133,7 @@ class HorizontalRowTest : DpadRecyclerViewTest() { } }) } + waitForLayout() row.setExtraLayoutSpace(start = row.getSize()) assertChildrenPositions() } diff --git a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/VerticalColumnTest.kt b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/VerticalColumnTest.kt index 8cf62950..e4b2d999 100644 --- a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/VerticalColumnTest.kt +++ b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/VerticalColumnTest.kt @@ -27,6 +27,7 @@ import com.rubensousa.dpadrecyclerview.test.helpers.getRecyclerViewBounds import com.rubensousa.dpadrecyclerview.test.helpers.getRelativeItemViewBounds import com.rubensousa.dpadrecyclerview.test.helpers.onRecyclerView import com.rubensousa.dpadrecyclerview.test.helpers.selectPosition +import com.rubensousa.dpadrecyclerview.test.helpers.waitForLayout import com.rubensousa.dpadrecyclerview.test.tests.DpadRecyclerViewTest import com.rubensousa.dpadrecyclerview.testfixtures.ColumnLayout import com.rubensousa.dpadrecyclerview.testfixtures.LayoutConfig @@ -157,6 +158,7 @@ class VerticalColumnTest : DpadRecyclerViewTest() { } }) } + waitForLayout() column.setExtraLayoutSpace(start = column.getSize()) assertChildrenPositions(column) } From f691a506f66d9b0a45a75a5f7ca2508adef6960f Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 30 May 2024 11:36:53 +0200 Subject: [PATCH 06/11] Do not reset subPosition if there are no pending changes --- .../dpadrecyclerview/layoutmanager/PivotSelector.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/PivotSelector.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/PivotSelector.kt index 0818ee30..0e1f16c6 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/PivotSelector.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/PivotSelector.kt @@ -79,7 +79,10 @@ internal class PivotSelector( fun consumePendingSelectionChanges(state: RecyclerView.State): Boolean { var consumed = false - if (position != RecyclerView.NO_POSITION && positionOffset != OFFSET_DISABLED) { + if (position != RecyclerView.NO_POSITION + && positionOffset != OFFSET_DISABLED + && positionOffset != 0 + ) { applyPositionOffset(state.itemCount) subPosition = 0 consumed = true From 54e0904fc9dc28a273bfbafef0bc53e4a8de25ee Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 30 May 2024 11:38:34 +0200 Subject: [PATCH 07/11] Reset pending layout flag after layout is completed --- .../test/tests/layout/LayoutWhileScrollingTest.kt | 9 --------- .../com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt | 9 +++++++-- .../dpadrecyclerview/layoutmanager/PivotLayoutManager.kt | 2 ++ 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/LayoutWhileScrollingTest.kt b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/LayoutWhileScrollingTest.kt index f25c276e..dfa1b299 100644 --- a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/LayoutWhileScrollingTest.kt +++ b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/LayoutWhileScrollingTest.kt @@ -17,7 +17,6 @@ package com.rubensousa.dpadrecyclerview.test.tests.layout import androidx.recyclerview.widget.RecyclerView -import androidx.test.espresso.Espresso import com.google.common.truth.Truth.assertThat import com.rubensousa.dpadrecyclerview.ChildAlignment import com.rubensousa.dpadrecyclerview.DpadRecyclerView @@ -26,7 +25,6 @@ import com.rubensousa.dpadrecyclerview.test.TestAdapterConfiguration import com.rubensousa.dpadrecyclerview.test.TestLayoutConfiguration import com.rubensousa.dpadrecyclerview.test.helpers.onRecyclerView import com.rubensousa.dpadrecyclerview.test.helpers.selectLastPosition -import com.rubensousa.dpadrecyclerview.test.helpers.waitForCondition import com.rubensousa.dpadrecyclerview.test.helpers.waitForIdleScrollState import com.rubensousa.dpadrecyclerview.test.tests.DpadRecyclerViewTest import com.rubensousa.dpadrecyclerview.testing.KeyEvents @@ -86,9 +84,6 @@ class LayoutWhileScrollingTest : DpadRecyclerViewTest() { } } waitForIdleScrollState() - waitForCondition("Wait for layout complete") { recyclerView -> - !recyclerView.isLayoutRequested - } // then assertThat(layoutCompleted).isEqualTo(1) @@ -117,10 +112,6 @@ class LayoutWhileScrollingTest : DpadRecyclerViewTest() { } } waitForIdleScrollState() - Espresso.onIdle() - waitForCondition("Wait for layout complete") { recyclerView -> - !recyclerView.isLayoutRequested - } // then assertThat(layoutCompleted).isEqualTo(1) diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt index b30d6561..08f6c44b 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt @@ -212,6 +212,7 @@ open class DpadRecyclerView @JvmOverloads constructor( pivotLayoutManager?.removeOnViewHolderSelectedListener(viewHolderTaskExecutor) pivotLayoutManager?.updateRecyclerView(null) if (pivotLayoutManager !== layout) { + pivotLayoutManager?.layoutCompletedListener = null pivotLayoutManager?.clearOnLayoutCompletedListeners() pivotLayoutManager?.clearOnViewHolderSelectedListeners() } @@ -224,6 +225,11 @@ open class DpadRecyclerView @JvmOverloads constructor( } if (layout is PivotLayoutManager) { layout.updateRecyclerView(this) + layout.layoutCompletedListener = object : OnLayoutCompletedListener { + override fun onLayoutCompleted(state: State) { + hasPendingLayout = false + } + } layout.addOnViewHolderSelectedListener(viewHolderTaskExecutor) pivotLayoutManager = layout } @@ -231,7 +237,6 @@ open class DpadRecyclerView @JvmOverloads constructor( final override fun requestLayout() { if (isRequestLayoutAllowed()) { - hasPendingLayout = false if (DEBUG) { Log.i(TAG, "Layout Requested") } @@ -458,7 +463,7 @@ open class DpadRecyclerView @JvmOverloads constructor( * while the layout was locked and in that case, we should honor those requests instead * of just performing a full layout */ - postDelayed({ requestLayout() }, 500L) + post { requestLayout() } } override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/PivotLayoutManager.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/PivotLayoutManager.kt index 73286406..5b82d14d 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/PivotLayoutManager.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/PivotLayoutManager.kt @@ -74,6 +74,7 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager() private var hadFocusBeforeLayout = false private var recyclerView: RecyclerView? = null private var isScrollingFromTouchEvent = false + internal var layoutCompletedListener: DpadRecyclerView.OnLayoutCompletedListener? = null override fun checkLayoutParams(layoutParams: RecyclerView.LayoutParams?): Boolean { return layoutParams is DpadLayoutParams @@ -128,6 +129,7 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager() scroller.cancelSmoothScroller() pivotSelector.onLayoutChildren(state) pivotLayout.onLayoutChildren(recycler, state) + layoutCompletedListener?.onLayoutCompleted(state) } override fun onLayoutCompleted(state: RecyclerView.State) { From 1e2988dd2dc55bcb21c0436b213a718ea7556d86 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 30 May 2024 11:40:27 +0200 Subject: [PATCH 08/11] Update docs for new changes --- .../com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt index 08f6c44b..9ef68d8c 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt @@ -1294,15 +1294,13 @@ open class DpadRecyclerView @JvmOverloads constructor( fun getOnMotionInterceptListener(): OnMotionInterceptListener? = motionInterceptListener /** - * By default, [DpadRecyclerView] allows triggering a layout-pass during scrolling. - * However, there might be some cases where someone is interested in disabling this behavior, - * for example: + * By default, [DpadRecyclerView] skips layout requests during scrolling because of: * 1. Compose animations trigger a full unnecessary layout-pass * 2. Content jumping around while scrolling is not ideal sometimes * * @param enabled true if layout requests should be possible while scrolling, * or false if they should be postponed until [RecyclerView.SCROLL_STATE_IDLE]. - * Default is true. + * Default is false. */ fun setLayoutWhileScrollingEnabled(enabled: Boolean) { layoutWhileScrollingEnabled = enabled From 7f689f7471ba29c1d494fa608dbfbf6519a39ed1 Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 30 May 2024 12:01:25 +0200 Subject: [PATCH 09/11] Decrease number of events to decrease flakiness --- .../dpadrecyclerview/compose/DpadComposeFocusViewHolderTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dpadrecyclerview-compose/src/androidTest/java/com/rubensousa/dpadrecyclerview/compose/DpadComposeFocusViewHolderTest.kt b/dpadrecyclerview-compose/src/androidTest/java/com/rubensousa/dpadrecyclerview/compose/DpadComposeFocusViewHolderTest.kt index 67db7089..23d4a1aa 100644 --- a/dpadrecyclerview-compose/src/androidTest/java/com/rubensousa/dpadrecyclerview/compose/DpadComposeFocusViewHolderTest.kt +++ b/dpadrecyclerview-compose/src/androidTest/java/com/rubensousa/dpadrecyclerview/compose/DpadComposeFocusViewHolderTest.kt @@ -170,7 +170,7 @@ class DpadComposeFocusViewHolderTest { @Test fun testAllViewHoldersAreFocusedOnKeyPress() { // given - val events = 10 + val events = 5 // when repeat(events) { From 84aa786d3b519e1d0966664850d65cf8051a0eaa Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 30 May 2024 12:31:45 +0200 Subject: [PATCH 10/11] Revert waitForLayout --- .../dpadrecyclerview/test/tests/layout/HorizontalRowTest.kt | 2 -- .../dpadrecyclerview/test/tests/layout/VerticalColumnTest.kt | 2 -- 2 files changed, 4 deletions(-) diff --git a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/HorizontalRowTest.kt b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/HorizontalRowTest.kt index 31ff0946..b816893e 100644 --- a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/HorizontalRowTest.kt +++ b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/HorizontalRowTest.kt @@ -27,7 +27,6 @@ import com.rubensousa.dpadrecyclerview.test.helpers.getRecyclerViewBounds import com.rubensousa.dpadrecyclerview.test.helpers.getRelativeItemViewBounds 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.testfixtures.LayoutConfig import com.rubensousa.dpadrecyclerview.testfixtures.LayoutManagerAssertions @@ -133,7 +132,6 @@ class HorizontalRowTest : DpadRecyclerViewTest() { } }) } - waitForLayout() row.setExtraLayoutSpace(start = row.getSize()) assertChildrenPositions() } diff --git a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/VerticalColumnTest.kt b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/VerticalColumnTest.kt index e4b2d999..8cf62950 100644 --- a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/VerticalColumnTest.kt +++ b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/layout/VerticalColumnTest.kt @@ -27,7 +27,6 @@ import com.rubensousa.dpadrecyclerview.test.helpers.getRecyclerViewBounds import com.rubensousa.dpadrecyclerview.test.helpers.getRelativeItemViewBounds import com.rubensousa.dpadrecyclerview.test.helpers.onRecyclerView import com.rubensousa.dpadrecyclerview.test.helpers.selectPosition -import com.rubensousa.dpadrecyclerview.test.helpers.waitForLayout import com.rubensousa.dpadrecyclerview.test.tests.DpadRecyclerViewTest import com.rubensousa.dpadrecyclerview.testfixtures.ColumnLayout import com.rubensousa.dpadrecyclerview.testfixtures.LayoutConfig @@ -158,7 +157,6 @@ class VerticalColumnTest : DpadRecyclerViewTest() { } }) } - waitForLayout() column.setExtraLayoutSpace(start = column.getSize()) assertChildrenPositions(column) } From 4b26cca1e6f1b3264544835b7e7fa76014b5957e Mon Sep 17 00:00:00 2001 From: Ruben Sousa Date: Thu, 30 May 2024 12:32:45 +0200 Subject: [PATCH 11/11] Remove waitForLayout --- .../test/helpers/RecyclerViewTestExtensions.kt | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/helpers/RecyclerViewTestExtensions.kt b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/helpers/RecyclerViewTestExtensions.kt index 8e4b5ac1..53ebee4b 100644 --- a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/helpers/RecyclerViewTestExtensions.kt +++ b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/helpers/RecyclerViewTestExtensions.kt @@ -186,22 +186,6 @@ fun waitForIdleScrollState(id: Int = R.id.recyclerView) { Espresso.onView(withId(id)).perform(DpadRecyclerViewActions.waitForIdleScroll()) } -fun waitForLayout() { - var completedLayout = false - onRecyclerView("Add listener") { recyclerView -> - recyclerView.addOnLayoutCompletedListener(object : - DpadRecyclerView.OnLayoutCompletedListener { - override fun onLayoutCompleted(state: RecyclerView.State) { - completedLayout = true - } - }) - } - waitForCondition("Waiting for layout") { recyclerView -> - !recyclerView.isLayoutRequested && completedLayout - } -} - - fun waitForAdapterUpdate(id: Int = R.id.recyclerView) { Espresso.onView(withId(id)).perform(DpadRecyclerViewActions.waitForAdapterUpdate()) }