Home > Software design >  How to find find specific Views of the Recycler View and change the background colour?
How to find find specific Views of the Recycler View and change the background colour?

Time:11-06

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)
  }
  1. 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

enter image description here

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:

  1. Every item layout point to their drawable state.

    android:background="@drawable/green_sushi_state"
    
  2. 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.

  1. Under my ListAdapter I am using SparseBooleanArray to keep track of what was selected :

    private val tvSparseBooleanArraySelectedItems = SparseBooleanArray()
    
  2. 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
    
  3. 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
        }
    
    }
    
  4. 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.

  • Related