I'm using the RecyclerView selection library but my ViewHolders also have their own OnClickListeners. When I select items quickly after another, or double-tap an item, instead of selecting/unselecting the item, the click is passed through to my own OnClickListener. This behavior is really annoying. Is this a bug or a problem with my setup?
This is my code (I removed the not interesting parts).
Adapter:
class UserListAdapter : ListAdapter<User, UserListAdapter.UserViewHolder>(USER_COMPARATOR) {
private var listener: ((User) -> Unit)? = null
[...]
override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
val user = getItem(position)
val tracker = tracker
if (user != null && tracker != null) {
holder.bind(user, tracker.isSelected(user.uid))
}
}
inner class UserViewHolder(private val binding: ItemUserListBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
binding.root.setOnClickListener {
val position = bindingAdapterPosition
if (position != RecyclerView.NO_POSITION) {
val item = getItem(position)
listener?.invoke(item)
}
}
}
fun bind(user: User, isSelected: Boolean) {
binding.apply {
[...]
root.isActivated = isSelected
}
}
fun getItemDetails() = object : ItemDetailsLookup.ItemDetails<String>() {
override fun getPosition() = bindingAdapterPosition
override fun getSelectionKey() = getItem(bindingAdapterPosition).uid
}
}
class KeyProvider(private val adapter: UserListAdapter) :
ItemKeyProvider<String>(SCOPE_CACHED) {
override fun getKey(position: Int) = adapter.currentList[position].uid
override fun getPosition(key: String) =
adapter.currentList.indexOfFirst { it.uid == key }
}
class DetailsLookup(private val recyclerView: RecyclerView) : ItemDetailsLookup<String>() {
override fun getItemDetails(e: MotionEvent): ItemDetails<String>? {
val view = recyclerView.findChildViewUnder(e.x, e.y)
if (view != null) {
return (recyclerView.getChildViewHolder(view) as UserListAdapter.UserViewHolder).getItemDetails()
}
return null
}
}
var tracker: SelectionTracker<String>? = null
fun setOnClickListener(listener: (User) -> Unit) {
this.listener = listener
}
[...]
}
Fragment:
@AndroidEntryPoint
class UserListFragment : Fragment(R.layout.fragment_user_list) {
[...]
private lateinit var selectionTracker: SelectionTracker<String>
private var actionMode: ActionMode? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
[...]
val userAdapter = UserListAdapter()
binding.apply {
recyclerView.apply {
adapter = userAdapter
layoutManager = LinearLayoutManager(requireContext())
setHasFixedSize(true)
addItemDecoration(
DividerItemDecoration(
requireContext(),
DividerItemDecoration.VERTICAL
)
)
}
selectionTracker = SelectionTracker.Builder(
"user selection",
recyclerView,
UserListAdapter.KeyProvider(userAdapter),
UserListAdapter.DetailsLookup(recyclerView),
StorageStrategy.createStringStorage()
).withSelectionPredicate(
SelectionPredicates.createSelectAnything()
).build()
userAdapter.tracker = selectionTracker
selectionTracker.addObserver(selectionObserver)
}
if (savedInstanceState != null) {
selectionTracker.onRestoreInstanceState(savedInstanceState)
if (selectionTracker.hasSelection()) {
selectionObserver.onSelectionChanged()
}
}
userAdapter.setOnClickListener { user ->
viewModel.onUserClick(user)
}
viewModel.userList.observe(viewLifecycleOwner) { userList ->
userAdapter.submitList(userList)
}
[...]
}
private val selectionObserver = object : SelectionTracker.SelectionObserver<String>() {
override fun onSelectionChanged() {
if (selectionTracker.selection.size() > 0) {
if (actionMode == null) {
actionMode =
(requireActivity() as AppCompatActivity).startSupportActionMode(
actionModeCallback
)
}
actionMode?.title = selectionTracker.selection.size().toString()
} else {
actionMode?.finish()
}
selectionTracker.copySelection(viewModel.selection)
}
}
private val actionModeCallback = object : ActionMode.Callback {
override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
mode?.menuInflater?.inflate(R.menu.menu_action_mode_user_list_fragment, menu)
return true
}
override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
return false
}
override fun onActionItemClicked(
mode: ActionMode?,
item: MenuItem?
): Boolean {
return when (item?.itemId) {
R.id.action_new_group -> {
viewModel.onNewGroupClick()
actionMode?.finish()
true
}
else -> false
}
}
override fun onDestroyActionMode(mode: ActionMode?) {
actionMode = null
selectionTracker.clearSelection()
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
if (::selectionTracker.isInitialized) {
selectionTracker.onSaveInstanceState(outState)
}
}
override fun onDestroyView() {
super.onDestroyView()
actionMode?.finish()
}
}
It is a bug in recyclerview-selection
library
I suggest you increment recyclerview-selection library to latest:
'androidx.recyclerview:recyclerview-selection:1.1.0-rc03'
Here is the list of changes