I am building an app using Kotlin with the MVVM approach, and my Recycler View using multiple view types.
Inside my List Adapter inside the override fun onBindViewHolder, I have a code that detects the first click on the row, the second click to the same row and the first click to the different row.
The click detection works correctly. My goal here is to save the correct view id when I click on the row the first time, then when I click on a different row I would like to find the first row and put back the original background. I know that this is a recycler view, but I do not scroll the view I just would like to sort the click and put back the original background. I already saw a lot of examples where someone hardcoded the background colour, but this is not what I am looking for.
I already tried to save the view id, but seems to me I am saving the wrong id because when I try to restore the current view id is the same as the saved view id.
The code that should find the previouse view is this:
val prevConstrainLayoutView = holder.itemView.findViewById<ConstraintLayout>(prevClickedItemViewId)
}
- How to save the correct view id or something else and then restore the previously clicked row with the original background colour?
Current Androdi Handheld Screen
onBindViewHolder
override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
val current = getItem(position)
holder.bind(current)
Log.d("onBindViewHolder->", "Views")
// apply the click listener for the item
holder.itemView.setOnClickListener {
// that should check if something was selected, but not sure
if (holder.bindingAdapterPosition != RecyclerView.NO_POSITION) {
onClickListener.onClick(current)
if (clicked == 1 && clickedItem != current.id) {
prevClickedItem = clickedItem
prevClickedItemType = clickedItemType
prevClickedItemViewId = clickedItemViewId
prevClickedItemRootBackgroundDrawable = clickedItemRootBackgroundDrawable
prevClickedItemRootBackgroundColour = clickedItemRootBackgroundColour
prevClickedItemView = clickedItemView
clicked = 1
clickedItemRootBackgroundDrawable = holder.itemView.background.current
clickedItemRootBackgroundColour = holder.itemView.solidColor
clickedItemViewId = holder.itemView.id
clickedItemType = current.orderBy
clickedItem = current.id
clickedItemView = holder.itemView
clickedItemView.tag = 2
if (clickedItem!=prevClickedItem && prevClickedItemViewId!=null && prevClickedItemType!=-1 && clickedConstraintLayout!=null) {
val prevConstrainLayoutView = holder.itemView.findViewById<ConstraintLayout>(prevClickedItemViewId)
Log.d("onBindViewHolder->", "Clicked second time different row")
Log.d("onBindViewHolder->", "$prevConstrainLayoutView and $prevClickedItemType")
when (prevClickedItemType) {
TYPE_ZERO -> {
prevConstrainLayoutView.setBackgroundColor(ContextCompat.getColor(holder.itemView.context, R.color.green_sushi))
Log.d("onBindViewHolder->", "Clicked second time different row, set the prev view to: green_sushi")
}
TYPE_ONE -> {
prevConstrainLayoutView.setBackgroundColor(ContextCompat.getColor(holder.itemView.context, R.color.yellow_background))
Log.d("onBindViewHolder->", "Clicked second time different row, set the prev view to: yellow_background")
}
TYPE_TWO -> {
prevConstrainLayoutView.setBackgroundColor(ContextCompat.getColor(holder.itemView.context, R.color.white_text))
Log.d("onBindViewHolder->", "Clicked second time different row, set the prev view to: white_text")
}
TYPE_THREE -> {
prevConstrainLayoutView.setBackgroundColor(ContextCompat.getColor(holder.itemView.context, R.color.blue_heather))
Log.d("onBindViewHolder->", "Clicked second time different row, set the prev view to: blue_heather")
}
TYPE_FOUR -> {
prevConstrainLayoutView.setBackgroundResource(R.drawable.purple_orange_background)
Log.d("onBindViewHolder->", "Clicked second time different row, set the prev view to: purple_orange_background")
}
else -> {
prevConstrainLayoutView.setBackgroundColor(ContextCompat.getColor(holder.itemView.context, R.color.green_sushi))
Log.d("onBindViewHolder->", "Clicked second time different row, set the prev view to: green_sushi")
}
}
}
holder.itemView.setBackgroundColor(
ContextCompat.getColor(
holder.itemView.context,
R.color.blue_background
)
)
} else if (clicked == 1 && clickedItem == current.id) {
// second click the same row
clicked = 0
clickedItem = current.id
} else if (clicked == 0) {
// first click
clicked = 1
clickedItem = current.id
clickedItemType = current.orderBy
clickedItemViewId = holder.itemView.id
holder.itemView.tag = 1
clickedItemRootBackgroundDrawable = holder.itemView.background.current
clickedItemRootBackgroundColour = holder.itemView.solidColor
clickedItemView = holder.itemView
clickedConstraintLayout = holder.itemView.findViewById<ConstraintLayout>(R.id.root)
Log.d("onBindViewHolder->", "Clicked first time, set the view to: blue_background, "
"\nconstraint layout:$clickedConstraintLayout")
holder.itemView.setBackgroundColor(
ContextCompat.getColor(
holder.itemView.context,
R.color.blue_background
)
)
}
}
}
}
item view layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@ id/root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/purple_orange_background"
android:paddingLeft="24dp"
android:paddingRight="24dp">
<TextView
android:id="@ id/textView"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:gravity="center_vertical"
android:text="View 6 TextView"
android:textColor="@color/white"
android:textSize="24sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
This is what I am using in my ListAdapter:
class WordListAdapter(private val onClickListener: MyRecyclerViewOnClickListener) :
ListAdapter<Word, WordListAdapter.WordViewHolder>(WordsComparator()) {
This is part of the code from View Model:
val allOrderedWords: LiveData<List<Word>> = repository.allOrderedWords.asLiveData()
This is in my Activity:
wordViewModel.allOrderedWords.observe(this, Observer { words ->
// Update the cached copy of the words in the adapter.
words?.let { adapter.submitList(it) }
})
CodePudding user response:
I meant that the view holder should always represent the item and never be a source of information. Single scroll and you will lose it. Do not store "view id", store information about what item it was.
var clickedItem: Word? = null
override fun onBindViewHolder(holder: WordViewHolder, position: Int) {
val current = getItem(position)
val setBackgroundColor: (Int) -> Unit = { color ->
viewHolder.itemView.setBackgroundColor(ContextCompat.getColor(viewHolder.itemView.context,color)
}
holder.bind(current)
val color = when (current.itemType) {
TYPE_ZERO -> R.color.green_sushi
TYPE_ONE -> R.color.yellow_background
TYPE_TWO -> R.color.white_text
TYPE_THREE -> R.color.blue_heather
TYPE_FOUR -> R.drawable.purple_orange_background
else -> R.color.green_sushi
}
setBackgroundColor(color)
holder.itemView.setOnClickListener {
onClickListener.onClick(current)
when {
clickedItem == null -> {
clickedItem= current
setBackgroundColor(R.color.blue_background)
}
clickedItem == current -> {
// Here I assume that you want to clear the "mark"?
clickedItem= null
notifyItemChanged(position)
}
clicked != current -> {
val oldClicked = clickedItem
clickedItem = current
val indexOfPreviouslyClicked = data.indexOf(oldClicked)
notifyItemChanged(indexOfPreviouslyClicked)
setBackgroundColor(R.color.blue_background)
}
}
}
}
This way you will be able to say RecyclerView
(with notifyItemChanged(pos)
) itself that a certain item should be updated and it will force onBindViewHolder
on that item.
CodePudding user response:
In the end, I find a solution that solves the issue and works exactly as expected.
Step by step how I solve the issue:
Every item layout point to their drawable state.
android:background="@drawable/green_sushi_state"
Every drawable state has two pointers one when you click - select item and second is the default
<item android:drawable="@drawable/pressed_blue_background" android:state_pressed="false" android:state_selected="true" />
<item android:drawable="@drawable/green_sushi_background" />
These two pointers point to the background with the colour that you prefer.
Under my ListAdapter I am using SparseBooleanArray to keep track of what was selected :
private val tvSparseBooleanArraySelectedItems = SparseBooleanArray()
Inside my onBindViewHolder function, I am checking if the position exists in the SparseBooleanArray or not. The default value is false. The value is assigned to my Constraint Layout. The value is assigned whenever you scroll.
val mConstraintLayout = holder.itemView.findViewById<ConstraintLayout>(R.id.root) val isItSelected = tvSparseBooleanArraySelectedItems.getOrDefault(position, false) mConstraintLayout.isSelected = isItSelected
Under my ViewHolder inner class, I have multiple types of my views.
private fun bindOther(word: Word) { //Do your view assignment here from the data model val mTextView = itemView.findViewById<TextView>(R.id.textView) mTextView.text = word.word val mConstraintLayout = itemView.findViewById<ConstraintLayout>(R.id.root) mTextView.setOnClickListener { onClickListener.onClick(word) manageSparseBooleanArrayData(tvSparseBooleanArraySelectedItems, bindingAdapterPosition) mConstraintLayout.isSelected = true } }
fun manageSparseBooleanArrayData manages the SparseBooleanArray by iterating through, finding the key, deleting the key and notifyItemChanged(keyIn). I am iterating through the SparseBooleanArrayData because I want to use notifyItemChanged instead of notifyDataSetChanged.
fun manageSparseBooleanArrayData(_sparseBooleanArrayOnTextView: SparseBooleanArray, _position: Int) { when (currentTvSelectionMode) { SINGLE_SELECTION -> { if (_sparseBooleanArrayOnTextView.isNotEmpty()) { for (i in 0 until _sparseBooleanArrayOnTextView.size()) { if (_sparseBooleanArrayOnTextView.valueAt(i)) { val keyIn = _sparseBooleanArrayOnTextView.keyAt(i) val value = _sparseBooleanArrayOnTextView.get(keyIn) _sparseBooleanArrayOnTextView.delete(keyIn) notifyItemChanged(keyIn) } } } tvSparseBooleanArraySelectedItems.put(_position, true) } MULTIPLE_SELECTION -> { tvSparseBooleanArraySelectedItems.put(_position, true) } else -> {} } }
Hope you find my solution helpful.