I have a recycler view where I list different jobs and each job has a progress bar, because you can do multiple jobs at a time. I'm currently using a handler to reduce my progress bar, but it only works for one job at a time. I want it to be tied to a position rather than the full recycler view so I can have multiple handlers running at a time. How do I update my adapter so that the handler (or another timer if it would work better) is specific to the [position] rather than the full recycler view?
class JobAdapter(
private val context: Context,
private val dataset: List<JobList>
) : RecyclerView.Adapter<JobAdapter.ItemViewHolder>() {
class ItemViewHolder(private val view: View): RecyclerView.ViewHolder(view){
val tvJobName = view.findViewById<TextView>(R.id.tvJobName)
val tvJobTime = view.findViewById<TextView>(R.id.tvJobTime)
val tvJobReward = view.findViewById<TextView>(R.id.tvJobReward)
val pbJobTime = view.findViewById<ProgressBar>(R.id.pbJobTime)
val btnStartJob = view.findViewById<Button>(R.id.btnStartJob)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val jobLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.job_layout, parent, false)
return ItemViewHolder(jobLayout)
}
var currentProgress: Int = 0
var counter: Int=0
var isTriggered: Boolean = false
override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
holder.tvJobName.text = item.dcJobName
holder.tvJobTime.text = item.dcJobTime.toString() "s"
holder.tvJobReward.text = "$" item.dcJobReward.toString()
val maxAllowable=25
val mills = (1000/maxAllowable)*item.dcJobTime
holder.btnStartJob.setOnClickListener{
val jobHandler = Handler()
val jobRunnable: Runnable = object : Runnable {
override fun run() {
counter
if (counter<=maxAllowable){
currentProgress-=100/maxAllowable
holder.pbJobTime.progress = currentProgress
jobHandler.postDelayed(this, mills.toLong())
} else {
counter = 0
isTriggered=false
}
}
}
if (!isTriggered) {
currentProgress=100
holder.pbJobTime.progress = currentProgress
isTriggered=true
jobHandler.post(jobRunnable)
}
}
}
override fun getItemCount() = dataset.size }
CodePudding user response:
The point of a RecyclerView
is that you have a handful of ViewHolders
that get reused (recycled) to display all the items in the list. So at any given time, a particular VH could be representing any arbitrary item. But your runnable (which is tied to a specific item) is updating a specific ViewHolder - so even if it's not actually displaying that item, it will update with that item's progress.
So you're looking for a way to get around that, right? You could try and find out if a particular VH is displaying that particular item, and update it if so - but that's extremely messy, could get pretty complicated (you're not cancelling existing runnables right?) and you could get some awkward bugs happening.
Personally I'd recommend breaking this down into a few separate parts:
- your data and basic recycler view
- something that holds running items and their current progress states
- something like your Runnable that "ticks" every so often, updates the progress states, and tells the RecyclerView to update and display the current progress
The "current progress states" could just be a map of Item -> Progress. When you start a job, add that item and its initial progress to the map (this will clear any current progress, you could work around that if it's not what you want). Start the Handler if it's not already running
The Handler's runnable would just iterate over the map, update the progress for each item, remove it from the map if it's expired, and call notifyItemChanged
on the Adapter
(so it gets redrawn with the current progress). Ideally the Handler would stop if the map is empty (i.e. no jobs to keep updating).
Your display code in onBindViewHolder
could just check that map, see if the item it's displaying has a progress value, and display that if it does, otherwise show the "no running job" state (remember you need to update everything since the ViewHolder might have been displaying some other state before)
I hope that makes sense - having a single Runnable
task ticking every so often and updating the current state is way easier to manage than multiple Runnable
s doing their own thing. Having a single source of data about your jobs and their state makes it way easier to update them (and do things like persist that state). And having the RecyclerView
update its own display when necessary, by referring to a source of data, makes it way easier to manage that displayed data, and not worry about internal details like what ViewHolder
objects are visible and which item they currently represent