Multiple count down timers in RecyclerView flickering when scrolled
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
- Make Handler with
postDelayed
method and callnotifyDataSetChanged()
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!!");
}
}
-
if you think
notifyDataSetChanged()
every millisecond is wrong then here is second option. Create class withRunnable
implementation and use in in adapter withsetTag()
andgetTag()
methodpublic 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);
}
Related videos on Youtube
Hasan shaikh
Updated on September 14, 2022Comments
-
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>