RecyclerView中使用CountDownTimer遇滚动数据重复及显示异常问题
解决RecyclerView中CountDownTimer滚动数据重复与状态错误问题
你遇到的是RecyclerView复用机制下常见的Timer管理问题——明明Log里大部分项的calculateEnd为负数(本该显示“Finished”),但界面却错误显示计时器,滚动时还出现数据混乱。咱们一步步拆解解决:
问题根源分析
- ViewHolder复用未清理旧Timer:列表项滚出屏幕后,ViewHolder会被复用给新项,但你之前只在
onViewDetachedFromWindow取消Timer,却没在绑定新数据前先清除ViewHolder中残留的旧Timer,导致旧Timer继续更新UI,和新数据的Timer冲突。 - 未严格判断计时状态:
refreshTime方法没有处理calculateEnd <= 0的情况,无论数据是否已结束都启动Timer,导致负数时间也显示计时。 - 重复触发Timer启动:同时在
onBindViewHolder和onViewAttachedToWindow调用refreshTime,同一个ViewHolder被多次绑定Timer,加剧状态混乱。
具体解决方案
1. 优化Adapter与ViewHolder的Timer管理
修改代码,确保每个ViewHolder的Timer和对应数据绑定,复用前清理旧状态:
public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> { private List<TodayGmodel> model; private Context context; public Adapter(Context context, List<TodayGmodel> model) { this.model = model; this.context = context; } @Override public Adapter.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.timer_adapter_layout, viewGroup, false); return new ViewHolder(view); } @Override public void onBindViewHolder(final Adapter.ViewHolder viewHolder, int position) { TodayGmodel todayGmodel = model.get(position); long remainingTime = todayGmodel.getCalculateEnd(); // 绑定新数据前,先取消当前ViewHolder的旧Timer,避免复用干扰 if (viewHolder.downTimer != null) { viewHolder.downTimer.cancel(); viewHolder.downTimer = null; } // 根据剩余时间判断UI状态 if (remainingTime <= 0) { // 时间已结束,直接显示Finished viewHolder.timerTextView.setText("Finished"); // 如有其他计时控件(如ProgressBar),同步隐藏/重置 // viewHolder.timerProgressBar.setVisibility(View.GONE); } else { // 时间未结束,启动新Timer viewHolder.refreshTime(remainingTime); } Log.e("timerLogList", "Position: " + position + ", RemainingTime: " + remainingTime); } // 移除onViewAttachedToWindow的重复逻辑,避免重复启动Timer // @Override // public void onViewAttachedToWindow(@NonNull ViewHolder holder) { // int pos = holder.getAdapterPosition(); // TodayGmodel todayGmodel = model.get(pos); // holder.refreshTime(todayGmodel.getCalculateEnd()); // } @Override public void onViewDetachedFromWindow(@NonNull ViewHolder holder) { if (holder.downTimer != null) { holder.downTimer.cancel(); holder.downTimer = null; } } @Override public int getItemCount() { return model.size(); } public class ViewHolder extends RecyclerView.ViewHolder { private TextView timerTextView; public CountDownTimer downTimer; public ViewHolder(View itemView) { super(itemView); timerTextView = itemView.findViewById(R.id.timer_text); } public void refreshTime(long remainingTime) { // 双重保险:确保旧Timer已取消 if (downTimer != null) { downTimer.cancel(); } // 注意单位:如果calculateEnd是秒,要转成毫秒 downTimer = new CountDownTimer(remainingTime * 1000, 1000) { @Override public void onTick(long millisUntilFinished) { // 格式化剩余时间为hh:mm:ss long seconds = millisUntilFinished / 1000; long minutes = seconds / 60; long hours = minutes / 60; seconds = seconds % 60; minutes = minutes % 60; String timeStr = String.format("%02d:%02d:%02d", hours, minutes, seconds); timerTextView.setText(timeStr); } @Override public void onFinish() { timerTextView.setText("Finished"); // 计时结束后更新数据源,避免滚动回来重新计时 int pos = getAdapterPosition(); if (pos != RecyclerView.NO_POSITION && model.get(pos).getCalculateEnd() > 0) { model.get(pos).setCalculateEnd(0); } } }.start(); } } }
2. 关键修改点说明
- 绑定前清理旧Timer:在
onBindViewHolder中先取消旧Timer,确保复用后的ViewHolder状态干净。 - 严格判断时间状态:只有
remainingTime > 0时才启动Timer,否则直接显示“Finished”,解决负数时间显示计时器的问题。 - 移除重复启动逻辑:删除
onViewAttachedToWindow中的refreshTime调用,避免重复绑定Timer。 - 计时结束更新数据源:在
onFinish中把对应数据的calculateEnd设为0,确保后续复用该数据时显示正确状态。 - 单位校验:确认
calculateEnd的单位是秒还是毫秒,这里默认是秒,所以乘以1000转成CountDownTimer需要的毫秒。
3. 额外注意事项
- 确保
TodayGmodel类有setCalculateEnd方法,用于更新计时结束后的状态。 - 如果布局中有其他计时相关控件(如进度条),记得在时间结束时同步隐藏或重置。
- 建议在Activity的
onDestroy方法中清空Adapter数据源并调用adapter.notifyDataSetChanged(),避免内存泄漏。
验证效果
修改后滚动RecyclerView:
- 所有
calculateEnd <= 0的项会稳定显示“Finished”。 - 仅
calculateEnd > 0的项启动计时器,复用后不会出现数据重复或状态错误。 - Log中的剩余时间和界面显示完全对应。
内容的提问来源于stack exchange,提问作者Dr.Kong
相关产品推荐
相关产品推荐

