RecyclerView每个Item展示倒计时

img1

需求

今天的需求是要在RecyclerView里的每个Item上展示一个倒计时的操作,主要是用来提醒用户当前订单还有多长时间就会被自动取消,从而来提醒用户尽快支付。

1.需求分析

这里有几个问题需要解决?

1.首先是计时器的问题,是每个item使用一个计时器还是共用一个講器?

这里我选择了共用一个计时器,这里主要有两点:1.多个计时器性能上会有问题,会带来很大的开销;2.多个计时器维护起来非常的麻烦。很容易错乱。

2.以什么方式来进行item时间的更新?

把计时器独立出来,写成一个公共的方法,只处理倒计时的工作,谁要使用,我并不关心,使用观察者模式来通知需要倒计时的地方。当然这里同时只会有一个地方需要更新,所以,这里就只是通过写接口的方式来通知。

3.由于item的复用,怎么去更新时间,而又不会重复显示?

这里为了避免item的错乱,显然要将holder和position进行绑定,只有对应上,才不会导致item的错乱。这里直接用notifyDataChanged()来刷新数据,也就是当时间每过去一秒就是刷新一次,来更新每个item上的数据

4.性能优化问题?

这里主要是说更新的问题,使用notifyDataChanged()来每次都更新数据,当在滑动的时候就会导致页面的卡顿,这是因为滑动的时候,数据同时也在刷新。解决方法:只有当页面处于idle时才会去更新时间。

2.定时器工具类的实现

定时器的实现:
1.这里我直接通过handle来写一个循环,然后,提供一个start()方法来实现一个定时器

public BackTimerUtils(Activity context, List<Long> data) {
    this.mTimeList = data;
    this.mContext = context;
}
private Handler handler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        start();
    }
}
public void start() {
    handler.postDelayed(new Runnable() {
        @Override
        public void run() {
            handler.sendEmptyMessage(0);
            handleListTime();
        }
    }, 1000);
}

这里的 List<Long> data()主要是需要接收一个每item剩余的时间(这里都是毫秒数),也就是每个item从哪个时间 点开始倒计时。

2.写一个接口,让需要倒计时的地方来监听此定时器接收更新

    public interface TimerListener {
    void timeData(List<Long> data);
}
public void setTimeListener(TimerListener listener)   {
    this.mTimerListener = listener;
}
private void handleListTime() {
    if (mTimeList != null) {
        for (int x = 0; x < mTimeList.size(); x++) {
            mTimeList.set(x, mTimeList.get(x) - 1000);
        }
        if (mTimerListener != null) {
            mTimerListener.timeData(mTimeList);
        }
    }
}

这里有handleListTime()方法主要是把集合里的数据都减去1秒后,再通过接口将此集合回调给需要更新的地方。

3.提供一个方法将毫秒数转换成分和秒

public void upDateTimeToView(final BaseAdapter.BaseViewHoder hoder, Long mTime) {
    if (mTime / (1000 * 60) >= 0) {
        currentMinute = (int) (mTime / (1000 * 60));
        if (String.valueOf(currentMinute).length() < 2)
            currentStringMinute = "0" + currentMinute;
        else
            currentStringMinute = currentMinute + "";
    } else
        currentStringMinute = "00";
    if ((mTime % (1000 * 60)) / 1000 >= 0) {
        currentSecond = (int) ((mTime % (1000 * 60)) / 1000);
        if (String.valueOf(currentSecond).length() < 2)
            currentStringSecond = "0" + currentSecond;
        else
            currentStringSecond = currentSecond + "";
    } else
        currentStringSecond = "00";
    mContext.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            hoder.order_item_pay_timer_minute.setText(currentStringMinute);
            hoder.order_item_pay_timer_second.setText(currentStringSecond);
        }
    });
}

这个方法使用返回值的方式,会比将view直接传过来更好。更加的通用。

到这里,计时器部分的代码就已经写完了,谁需要用到倒计时,直接将要倒计时的时间传过来就可以了。

3.RecyclerView的Item更新部分

1.注册定时器,获取更改后的数据进行更新

  @Override
public void timeData(List<Long> data) {        
        mTimeList = data;
        notifyDataSetChanged();        
}

通过mTimeList变量来记录数据,并通过notifyDataSetChanged()来刷新列表
2.在BindViewHolder中根据holder和position来获取每个item对应的数据,并更新

if (mTimeList != null && mTimeList.size() >= position - 1)
    mTimerUtils.upDateTimeToView(holder, mTimeList.get(position));    

这样子,倒计时就会正确的显示在每个相对应的item上了.

4.优化部分

完成上面的操作,主体功能已经大致完成了,倒计时也可以正常运转了。可是当滑动的时候,界面非常的卡顿,不应该啊,这里又没有非常复杂的ui展示,怎么会这么卡呢?原来是在滑动的过程中,我们去更新的ui。需要停止这样的操作行为 。
     我们通过对recyclerView滑动事件进行监听,通过一个标志位来设置相应的状态,以保证只有在recyclerView处于idle状态时,才去更新时间。

@Override
protected void handleScrollChanged(RecyclerView recyclerView, int newState) {
    switch (newState) {
        case RecyclerView.SCROLL_STATE_IDLE:
            isIdle = true;
            break;
        case RecyclerView.SCROLL_STATE_DRAGGING:
            isIdle = false;
            break;
        case RecyclerView.SCROLL_STATE_SETTLING:
            isIdle = false;
            break;
    }
}

@Override
public void timeData(List<Long> data) {
    if (isIdle) { //如果是在idle状态 才去刷新数据
        mTimeList = data;
        notifyDataSetChanged();
    }
}

这样处理后,会发现item在滑动过程中明显顺畅了。。

到这里整个倒计时的处理就已经完成了。