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

Add deselection listener with previous ViewHolder that was selected #280

Merged
merged 4 commits into from
Oct 16, 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* 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.testfixtures

import androidx.recyclerview.widget.RecyclerView

data class DpadDeselectionEvent(val viewHolder: RecyclerView.ViewHolder)
9 changes: 8 additions & 1 deletion dpadrecyclerview/api/dpadrecyclerview.api
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,13 @@ public class com/rubensousa/dpadrecyclerview/DpadRecyclerView : androidx/recycle
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;)V
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;I)V
public synthetic fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun addOnChildLaidOutListener (Lcom/rubensousa/dpadrecyclerview/OnChildLaidOutListener;)V
public final fun addOnFocusLostListener (Lcom/rubensousa/dpadrecyclerview/OnFocusLostListener;)V
public final fun addOnLayoutCompletedListener (Lcom/rubensousa/dpadrecyclerview/DpadRecyclerView$OnLayoutCompletedListener;)V
public final fun addOnViewFocusedListener (Lcom/rubensousa/dpadrecyclerview/OnViewFocusedListener;)V
public final fun addOnViewHolderSelectedListener (Lcom/rubensousa/dpadrecyclerview/OnViewHolderSelectedListener;)V
public final fun addRecyclerListener (Landroidx/recyclerview/widget/RecyclerView$RecyclerListener;)V
public final fun clearOnChildLaidOutListeners ()V
public final fun clearOnFocusLostListeners ()V
public final fun clearOnLayoutCompletedListeners ()V
public final fun clearOnViewFocusedListeners ()V
Expand Down Expand Up @@ -132,6 +134,7 @@ public class com/rubensousa/dpadrecyclerview/DpadRecyclerView : androidx/recycle
public final fun onRtlPropertiesChanged (I)V
public fun onScrollStateChanged (I)V
protected fun onSizeChanged (IIII)V
public final fun removeOnChildLaidOutListener (Lcom/rubensousa/dpadrecyclerview/OnChildLaidOutListener;)V
public final fun removeOnFocusLostListener (Lcom/rubensousa/dpadrecyclerview/OnFocusLostListener;)V
public final fun removeOnLayoutCompletedListener (Lcom/rubensousa/dpadrecyclerview/DpadRecyclerView$OnLayoutCompletedListener;)V
public final fun removeOnViewFocusedListener (Lcom/rubensousa/dpadrecyclerview/OnViewFocusedListener;)V
Expand Down Expand Up @@ -345,11 +348,13 @@ public abstract interface class com/rubensousa/dpadrecyclerview/OnViewFocusedLis
}

public abstract interface class com/rubensousa/dpadrecyclerview/OnViewHolderSelectedListener {
public abstract fun onViewHolderDeselected (Landroidx/recyclerview/widget/RecyclerView;Landroidx/recyclerview/widget/RecyclerView$ViewHolder;)V
public abstract fun onViewHolderSelected (Landroidx/recyclerview/widget/RecyclerView;Landroidx/recyclerview/widget/RecyclerView$ViewHolder;II)V
public abstract fun onViewHolderSelectedAndAligned (Landroidx/recyclerview/widget/RecyclerView;Landroidx/recyclerview/widget/RecyclerView$ViewHolder;II)V
}

public final class com/rubensousa/dpadrecyclerview/OnViewHolderSelectedListener$DefaultImpls {
public static fun onViewHolderDeselected (Lcom/rubensousa/dpadrecyclerview/OnViewHolderSelectedListener;Landroidx/recyclerview/widget/RecyclerView;Landroidx/recyclerview/widget/RecyclerView$ViewHolder;)V
public static fun onViewHolderSelected (Lcom/rubensousa/dpadrecyclerview/OnViewHolderSelectedListener;Landroidx/recyclerview/widget/RecyclerView;Landroidx/recyclerview/widget/RecyclerView$ViewHolder;II)V
public static fun onViewHolderSelectedAndAligned (Lcom/rubensousa/dpadrecyclerview/OnViewHolderSelectedListener;Landroidx/recyclerview/widget/RecyclerView;Landroidx/recyclerview/widget/RecyclerView$ViewHolder;II)V
}
Expand Down Expand Up @@ -485,12 +490,14 @@ public final class com/rubensousa/dpadrecyclerview/layoutmanager/DpadLayoutParam
public final class com/rubensousa/dpadrecyclerview/layoutmanager/PivotLayoutManager : androidx/recyclerview/widget/RecyclerView$LayoutManager, androidx/recyclerview/widget/ItemTouchHelper$ViewDropHandler, androidx/recyclerview/widget/RecyclerView$SmoothScroller$ScrollVectorProvider {
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;II)V
public fun <init> (Landroidx/recyclerview/widget/RecyclerView$LayoutManager$Properties;)V
public final fun addOnChildLaidOutListener (Lcom/rubensousa/dpadrecyclerview/OnChildLaidOutListener;)V
public final fun addOnLayoutCompletedListener (Lcom/rubensousa/dpadrecyclerview/DpadRecyclerView$OnLayoutCompletedListener;)V
public final fun addOnViewFocusedListener (Lcom/rubensousa/dpadrecyclerview/OnViewFocusedListener;)V
public final fun addOnViewHolderSelectedListener (Lcom/rubensousa/dpadrecyclerview/OnViewHolderSelectedListener;)V
public fun canScrollHorizontally ()Z
public fun canScrollVertically ()Z
public fun checkLayoutParams (Landroidx/recyclerview/widget/RecyclerView$LayoutParams;)Z
public final fun clearOnChildLaidOutListeners ()V
public final fun clearOnLayoutCompletedListeners ()V
public final fun clearOnViewFocusedListeners ()V
public final fun clearOnViewHolderSelectedListeners ()V
Expand Down Expand Up @@ -547,6 +554,7 @@ public final class com/rubensousa/dpadrecyclerview/layoutmanager/PivotLayoutMana
public fun onSaveInstanceState ()Landroid/os/Parcelable;
public fun performAccessibilityAction (Landroidx/recyclerview/widget/RecyclerView$Recycler;Landroidx/recyclerview/widget/RecyclerView$State;ILandroid/os/Bundle;)Z
public fun prepareForDrop (Landroid/view/View;Landroid/view/View;II)V
public final fun removeOnChildLaidOutListener (Lcom/rubensousa/dpadrecyclerview/OnChildLaidOutListener;)V
public final fun removeOnLayoutCompletedListener (Lcom/rubensousa/dpadrecyclerview/DpadRecyclerView$OnLayoutCompletedListener;)V
public final fun removeOnViewFocusedListener (Lcom/rubensousa/dpadrecyclerview/OnViewFocusedListener;)V
public final fun removeOnViewHolderSelectedListener (Lcom/rubensousa/dpadrecyclerview/OnViewHolderSelectedListener;)V
Expand All @@ -571,7 +579,6 @@ public final class com/rubensousa/dpadrecyclerview/layoutmanager/PivotLayoutMana
public final fun setLoopDirection (Lcom/rubensousa/dpadrecyclerview/DpadLoopDirection;)V
public final fun setMaxPendingAlignments (I)V
public final fun setMaxPendingMoves (I)V
public final fun setOnChildLaidOutListener (Lcom/rubensousa/dpadrecyclerview/OnChildLaidOutListener;)V
public final fun setOrientation (I)V
public final fun setParentAlignment (Lcom/rubensousa/dpadrecyclerview/ParentAlignment;Z)V
public final fun setRecycleChildrenOnDetach (Z)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import com.rubensousa.dpadrecyclerview.OnViewHolderSelectedListener
import com.rubensousa.dpadrecyclerview.UnboundViewPool
import com.rubensousa.dpadrecyclerview.ViewHolderTask
import com.rubensousa.dpadrecyclerview.test.tests.AbstractTestAdapter
import com.rubensousa.dpadrecyclerview.testfixtures.DpadDeselectionEvent
import com.rubensousa.dpadrecyclerview.testfixtures.DpadFocusEvent
import com.rubensousa.dpadrecyclerview.testfixtures.DpadSelectionEvent
import com.rubensousa.dpadrecyclerview.testing.R
Expand Down Expand Up @@ -59,6 +60,7 @@ open class TestGridFragment : Fragment(R.layout.dpadrecyclerview_test_container)
}

private val selectionEvents = ArrayList<DpadSelectionEvent>()
private val deselectionEvents = ArrayList<DpadDeselectionEvent>()
private val viewHolderSelections = ArrayList<Int>()
private val viewHolderDeselections = ArrayList<Int>()
private val tasks = ArrayList<DpadSelectionEvent>()
Expand All @@ -81,7 +83,7 @@ open class TestGridFragment : Fragment(R.layout.dpadrecyclerview_test_container)
}
recyclerView.addOnViewHolderSelectedListener(this)
recyclerView.addOnViewFocusedListener(this)
recyclerView.setOnChildLaidOutListener(this)
recyclerView.addOnChildLaidOutListener(this)

recyclerView.apply {
setReverseLayout(layoutConfig.reverseLayout)
Expand Down Expand Up @@ -127,6 +129,10 @@ open class TestGridFragment : Fragment(R.layout.dpadrecyclerview_test_container)
)
}

override fun onViewHolderDeselected(parent: RecyclerView, child: RecyclerView.ViewHolder) {
deselectionEvents.add(DpadDeselectionEvent(child))
}

override fun onViewHolderSelected(
parent: RecyclerView,
child: RecyclerView.ViewHolder?,
Expand Down Expand Up @@ -196,6 +202,8 @@ open class TestGridFragment : Fragment(R.layout.dpadrecyclerview_test_container)

fun getSelectionEvents(): List<DpadSelectionEvent> = selectionEvents

fun getDeselectionEvents(): List<DpadDeselectionEvent> = deselectionEvents

fun getViewHolderSelections(): List<Int> = viewHolderSelections

fun getViewHolderDeselections(): List<Int> = viewHolderDeselections
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,14 @@ class ReverseVerticalTest : DpadRecyclerViewTest() {

repeat(5) {
KeyEvents.pressDown()
waitForIdleScrollState()
column.scrollDown()
assertChildrenPositions(column)
}

repeat(5) {
KeyEvents.pressUp()
waitForIdleScrollState()
column.scrollUp()
assertChildrenPositions(column)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import com.rubensousa.dpadrecyclerview.test.helpers.selectPosition
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.testfixtures.DpadDeselectionEvent
import com.rubensousa.dpadrecyclerview.testfixtures.DpadSelectionEvent
import com.rubensousa.dpadrecyclerview.testing.KeyEvents
import com.rubensousa.dpadrecyclerview.testing.R
Expand Down Expand Up @@ -283,4 +284,22 @@ class SelectionTest : DpadRecyclerViewTest() {
assertFocusAndSelection(numberOfItems + 9)
}

@Test
fun testDeselectionEventIsSent() {
// given
launchFragment()

// when
KeyEvents.pressDown()
waitForIdleScrollState()

// then
var receivedEvents: List<DpadDeselectionEvent> = emptyList()
executeOnFragment { fragment ->
receivedEvents = fragment.getDeselectionEvents()
}
assertThat(receivedEvents.size).isEqualTo(1)
assertThat(receivedEvents.first().viewHolder.layoutPosition).isEqualTo(0)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1560,8 +1560,37 @@ open class DpadRecyclerView @JvmOverloads constructor(
*
* @param listener the listener to be invoked.
*/
@Deprecated("Use addOnChildLaidOutListener")
fun setOnChildLaidOutListener(listener: OnChildLaidOutListener?) {
requireLayout().setOnChildLaidOutListener(listener)
requireLayout().clearOnLayoutCompletedListeners()
if (listener != null) {
requireLayout().addOnChildLaidOutListener(listener)
}
}

/**
* Registers a callback to be invoked when an item of this [DpadRecyclerView] has been laid out.
*
* @param listener the listener to be invoked.
*/
fun addOnChildLaidOutListener(listener: OnChildLaidOutListener) {
requireLayout().addOnChildLaidOutListener(listener)
}

/**
* @see addOnChildLaidOutListener
*
* @param listener the listener to be removed.
*/
fun removeOnChildLaidOutListener(listener: OnChildLaidOutListener) {
requireLayout().removeOnChildLaidOutListener(listener)
}

/**
* Clears all listeners added by [addOnChildLaidOutListener]
*/
fun clearOnChildLaidOutListeners() {
requireLayout().clearOnChildLaidOutListeners()
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,14 @@ interface OnViewHolderSelectedListener {
) {
}

/**
* @param parent The RecyclerView where the deselection happened.
* @param child The ViewHolder within the RecyclerView that was deselected
*/
fun onViewHolderDeselected(
parent: RecyclerView,
child: RecyclerView.ViewHolder,
) {
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -706,8 +706,16 @@ class PivotLayoutManager(properties: Properties) : RecyclerView.LayoutManager(),
return layoutInfo.findLastCompletelyVisiblePosition()
}

fun setOnChildLaidOutListener(listener: OnChildLaidOutListener?) {
pivotLayout.setOnChildLaidOutListener(listener)
fun addOnChildLaidOutListener(listener: OnChildLaidOutListener) {
pivotLayout.addOnChildLaidOutListener(listener)
}

fun removeOnChildLaidOutListener(listener: OnChildLaidOutListener) {
pivotLayout.removeOnChildLaidOutListener(listener)
}

fun clearOnChildLaidOutListeners() {
pivotLayout.clearOnChildLaidOutListeners()
}

fun addOnLayoutCompletedListener(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package com.rubensousa.dpadrecyclerview.layoutmanager
import android.util.Log
import android.view.View
import android.view.ViewParent
import androidx.core.view.ViewCompat
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.LayoutManager
import androidx.recyclerview.widget.RecyclerView.ViewHolder
Expand Down Expand Up @@ -65,7 +64,7 @@ internal class PivotSelector(
private val requestLayoutRunnable = Runnable {
layoutManager.requestLayout()
}
private var selectedViewHolder: DpadViewHolder? = null
private var selectedViewHolder: ViewHolder? = null
private var pendingChildFocus: View? = null

fun update(newPosition: Int, newSubPosition: Int = 0): Boolean {
Expand Down Expand Up @@ -219,10 +218,21 @@ internal class PivotSelector(
dispatchViewHolderSelected()
dispatchViewHolderSelectedAndAligned()
}
/**
* Always update the selected ViewHolder on every layout update
*/
if (position >= 0 && position < layoutInfo.getItemCount()) {
layoutInfo.findViewByAdapterPosition(position)?.let { view ->
layoutInfo.getChildViewHolder(view)?.let {
selectedViewHolder = it
}
}
}
}

fun getCurrentSubPositions(): Int {
return selectedViewHolder?.getSubPositionAlignments()?.size ?: 0
val dpadViewHolder = selectedViewHolder as? DpadViewHolder
return dpadViewHolder?.getSubPositionAlignments()?.size ?: 0
}

fun setSelectionUpdatePending() {
Expand Down Expand Up @@ -332,18 +342,20 @@ internal class PivotSelector(
}

if (viewHolder !== selectedViewHolder) {
selectedViewHolder?.onViewHolderDeselected()
selectedViewHolder?.let { viewHolder ->
if (viewHolder is DpadViewHolder) {
viewHolder.onViewHolderDeselected()
}
selectionListeners.forEach { listener ->
listener.onViewHolderDeselected(recyclerView, viewHolder)
}
}
if (viewHolder is DpadViewHolder) {
selectedViewHolder = viewHolder
viewHolder.onViewHolderSelected()
} else {
selectedViewHolder = null
}
}

if (!hasSelectionListeners()) {
return
}
selectedViewHolder = viewHolder

if (viewHolder != null) {
selectionListeners.forEach { listener ->
Expand Down Expand Up @@ -393,10 +405,6 @@ internal class PivotSelector(
viewHolder.onViewHolderSelectedAndAligned()
}

if (!hasSelectionListeners()) {
return
}

if (viewHolder != null) {
selectionListeners.forEach { listener ->
listener.onViewHolderSelectedAndAligned(
Expand Down Expand Up @@ -446,7 +454,16 @@ internal class PivotSelector(
subPosition = 0
positionOffset = 0
}
selectedViewHolder?.onViewHolderDeselected()
selectedViewHolder?.let { viewHolder ->
if (viewHolder is DpadViewHolder) {
viewHolder.onViewHolderDeselected()
}
recyclerView?.let {
selectionListeners.forEach { listener ->
listener.onViewHolderDeselected(it, viewHolder)
}
}
}
selectedViewHolder = null
}

Expand All @@ -456,9 +473,7 @@ internal class PivotSelector(
* We might need to resize rows when wrap_content is used, so schedule a new layout request
*/
private fun scheduleNewLayout(recyclerView: RecyclerView) {
ViewCompat.postOnAnimation(recyclerView, requestLayoutRunnable)
recyclerView.postOnAnimation(requestLayoutRunnable)
}

private fun hasSelectionListeners(): Boolean = selectionListeners.isNotEmpty()

}
Loading
Loading