Multiple count down timers in RecyclerView flickering when scrolled

13,607

Solution 1

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) {

          .... 
  }
}

Solution 2

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

  1. Make Handler 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);
}
Share:
13,607

Related videos on Youtube

Hasan shaikh
Author by

Hasan shaikh

Updated on September 14, 2022

Comments

  • Hasan shaikh
    Hasan shaikh over 1 year

    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 timer is working fine but when scrolled up it starts flickering. Searched a lot but did not got the good reference. Can any one help me?

    This is my RecyclerView adapter

    public class MyOfferAdapter extends RecyclerView.Adapter<MyOfferAdapter.FeedViewHolder>{
    private final Context mContext;
    private final LayoutInflater mLayoutInflater;
    private ArrayList<Transactions> mItems = new ArrayList<>();
    private ImageLoader mImageLoader;
    private String imageURL;
    private View mView;
    private String mUserEmail;
    
    public MyOfferAdapter(Context context) {
        mContext = context;
        mLayoutInflater = LayoutInflater.from(context);
        VolleySingleton mVolley = VolleySingleton.getInstance(mContext);
        mImageLoader = mVolley.getImageLoader();
    }
    
    
    public void addItems(ArrayList<Transactions> items,String userEmail) {
        int count = mItems.size();
        mItems.addAll(items);
        mUserEmail = userEmail;
        notifyItemRangeChanged(count, items.size());
    }
    
    
    @Override
    public FeedViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        mView = mLayoutInflater.inflate(R.layout.my_feed_item_layout, parent, false);
        return new FeedViewHolder(mView);
    }
    
    @Override
    public void onBindViewHolder(final FeedViewHolder holder, final int position) {
        holder.desc.setText(mItems.get(position).getDescription());//replace by title
        holder.scratchDes.setText(mItems.get(position).getScratchDescription());
    
        long timer = mItems.get(position).getTimerExpiryTimeStamp();
        Date today = new Date();
        final long currentTime = today.getTime();
        long expiryTime = timer - currentTime;
    
    
        new CountDownTimer(expiryTime, 500) {
            public void onTick(long millisUntilFinished) {
                long seconds = millisUntilFinished / 1000;
                long minutes = seconds / 60;
                long hours = minutes / 60;
                long days = hours / 24;
                String time = days+" "+"days" +" :" +hours % 24 + ":" + minutes % 60 + ":" + seconds % 60;
                holder.timerValueTimeStamp.setText(time);
            }
    
            public void onFinish() {
                holder.timerValueTimeStamp.setText("Time up!");
            }
        }.start();
    
    }
    
    @Override
    public int getItemCount() {
        return mItems.size();
    }
    
    public static class FeedViewHolder extends RecyclerView.ViewHolder {
        TextView desc;
        TextView scratchDes;
        TextView timerValueTimeStamp;
        ImageView feedImage;
        CardView mCv;
    
        public FeedViewHolder(View itemView) {
            super(itemView);
            mCv = (CardView) itemView.findViewById(R.id.cv_fil);
            desc = (TextView) itemView.findViewById(R.id.desc_tv_fil);
            feedImage = (ImageView) itemView.findViewById(R.id.feed_iv_fil);
            scratchDes = (TextView) itemView.findViewById(R.id.tv_scratch_description);
            timerValueTimeStamp = (TextView) itemView.findViewById(R.id.tv_timer_value_time_stamp);
    
        }
    
    }
    

    And this is my xml file used in adapter

    <LinearLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    
    <android.support.v7.widget.CardView     xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/cv_fil"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="@dimen/card_margin"
        android:layout_gravity="center"
        app:cardUseCompatPadding="true"
        app:cardElevation="4dp"
        android:elevation="6dp">
    
        <RelativeLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <ImageView
                android:id="@+id/feed_iv_fil"
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:layout_alignParentTop="true"
                android:scaleType="fitXY"
                android:tint="@color/grey_tint_color" />
    
            <TextView
                android:id="@+id/tv_scratch_description"
                style="@style/ListItemText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:text="casul shoes"
                android:fontFamily="sans-serif-light"
                android:padding="10dp" />
    
    
            <TextView
                android:id="@+id/tv_timer_value_time_stamp"
                style="@style/CardTitle"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerInParent="true"
                />
    
            <TextView
                android:id="@+id/desc_tv_fil"
                style="@style/VendorNameText"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_below="@id/feed_iv_fil"
                android:textColor="#3f3e3f"
                android:padding="10dp"
                />
    
        </RelativeLayout>
    
    
    </android.support.v7.widget.CardView>
    

    And this is screen shot of my RecyclerView enter image description here