Home > Blockchain >  Can you change the color of a textview in a recyclerview adapter after a certain condition is met in
Can you change the color of a textview in a recyclerview adapter after a certain condition is met in

Time:07-25

I have a basic function that displays the elapsed time every time the button is pressed. I cannot get the logic in MainActivity to transfer to the recyclerview adapter. I simply want the text output color to change to red after the time passes 5 seconds. I have tried to research how to do this for the past week and I cannot find the exact answer. I'm hoping someone can help.

I have tried it with and without the boolean in the data class. I wasn't sure if that was required.

Here is my code:

Main Activity:`

class MainActivity : AppCompatActivity() {
var startTime = SystemClock.elapsedRealtime()
var displaySeconds = 0

private lateinit var binding: ActivityMainBinding
private val secondsList = generateSecondsList()
private val secondsAdapter = Adapter(secondsList)

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    binding = ActivityMainBinding.inflate(layoutInflater)
    setContentView(binding.root)

    recyclerView.adapter = secondsAdapter
    recyclerView.layoutManager = LinearLayoutManager(this)
    recyclerView.setHasFixedSize(false)

    binding.button.setOnClickListener {
        getDuration()
        addSecondsToRecyclerView()
    }

}

fun getDuration(): Int {
    val endTime = SystemClock.elapsedRealtime()
    val elapsedMilliSeconds: Long = endTime - startTime
    val elapsedSeconds = elapsedMilliSeconds / 1000.0
    displaySeconds = elapsedSeconds.toInt()
    return displaySeconds
}

private fun generateSecondsList(): ArrayList<Seconds> {
    return ArrayList()
}

fun addSecondsToRecyclerView() {
    val addSeconds =
        Seconds(getDuration(), true)
    secondsList.add(addSeconds)
    secondsAdapter.notifyItemInserted(secondsList.size - 1)
}

}

Adapter:

    var adapterSeconds = MainActivity().getDuration()

class Adapter( private val rvDisplay: MutableList ) : RecyclerView.Adapter<Adapter.AdapterViewHolder>() {

    class AdapterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

    val textView1: TextView = itemView.tv_seconds
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AdapterViewHolder {
    val myItemView = LayoutInflater.from(parent.context).inflate(
        R.layout.rv_item,
        parent, false
    )
    return AdapterViewHolder(myItemView)
}


override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
    val currentDisplay = rvDisplay[position]

    currentDisplay.isRed = adapterSeconds > 5

    holder.itemView.apply {
        val redColor = ContextCompat.getColor(context, R.color.red).toString()
        val blackColor = ContextCompat.getColor(context, R.color.black).toString()
        if (currentDisplay.isRed) {
            holder.textView1.setTextColor(redColor.toInt())
            holder.textView1.text = currentDisplay.rvSeconds.toString()
        } else {
            holder.textView1.setTextColor(blackColor.toInt())
            holder.textView1.text = currentDisplay.rvSeconds.toString()
        }
    }
}

override fun getItemCount() = rvDisplay.size

}

Data Class:

data class Seconds(
var rvSeconds: Int,
var isRed: Boolean

)

CodePudding user response:

when you call secondsList.add(addSeconds) then the data that is already inside secondsList should be updated too.

you could do something like

    private var secondsList = generateSecondsList() // make this var

    fun addSecondsToRecyclerView() {
        val addSeconds =
            Seconds(getDuration(), true)
        secondsList.add(addSeconds)
        if ( /* TODO check if time has passed */) {
           secondsList = secondsList.map { it.isRed = true }
           secondsAdapter.rvDisplay = secondsList // TODO also make rvDisplay a var
           secondsAdapter.notifyDatasetChanged() // also need to tell rv to redraw the all views
        } else {
           secondsAdapter.notifyItemInserted(secondsList.size - 1)
        }
    }

that might work, but to be honest it looks bad... There is already a lot of logic inside Activity. Read about MVVM architecture and LiveData, there should be another class called ViewModel that would keep track of time and the data. Activity should be as simple as possible, because it has lifecycle, so if you rotate the screen, all your state will be lost.

CodePudding user response:

Your code isn't really working because of this:

var adapterSeconds = MainActivity().getDuration()

override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
    ...
    currentDisplay.isRed = adapterSeconds > 5
    ...
}

You're only setting adapterSeconds right there, so it never updates as time passes. I assume you want to know the moment 5 seconds has elapsed, and then update the RecyclerView at that moment - in that case you'll need some kind of timer task that will fire after 5 seconds, and can tell the adapter to display things as red. Let's deal with that first:

class Adapter( private val rvDisplay: MutableList ) : RecyclerView.Adapter<Adapter.AdapterViewHolder>() {

    private var displayRed = false
        set(value) {
            field = value
            // Refresh the display - the ItemChanged methods mean something about the items
            // has changed, rather than a structural change in the list
            // But you can use notifyDataSetChanged if you want (better to be specific though)
            notifyItemRangeChanged(0, itemCount)
        }

    override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
        if (displayRed) {
            // show things as red - you shouldn't need to store that state in the items
            // themselves, it's not about them - it's an overall display state, right?
        } else {
            // display as not red
        }
    }

So with that setter function, every time you update displayRed it'll refresh the display, which calls onBindViewHolder, which checks displayRed to see how to style things. It's better to put all this internal refreshing stuff inside the adapter - just pass it data and events, let it worry about what needs to happen internally and to the RecyclerView it's managing, y'know?


Now we have a thing we can set to control how the list looks, you just need a timer to change it. Lots of ways to do this - a CountdownTimer, a coroutine, but let's keep things simple for this example and just post a task to the thread's Looper. We can do that through any View instead of creating a Handler:

// in MainActivity
recyclerView.postDelayed({ secondsAdapter.displayRed = true }, 5000)

That's it! Using any view, post a delayed function that tells the adapter to display as red.

It might be more helpful to store that runnable as an object:

private val showRedTask = Runnable { secondsAdapter.displayRed = true }
...
recyclerView.postDelayed(showRedTask, 5000)

because then you can easily cancel it

recyclerView.removeCallbacks(showRedTask)

Hopefully that's enough for you to put some logic together to get what you want. Set displayRed = false to reset the styling, use removeCallbacks to cancel any running task, and postDelayed to start a new countdown. Not the only way to do it, but it's pretty neat!

  • Related