I have a strange problem.
I have a custom ListView
with BaseAdapter
. In my ListView
row layout, I have few TextView
s, Buttons
, and a SeekBar
. Everything works great, no problems with anything except with recycling.
What happens:
All SeekBar
's are hidden, except one which is visible. The SeekBar
is visible when MediaPlayer
on row is playing. That part is working great too.
But, when user scrolls up or down, and the row with visible SeekBar
is out of view, it recycles, and SeekBar
is recycled too and it keeps updating even tho it's not visible and that row is not playing(mp). And when users scrolls back to return to the view which is playing, SeekBar
is visible, but not updating and it's position is 0. Instead random SeekBar
is being updated, but it's not visible(I did test when all SeekBar
's are visible so I know that happens)
Of course I could've done most idiotic solution and disable ListView recycling but this makes user experience really bad and could possibly make app run out of memory, and using LargeHeap
is lame.
So the following methods are out of question.
@Override
public int getViewTypeCount() {
return getCount();
}
@Override
public int getItemViewType(int position) {
return position;
}
My question: Is there any obvious solution to this? How do I keep only one row from being recycled? I won't post code until it's really necessary, the code is too large. Instead I will just post important parts related to the issue:
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
View row = convertView;
holder = null;
if (row == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = inflater.inflate(R.layout.item, parent, false);
holder = new Ids(row);
row.setTag(holder);
} else {
holder = (Ids) row.getTag();
}
rowItemClass = (ListViewRow) getItem(position);
if (Globals.isPlaying && Globals.pos == position) {
holder.seekbar.setVisibility(View.VISIBLE);
} else {
holder.seekbar.setVisibility(View.GONE);
}
holder.seekbar.setTag(position);
final Ids finalHolder = holder;
...
OnClickListener{....
Globals.mp.start();
finalHolder.seekbar.setProgress(0);
finalHolder.seekbar.setMax(Globals.mp.getDuration());
..
updateTimeProgressBar = new Runnable() {
@Override
public void run() {
if (Globals.isPlaying) {
finalHolder.seekbar.setProgress(Globals.mp.getCurrentPosition());
int currentPosition = Globals.mp.getCurrentPosition();
handler.postDelayed(this, 100);
}
}
};
handler.postDelayed(updateTimeProgressBar, 100);
finalHolder.seekbar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
handler.removeCallbacks(updateTimeProgressBar);
Globals.mp.seekTo(finalHolder.seekbar.getProgress());
int currentPosition = Globals.mp.getCurrentPosition();
handler.postDelayed(updateTimeProgressBar, 500);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
handler.removeCallbacks(updateTimeProgressBar);
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// Intentionally left empty
}
});
}
and That's it.
Why is this happening?
Is there a way to disable recycling for one row?
Would be it be a bad idea to update all SeekBar
s in ListView
since only 1 mp will be playing? How bad is that for performance?
I'll outline one of the best ways to tackle this (in my opinion, of course) while respecting a
ListView's
ability to recycle.But first, this has got to go:
Solution:
Subclass
SeekBar
and let it handle all updates:Define two methods that will mark this
SeekBar
active & inactive.When marking a
SeekBar
active, provide the current position (progress). Alongside, post theRunnable
that will update thisSeekBar
When marking a
SeekBar
inactive, simply remove callbacks for theRunnable
and call it a dayInside your
Adapter
, the only state that you need to maintain is keeping track of the currently playing song. You can do this either by tracking the position as seen by yourListView
, or by holding on to theID
of the currently playing track (this, of course, will only work if your model uses IDs).Something like this:
Requested code:
Custom Seekbar class implementation:
In your adapter's
getView(...)
:Needless to say that you will need to use this custom implementation in your xml layout: