Android-RecyclerView列表倒计时错乱问题

前言

转眼间距离上次写博客已是过了一个年轮,期间发生了不少事;经历了离职、找工作,新公司的第一版项目上线。现在总算是有时间可以将遇到的问题梳理下了,后期有时间也会分享更多的东西~~

场景

今天分享的问题是当在列表里面显示倒计时,这时候滑动列表会出现时间显示不正常的问题。首先关于倒计时我们需要注意的问题有以下几方面:

  1. 在RecyclerView中ViewHolder的复用导致的时间乱跳的问题。
  2. 滑动列表时倒计时会重置的问题。
  3. 在退出页面后定时器的资源释放问题,这里我使用的是用系统自带的CountDownTimer

ps:这里我们讨论的是对倒计时要求不是很严格的场景,对于用户手动修改系统时间这种操作没法预计;对于淘宝秒杀这种业务场景建议是实时不断请求后台拿取正确时间,对应的接口尽量设计简单,响应数据更快。

接下来通过代码具体了解:

代码

// 适配器
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    //服务器返回数据
    private List<TimeBean> mDatas;
    //退出activity时关闭所有定时器,避免造成资源泄漏。
    private SparseArray<CountDownTimer> countDownMap;

    //记录每次刷新时的时间
    private long tempTime;

    public MyAdapter(Context context, List<TimeBean> datas) {
        mDatas = datas;
        countDownMap = new SparseArray<>();
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_common, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(final ViewHolder holder, int position) {
        final TimeBean data = mDatas.get(position);
        //记录时间点
        long timeStamp = System.currentTimeMillis() - tempTime;

        long time = data.getLeftTime() - timeStamp;

        //将前一个缓存清除
        if (holder.countDownTimer != null) {
            holder.countDownTimer.cancel();
        }
        if (time > 0) { //判断倒计时是否结束
            holder.countDownTimer = new CountDownTimer(time, 1000) {
                public void onTick(long millisUntilFinished) {
                    holder.timeTv.setText(getMinuteSecond(millisUntilFinished));
                }
                public void onFinish() {
                    //倒计时结束
                    holder.timeTv.setText("00:00");
                }
            }.start();

            countDownMap.put(holder.timeTv.hashCode(), holder.countDownTimer);
        } else {
            holder.timeTv.setText("00:00");
        }

    }

    @Override
    public int getItemCount() {
        if (mDatas != null && !mDatas.isEmpty()) {
            return mDatas.size();
        }
        return 0;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        public TextView timeTv;
        public CountDownTimer countDownTimer;

        public ViewHolder(View itemView) {
            super(itemView);
            timeTv = (TextView) itemView.findViewById(R.id.tv_time);
        }
    }


    public void setGetTime(long tempTime) {
        this.tempTime = tempTime;
    }

    /**
     * 将毫秒数换算成 00:00 形式
     */
    public static String getMinuteSecond(long time) {
        int ss = 1000;
        int mi = ss * 60;
        int hh = mi * 60;
        int dd = hh * 24;

        long day = time / dd;
        long hour = (time - day * dd) / hh;
        long minute = (time - day * dd - hour * hh) / mi;
        long second = (time - day * dd - hour * hh - minute * mi) / ss;

        String strMinute = minute < 10 ? "0" + minute : "" + minute;
        String strSecond = second < 10 ? "0" + second : "" + second;
        return strMinute + ":" + strSecond;
    }

    /**
     * 清空资源
     */
    public void cancelAllTimers() {
        if (countDownMap == null) {
            return;
        }
        for (int i = 0,length = countDownMap.size(); i < length; i++) {
            CountDownTimer cdt = countDownMap.get(countDownMap.keyAt(i));
            if (cdt != null) {
                cdt.cancel();
            }
        }
    }
}

以上算是整个问题的核心代码了;其中SparseArray<CountDownTimer> 用来保存列表里面的定时器,用于退出页面时回收定时器。SparseArray是安卓特有的数据结构,建议多使用;data.getLeftTime() 是服务器返回的需要倒计时的时间,毫秒为单位。

问题一:ViewHolder的复用导致的数据错乱
if (holder.countDownTimer != null) {
       holder.countDownTimer.cancel();
   }

每次设置倒计时之前重置下倒计时即可解决。

问题二:滑动列表时倒计时会重置的问题

这个问题是由于解决问题一而导致的,因为列表滑动时离开屏幕的会被复用,这个时候我们会重新设置定时器,之前我是在倒计时里面记录倒计时剩余的时间然后重新设值,但是还是会有问题;这里借用了系统时间来解决,也就是tempTime 这个值。

首先在服务器请求成功后回调里面设置这个值,如:

    private MyAdapter adapter;

    @Override
    public void onHttpRequestSuccess(String url, HttpContext httpContext)      {
        if (服务器返回数据) {
            adapter.setGetTime(System.currentTimeMillis());
    }

相当于每次做刷新操作时获取的都是系统当时的时间戳。

然后在adapter里面计算

long timeStamp = System.currentTimeMillis() - tempTime;
long time = data.getLeftTime() - timeStamp;

其中tempTime就是我们保存的系统当前时间戳,然后每次滑动列表时都会调用onBindViewHolder,所以timeStamp就是记录的距离上次刷新经过了多少秒,然后用服务器返回的需要倒计时的时间减去经过的秒数就是还剩下的倒计时秒数。最后给定时器设置上就好了。

问题三:资源的释放

在当前的activity中调用以下方法。

@Override
protected void onDestroy() {
    super.onDestroy();
    if (adapter != null) {
        adapter.cancelAllTimers();
    }
}

好了,今天的分享就到这了,因为代码比较简单,布局都是一个Textview,所以没有贴出来,需要代码的可以留言~~

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
要在RecyclerView中实现计时,可以通过在RecyclerView的Adapter中使用Handler和Runnable来实现。以下是一个简单的示例: 1. 在ViewHolder中添加一个TextView用于显示计时的时间,例如: ```java public class MyViewHolder extends RecyclerView.ViewHolder { TextView countdownTextView; Handler handler; Runnable runnable; public MyViewHolder(View itemView) { super(itemView); countdownTextView = itemView.findViewById(R.id.countdown_textview); handler = new Handler(); runnable = new Runnable() { @Override public void run() { // 更新计时的时间 countdownTextView.setText(getTimeLeft()); handler.postDelayed(this, 1000); // 每隔1秒更新一次 } }; } // 计算距离结束时间还剩下的时间 private String getTimeLeft() { long timeLeft = endTime - System.currentTimeMillis(); if (timeLeft <= 0) { return "计时已结束"; } else { long seconds = timeLeft / 1000; long minutes = seconds / 60; long hours = minutes / 60; long days = hours / 24; return String.format(Locale.getDefault(), "%02d:%02d:%02d:%02d", days, hours % 24, minutes % 60, seconds % 60); } } // 开始计时 public void startCountdown(long endTime) { this.endTime = endTime; handler.post(runnable); } // 停止计时 public void stopCountdown() { handler.removeCallbacks(runnable); } } ``` 2. 在Adapter中重写onBindViewHolder方法,在其中调用ViewHolder的startCountdown方法开始计时,例如: ```java @Override public void onBindViewHolder(MyViewHolder holder, int position) { // 获取当前item的结束时间 long endTime = data.get(position).getEndTime(); holder.startCountdown(endTime); } ``` 3. 在Activity或Fragment中,在RecyclerView被销毁的时候,调用ViewHolder的stopCountdown方法停止计时,例如: ```java @Override protected void onDestroy() { super.onDestroy(); for (int i = 0; i < recyclerView.getChildCount(); i++) { MyViewHolder holder = (MyViewHolder) recyclerView.getChildViewHolder(recyclerView.getChildAt(i)); holder.stopCountdown(); } } ``` 这样就能在RecyclerView中实现计时了。当然,还可以根据不同的需求进行更加灵活的实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值