需求
今天的需求是要在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在滑动过程中明显顺畅了。。
到这里整个倒计时的处理就已经完成了。