RecyclerView ViewHolder滚动后状态错乱问题求助
Hey, I've run into this exact headache with RecyclerViews before—total classic issue tied to how ViewHolder reuse works. Let's break down why this is happening and how to fix it for good.
Why This Happens
RecyclerView reuses ViewHolders to save memory instead of creating a new one for every list item. When you scroll, off-screen ViewHolders get pulled back into view and rebound to new data. If you're storing playback state (like play/pause visibility, seekbar status) only in the ViewHolder itself, that old state gets carried over to whatever new list item the ViewHolder is now displaying. The "regular interval" you're seeing is exactly the size of RecyclerView's ViewHolder pool—once it cycles through those, the same reused states pop up again.
The Fix: Bind State to Your Data Source, Not the ViewHolder
The key rule here is: all UI state for a list item should live in your data model, not the ViewHolder. Here's how to implement this step by step:
Add State Fields to Your Data Model
Update your song/track data class to include fields that track playback state. For example:data class Track( val id: String, val title: String, val artist: String, var isPlaying: Boolean = false, var currentSeekProgress: Int = 0 )This way, every track's playback state is tied directly to its data, not the temporary ViewHolder that displays it.
Refresh UI from the Data Source in
onBindViewHolder
Every time a ViewHolder is bound to a new item, you pull the state from the data model and update the UI accordingly—never rely on the ViewHolder's existing state. Here's what that looks like:override fun onBindViewHolder(holder: TrackViewHolder, position: Int) { val currentTrack = trackList[position] // Set basic content first holder.trackTitle.text = currentTrack.title holder.trackArtist.text = currentTrack.artist // Update playback-related UI based on data model state if (currentTrack.isPlaying) { holder.playBtn.visibility = View.GONE holder.pauseBtn.visibility = View.VISIBLE holder.seekBar.visibility = View.VISIBLE holder.seekBar.progress = currentTrack.currentSeekProgress } else { holder.playBtn.visibility = View.VISIBLE holder.pauseBtn.visibility = View.GONE holder.seekBar.visibility = View.GONE } // Handle play/pause clicks holder.playBtn.setOnClickListener { // Update the data model first currentTrack.isPlaying = true // Notify RecyclerView to refresh this specific item notifyItemChanged(position) // Trigger your actual playback logic here } holder.pauseBtn.setOnClickListener { currentTrack.isPlaying = false notifyItemChanged(position) // Trigger pause logic here } }Handle Global Playback State (If Only One Track Plays At a Time)
If your app only allows one track to play at once, you need to reset the previous playing track's state when a new one starts. Add a variable to track the current playing position, then update both items when switching:private var currentPlayingPosition: Int = -1 // Inside your play button click listener: if (currentPlayingPosition != -1) { // Reset the old playing track's state trackList[currentPlayingPosition].isPlaying = false notifyItemChanged(currentPlayingPosition) } // Set the new track as playing currentPlayingPosition = position trackList[position].isPlaying = true notifyItemChanged(position)Update Seekbar Progress Correctly
For real-time seekbar updates during playback, don't modify the ViewHolder directly. Instead, update thecurrentSeekProgressfield in the data model for the playing track, then callnotifyItemChanged(currentPlayingPosition)to refresh the UI.
Quick Recap
The root issue is that ViewHolders are temporary and reused—so never store item-specific state in them. Always tie state to your data model, and refresh the UI from that model every time the ViewHolder is bound. This will eliminate those weird state swaps when scrolling.
内容的提问来源于stack exchange,提问作者Munim Ahmed

