Search code examples
androidrunnableandroid-handlerpostdelayed

View recycling in listview causes postDelayed() issue


I have a list of user tasks/to-dos which have deadlines associated with it. Every minute on the minute I update the TextView that shows the time remaining for these task.

The problem I have is that some times when the textview gets updated the wrong time remaining gets displayed, especially if I scroll up and down then another task's time remaining will appear in its place.

I know this has something to do with the views in the list being recycled and that irreverent (off screen) views are still getting their logic processed. I narrowed down the bug to my Runnable class where some times the taskTime is associated with the wrong view.

I'm not sure how I can maintain the correct taskTime with the correct view, any suggestions?

p.s How do I clean up all the postDelayed() for views that are no longer visible?

MyCursorTreeAdapter.class

@Override
protected void bindChildView(View view, Context context, Cursor cursor, boolean isLastChild) {
    ViewHolder viewHolder = (ViewHolder) view.getTag();
    long taskTime = cursor.getLong(cursor.getColumnIndexOrThrow(TableTask.COLUMN_TIME));
    viewHolder.timeLeftTextView.setText(TimeUtils.getFormatedTimeLeft(taskTime));

    Runnable updateTimeRemainingRunnable = new UpdateTimeRemainingRunnable(view, taskTime);
    long postDelay = TimeUtils.millisecondsUntilNextMinute();
    view.postDelayed(updateTimeRemainingRunnable, postDelay);
}

private static class UpdateTimeRemainingRunnable implements Runnable {
    private final WeakReference<View> weakViewRef;
    private final long taskTime;

    protected UpdateTimeRemainingRunnable(View view, long taskTime) {
        weakViewRef = new WeakReference<>(view);
        this.taskTime = taskTime;
    }

    @Override
    public void run() {
        final View view = weakViewRef.get();
        if (view == null) return;

        ViewHolder viewHolder = (ViewHolder) view.getTag();
        view.getTag();

        // Task time is wrong here -- I sometimes get another task's task time

        String formatedTime = TimeUtils.getFormatedTimeLeft(taskTime);
        viewHolder.timeLeftTextView.setText(formatedTime);

        long postDelay = TimeUtils.millisecondsUntilNextSecond();
        view.postDelayed(this, postDelay);
    }
}

Solution

  • a direct way,

    use view.setTag(resouce_id,runnable) to bind the runnable and view together;

    when getChildView,first check view.getTag(resouce_id),if not null,use view.removeCallbacks() to clear it ,and rebine a new Runnable.

    the listview use recycled item when scrolling ... .

    but this is not an optimal solution.