Multiple count down timers in RecyclerView flickering when scrolled

前端 未结 3 1409
孤街浪徒
孤街浪徒 2020-11-30 09:27

I have implemented count down timer for each item of RecyclerView which is in a fragment activity. The count down timer shows the time remaining for expiry. The count down t

相关标签:
3条回答
  • 2020-11-30 09:44

    This problem is simple.

    RecyclerView reuses the holders, calling bind each time to update the data in them.

    Since you create a CountDownTimer each time any data is bound, you will end up with multiple timers updating the same ViewHolder.

    The best thing here would be to move the CountDownTimer in the FeedViewHolder as a reference, cancel it before binding the data (if started) and rescheduling to the desired duration.

    public void onBindViewHolder(final FeedViewHolder holder, final int position) {
        ...
        if (holder.timer != null) {
            holder.timer.cancel();
        }
        holder.timer = new CountDownTimer(expiryTime, 500) {
            ...
        }.start();
    }
    
    public static class FeedViewHolder extends RecyclerView.ViewHolder {
        ...
        CountDownTimer timer;
    
        public FeedViewHolder(View itemView) {
            ...
        }
    }
    

    This way you will cancel any current timer instance for that ViewHolder prior to starting another timer.

    0 讨论(0)
  • 2020-11-30 09:46

    There are two types of solution I can think of and they are without using CountDownTimer class

    1. Make Handle with postDelayed method and call notifyDataSetChanged() in that. In your adapter make calculation for timing. like below code.

    in Constructor of adapter class

    final Handler handler = new Handler();
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            notifyDataSetChanged();
            handler.postDelayed(this, 1000);
        }
    }, 1000);
    

    and in you onBindViewHolder method

    public void onBindViewHolder(final FeedViewHolder holder, final int position) {
    
          updateTimeRemaining(endTime, holder.yourTextView);
    }
    
    private void updateTimeRemaining(long endTime, TextView yourTextView) {
    
        long timeDiff = endTime - System.currentTimeMillis();
        if (timeDiff > 0) {
            int seconds = (int) (timeDiff / 1000) % 60;
            int minutes = (int) ((timeDiff / (1000 * 60)) % 60);
            int hours = (int) ((timeDiff / (1000 * 60 * 60)) % 24);
    
            yourTextView.setText(MessageFormat.format("{0}:{1}:{2}", hours, minutes, seconds));
        } else {
            yourTextView.setText("Expired!!");
        }
    }
    
    1. if you think notifyDataSetChanged() every millisecond is wrong then here is second option. Create class with Runnable implementation and use in in adapter with setTag() and getTag() method

          public class DownTimer implements Runnable {
      
          private TextView yourTextView;
          private long endTime;
          private DateFormat formatter = new SimpleDateFormat("HH:mm:ss", Locale.getDefault());
          private Handler handler = new Handler();
      
          public DownTimer(long endTime, TextView textView) {
              this.endTime = endTime;
              yourTextView = textView;
              formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
          }
      
          public void setEndTime(long endTime) {
              this.endTime = endTime;
          }
      
          public void start() {
              if (handler != null)
                  handler.postDelayed(this, 0);
          }
      
          public void cancel() {
              if (handler != null)
                  handler.removeCallbacks(this);
          }
      
          @Override
          public void run() {
              if (handler == null)
                  return;
      
              if (yourTextView == null && endTime == 0)
                  return;
      
              long timeDiff = endTime - System.currentTimeMillis();
      
              try {
                  Date date = new Date(timeDiff);
                  yourTextView.setText(formatter.format(date));
      
              }catch (Exception e){e.printStackTrace();}
              }
      }
      

    and use in onBindViewHolder like this

    if (holder.yourTextView.getTag() != null) {
        DownTimer downTimer = (DownTimer) holder.yourTextView.getTag();
        downTimer.cancel();
        downTimer.setEndTime(endTime);
        downTimer.start();
    } else {
        DownTimer downTimer = new DownTimer(endTime, holder.yourTextView);
        downTimer.start();
        holder.yourTextView.setTag(downTimer);
    }
    
    0 讨论(0)
  • 2020-11-30 09:53

    As Andrei Lupsa said you should hold CountDownTimer reference in your ViewHolder, if you didn't want to timer reset when scrolling (onBindViewHolder) , you should check if CountDownTimer reference is null or not in onBindViewHolder :

    public void onBindViewHolder(final FeedViewHolder holder, final int position) {
    
          ...
    
          if (holder.timer == null) {
              holder.timer = new CountDownTimer(expiryTime, 500) {
    
              ...
    
              }.start();   
          }
    }
    
    
    public static class FeedViewHolder extends RecyclerView.ViewHolder {
    
           ...
    
           CountDownTimer timer;
    
           public FeedViewHolder(View itemView) {
    
              .... 
      }
    }
    
    0 讨论(0)
提交回复
热议问题