问题
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
回答1:
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.
回答2:
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) {
....
}
}
回答3:
There are two types of solution I can think of and they are without using CountDownTimer
class
- Make Handle 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);
}
来源:https://stackoverflow.com/questions/38890863/multiple-count-down-timers-in-recyclerview-flickering-when-scrolled