recyclerview-selection: stop auto item deselection when touching to the blank space inside the recyclerview

Viewed 817

I am trying to use the recyclerview-selection library in my project. I followed this tutorial:

https://proandroiddev.com/a-guide-to-recyclerview-selection-3ed9f2381504

Everything workes fine. But I have a problem. If I tap/touch any blank space inside the RecyclerView, all the selected elements got deselected! I don't find any method or solution to disable this. What should I do?

I am using implementation 'androidx.recyclerview:recyclerview-selection:1.1.0-rc01' in my project.

Edit 1:

example image

I set the RecyclerView background red to describe my problem. Here, blue items are selected items. If I click any red area, then all the selected items got unselected! The select and deselect should only be done by clicking the items. So, I need to disable this feature (or bug!), that unselect all items!

Example project: https://github.com/ImaginativeShohag/multiselection

2 Answers

Solution is to have an out of context selection item like:

class ItemLookup(private val recyclerView: RecyclerView) : ItemDetailsLookup<Long>() {
    private val outOfContextSelection = object : ItemDetails<Long>() {
        override fun getPosition(): Int = OUT_OF_CONTEXT_POSITION.toInt()
        override fun getSelectionKey() = OUT_OF_CONTEXT_POSITION
    }

    override fun getItemDetails(e: MotionEvent): ItemDetails<Long>? {
        recyclerView.findChildViewUnder(e.x, e.y)?.let {
            return (recyclerView.getChildViewHolder(it) as?
                SelectorBarAdapter.SelectorBarViewHolder)?.itemDetails
        }
        return outOfContextSelection
    }

    companion object {
        const val OUT_OF_CONTEXT_POSITION = 10000L
    }
}

so that when a view which is not one of our clickable elements is clicked we can identify further on a predicate like follows:

class SingleSelectionPredicate : SelectionTracker.SelectionPredicate<Long>() {
override fun canSetStateForKey(key: Long, nextState: Boolean): Boolean {
    // warranties that an item can't be unselected on click
    // warranties that clicks out of the item's scope are disabled
    return nextState && key != ItemLookup.OUT_OF_CONTEXT_POSITION
}

override fun canSetStateAtPosition(position: Int, nextState: Boolean) = true
override fun canSelectMultiple() = false

}

You create your own SelectionTracker.SelectionPredicate<Long>.

Override the method canSetStateForKey(key: Long, nextState: Boolean)

like this:

override fun canSetStateForKey(@NonNull key: Long, nextState: Boolean): Boolean {
    rv.findViewHolderForItemId(key)?.let { holder -> adapter.canSetStateForItem(holder as YourItemHolder<YourItem>, nextState)}
    return true
}

and in your ViewHolder check if the item is already selected if so return false or vice versa.

Edit:

You can also define a "Selection Hotspot" by inSelectionHotspot(e: MotionEvent) in your ItemDetailsLookup.ItemDetails.

In your ViewHolder you can then check if the TouchEvent is in an area that requires a select or unselect.

for example:

override fun inSelectionHotspot(e: MotionEvent): Boolean {
    val rect = Rect()
    itemView.getGlobalVisibleRect(rect)
    if (rect.contains(e.rawX.toInt(), e.rawY.toInt())) { 
        //  in select region
    } else { 
        // not in select region 
    }
}
Related