You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RecyclerView中使用CountDownTimer遇滚动数据重复及显示异常问题

解决RecyclerView中CountDownTimer滚动数据重复与状态错误问题

你遇到的是RecyclerView复用机制下常见的Timer管理问题——明明Log里大部分项的calculateEnd为负数(本该显示“Finished”),但界面却错误显示计时器,滚动时还出现数据混乱。咱们一步步拆解解决:

问题根源分析

  1. ViewHolder复用未清理旧Timer:列表项滚出屏幕后,ViewHolder会被复用给新项,但你之前只在onViewDetachedFromWindow取消Timer,却没在绑定新数据前先清除ViewHolder中残留的旧Timer,导致旧Timer继续更新UI,和新数据的Timer冲突。
  2. 未严格判断计时状态:refreshTime方法没有处理calculateEnd <= 0的情况,无论数据是否已结束都启动Timer,导致负数时间也显示计时。
  3. 重复触发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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 09:45:29