滚动时 RecyclerView 中的多个倒数计时器闪烁
Multiple count down timers in RecyclerView flickering when scrolled
我已经为片段 activity 中的每个 RecyclerView 项目实现了倒数计时器。倒数计时器显示到期的剩余时间。倒数计时器工作正常,但向上滚动时开始闪烁。搜索了很多但没有得到很好的参考。谁能帮帮我?
这是我的 RecyclerView 适配器
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);
}
}
这是我在适配器中使用的 xml 文件
<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>
这是我的 RecyclerView 的屏幕截图
这个问题很简单
RecyclerView
复用holders,每次调用bind更新其中的数据
由于每次绑定任何数据时都会创建一个 CountDownTimer
,因此最终会有多个计时器更新同一个 ViewHolder
。
这里最好的办法是将CountDownTimer
移到FeedViewHolder
中作为参考,在绑定数据(如果开始)之前取消它并重新安排到所需的持续时间。
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) {
...
}
}
这样您将在启动另一个计时器之前取消该 ViewHolder
的任何当前计时器实例。
正如 Andrei Lupsa 所说,您应该在 ViewHolder 中保留 CountDownTimer 引用,如果您不想在滚动时重置计时器 (onBindViewHolder),您应该在 onBindViewHolder 中检查 CountDownTimer 引用是否为 null :
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) {
....
}
}
我能想到两种解决方案,它们都没有使用 CountDownTimer
class
- 使用
postDelayed
方法创建处理程序并在其中调用 notifyDataSetChanged()
。在您的适配器中计算时序。像下面的代码。
在适配器的构造函数中 class
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
handler.postDelayed(this, 1000);
}
}, 1000);
还有你的onBindViewHolder
方法
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!!");
}
}
如果您认为 notifyDataSetChanged()
每毫秒都是错误的,那么这是第二种选择。使用 Runnable
实现创建 class 并使用 setTag()
和 getTag()
方法在适配器中使用
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();}
}
}
并像这样在 onBindViewHolder
中使用
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);
}
我已经为片段 activity 中的每个 RecyclerView 项目实现了倒数计时器。倒数计时器显示到期的剩余时间。倒数计时器工作正常,但向上滚动时开始闪烁。搜索了很多但没有得到很好的参考。谁能帮帮我?
这是我的 RecyclerView 适配器
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);
}
}
这是我在适配器中使用的 xml 文件
<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>
这是我的 RecyclerView 的屏幕截图
这个问题很简单
RecyclerView
复用holders,每次调用bind更新其中的数据
由于每次绑定任何数据时都会创建一个 CountDownTimer
,因此最终会有多个计时器更新同一个 ViewHolder
。
这里最好的办法是将CountDownTimer
移到FeedViewHolder
中作为参考,在绑定数据(如果开始)之前取消它并重新安排到所需的持续时间。
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) { ... } }
这样您将在启动另一个计时器之前取消该 ViewHolder
的任何当前计时器实例。
正如 Andrei Lupsa 所说,您应该在 ViewHolder 中保留 CountDownTimer 引用,如果您不想在滚动时重置计时器 (onBindViewHolder),您应该在 onBindViewHolder 中检查 CountDownTimer 引用是否为 null :
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) {
....
}
}
我能想到两种解决方案,它们都没有使用 CountDownTimer
class
- 使用
postDelayed
方法创建处理程序并在其中调用notifyDataSetChanged()
。在您的适配器中计算时序。像下面的代码。
在适配器的构造函数中 class
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
handler.postDelayed(this, 1000);
}
}, 1000);
还有你的onBindViewHolder
方法
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!!");
}
}
如果您认为
notifyDataSetChanged()
每毫秒都是错误的,那么这是第二种选择。使用Runnable
实现创建 class 并使用setTag()
和getTag()
方法在适配器中使用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();} } }
并像这样在 onBindViewHolder
中使用
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);
}