diff --git a/dpadrecyclerview/api/dpadrecyclerview.api b/dpadrecyclerview/api/dpadrecyclerview.api index 5bc750e5..c5ed1b85 100644 --- a/dpadrecyclerview/api/dpadrecyclerview.api +++ b/dpadrecyclerview/api/dpadrecyclerview.api @@ -141,8 +141,10 @@ public class com/rubensousa/dpadrecyclerview/DpadRecyclerView : androidx/recycle public final fun setSelectedPositionSmooth (ILcom/rubensousa/dpadrecyclerview/ViewHolderTask;)V public final fun setSelectedSubPosition (I)V public final fun setSelectedSubPosition (II)V + public final fun setSelectedSubPosition (IILcom/rubensousa/dpadrecyclerview/ViewHolderTask;)V public final fun setSelectedSubPositionSmooth (I)V public final fun setSelectedSubPositionSmooth (II)V + public final fun setSelectedSubPositionSmooth (IILcom/rubensousa/dpadrecyclerview/ViewHolderTask;)V public final fun setSmoothFocusChangesEnabled (Z)V public final fun setSmoothScrollBehavior (Lcom/rubensousa/dpadrecyclerview/DpadRecyclerView$SmoothScrollByBehavior;)V public final fun setSmoothScrollMaxPendingAlignments (I)V diff --git a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/TestGridFragment.kt b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/TestGridFragment.kt index d45de64d..024b590a 100644 --- a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/TestGridFragment.kt +++ b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/TestGridFragment.kt @@ -107,7 +107,8 @@ open class TestGridFragment : Fragment(R.layout.dpadrecyclerview_test_container) recyclerView: DpadRecyclerView, adapterConfig: TestAdapterConfiguration ): RecyclerView.Adapter<*> { - return TestAdapter(adapterConfig, + return TestAdapter( + adapterConfig, onViewHolderSelected = ::addViewHolderSelected, onViewHolderDeselected = ::addViewHolderDeselected ) @@ -145,6 +146,30 @@ open class TestGridFragment : Fragment(R.layout.dpadrecyclerview_test_container) } } + fun selectWithTask( + position: Int, + subPosition: Int, + smooth: Boolean, + executeWhenAligned: Boolean = false + ) { + val recyclerView = requireView().findViewById(R.id.recyclerView) + val task = object : ViewHolderTask(executeWhenAligned) { + override fun execute(viewHolder: RecyclerView.ViewHolder) { + tasks.add( + DpadSelectionEvent( + position = position, + subPosition = subPosition + ) + ) + } + } + if (smooth) { + recyclerView.setSelectedSubPositionSmooth(position, subPosition, task) + } else { + recyclerView.setSelectedSubPosition(position, subPosition, task) + } + } + fun clearEvents() { selectionEvents.clear() alignedEvents.clear() diff --git a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/selection/SubSelectionTest.kt b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/selection/SubSelectionTest.kt index 081343f9..7dbe793a 100644 --- a/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/selection/SubSelectionTest.kt +++ b/dpadrecyclerview/src/androidTest/kotlin/com/rubensousa/dpadrecyclerview/test/tests/selection/SubSelectionTest.kt @@ -26,6 +26,7 @@ import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView import androidx.test.espresso.Espresso import androidx.test.espresso.matcher.ViewMatchers +import com.google.common.truth.Truth.assertThat import com.rubensousa.dpadrecyclerview.ChildAlignment import com.rubensousa.dpadrecyclerview.DpadRecyclerView import com.rubensousa.dpadrecyclerview.DpadViewHolder @@ -40,7 +41,9 @@ import com.rubensousa.dpadrecyclerview.test.assertions.ViewHolderSelectionCountA import com.rubensousa.dpadrecyclerview.test.helpers.assertFocusAndSelection import com.rubensousa.dpadrecyclerview.test.helpers.selectPosition import com.rubensousa.dpadrecyclerview.test.helpers.selectSubPosition +import com.rubensousa.dpadrecyclerview.test.helpers.waitForIdleScrollState import com.rubensousa.dpadrecyclerview.test.tests.DpadRecyclerViewTest +import com.rubensousa.dpadrecyclerview.testfixtures.DpadSelectionEvent import com.rubensousa.dpadrecyclerview.testing.KeyEvents import com.rubensousa.dpadrecyclerview.testing.R import org.junit.Test @@ -136,6 +139,62 @@ class SubSelectionTest : DpadRecyclerViewTest() { ) } + @Test + fun testTaskIsExecutedAfterViewHolderIsSelectedAndAligned() { + launchSubPositionFragment() + + selectWithTask( + position = 0, + subPosition = 1, + smooth = true, + executeWhenAligned = true + ) + + waitForIdleScrollState() + + selectWithTask( + position = 0, + subPosition = 2, + smooth = false, + executeWhenAligned = false + ) + + waitForIdleScrollState() + + assertThat(getSelectionsFromTasks()).isEqualTo( + listOf( + DpadSelectionEvent( + position = 0, + subPosition = 1 + ), + DpadSelectionEvent( + position = 0, + subPosition = 2 + ) + ) + ) + } + + + private fun getSelectionsFromTasks(): List { + var events = listOf() + fragmentScenario.onFragment { fragment -> + events = fragment.getTasksExecuted() + } + return events + } + + private fun selectWithTask( + position: Int, + subPosition: Int, + smooth: Boolean, + executeWhenAligned: Boolean + ) { + fragmentScenario.onFragment { fragment -> + fragment.selectWithTask(position, subPosition, smooth, executeWhenAligned) + } + } + private fun launchSubPositionFragment() { launchSubPositionFragment( getDefaultLayoutConfiguration(), @@ -147,15 +206,14 @@ class SubSelectionTest : DpadRecyclerViewTest() { layoutConfiguration: TestLayoutConfiguration, adapterConfiguration: TestAdapterConfiguration ): FragmentScenario { - return launchFragmentInContainer( + fragmentScenario = launchFragmentInContainer( fragmentArgs = TestGridFragment.getArgs( layoutConfiguration, adapterConfiguration ), themeResId = R.style.DpadRecyclerViewTestTheme - ).also { - fragmentScenario = it - } + ) + return fragmentScenario } class TestSubPositionFragment : TestGridFragment() { diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt index 087d08a3..5e1e6342 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/DpadRecyclerView.kt @@ -998,6 +998,18 @@ open class DpadRecyclerView @JvmOverloads constructor( requireLayout().selectPosition(position, subPosition, smooth = false) } + /** + * Performs a task on a ViewHolder at a given position and sub position after scrolling to it. + * + * @param position Adapter position of the item to select + * @param subPosition index of the alignment from [DpadViewHolder.getSubPositionAlignments] + * @param task Task to executed on the ViewHolder at the given position + */ + fun setSelectedSubPosition(position: Int, subPosition: Int, task: ViewHolderTask) { + viewHolderTaskExecutor.schedule(position, subPosition, task) + requireLayout().selectPosition(position, subPosition, smooth = false) + } + /** * Changes the sub selected view immediately without any scroll animation. * @param subPosition index of the alignment from [DpadViewHolder.getSubPositionAlignments] @@ -1023,6 +1035,18 @@ open class DpadRecyclerView @JvmOverloads constructor( requireLayout().selectPosition(position, subPosition, smooth = true) } + /** + * Performs a task on a ViewHolder at a given position and sub position after scrolling to it. + * + * @param position Adapter position of the item to select + * @param subPosition index of the alignment from [DpadViewHolder.getSubPositionAlignments] + * @param task Task to executed on the ViewHolder at the given position + */ + fun setSelectedSubPositionSmooth(position: Int, subPosition: Int, task: ViewHolderTask) { + viewHolderTaskExecutor.schedule(position, subPosition, task) + requireLayout().selectPosition(position, subPosition, smooth = true) + } + /** * @return the current selected position or [RecyclerView.NO_POSITION] if there's none */ diff --git a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/ViewHolderTaskExecutor.kt b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/ViewHolderTaskExecutor.kt index 3971c1a2..33b5849b 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/ViewHolderTaskExecutor.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/ViewHolderTaskExecutor.kt @@ -21,10 +21,18 @@ import androidx.recyclerview.widget.RecyclerView internal class ViewHolderTaskExecutor : OnViewHolderSelectedListener { private var targetPosition = RecyclerView.NO_POSITION + private var targetSubPosition = RecyclerView.NO_POSITION private var pendingTask: ViewHolderTask? = null fun schedule(position: Int, task: ViewHolderTask) { targetPosition = position + targetSubPosition = RecyclerView.NO_POSITION + pendingTask = task + } + + fun schedule(position: Int, subPosition: Int, task: ViewHolderTask) { + targetPosition = position + targetSubPosition = subPosition pendingTask = task } @@ -36,6 +44,7 @@ internal class ViewHolderTaskExecutor : OnViewHolderSelectedListener { ) { if (position == targetPosition && child != null + && (targetSubPosition == RecyclerView.NO_POSITION || targetSubPosition == subPosition) && pendingTask?.executeWhenAligned == false ) { executePendingTask(child) @@ -50,6 +59,7 @@ internal class ViewHolderTaskExecutor : OnViewHolderSelectedListener { ) { if (position == targetPosition && child != null + && (targetSubPosition == RecyclerView.NO_POSITION || targetSubPosition == subPosition) && pendingTask?.executeWhenAligned == true ) { executePendingTask(child) 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 2c9aca5b..871135ac 100644 --- a/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/PivotSelector.kt +++ b/dpadrecyclerview/src/main/java/com/rubensousa/dpadrecyclerview/layoutmanager/PivotSelector.kt @@ -62,12 +62,13 @@ internal class PivotSelector( fun update(newPosition: Int, newSubPosition: Int = 0): Boolean { val previousPosition = position + val previousSubPosition = subPosition position = constrainPivotPosition( position = newPosition, itemCount = layoutManager.itemCount ) subPosition = newSubPosition - return position != previousPosition || newSubPosition != subPosition + return position != previousPosition || subPosition != previousSubPosition } fun consumePendingSelectionChanges(state: RecyclerView.State): Boolean {