Android自定义倒计时控件

来源:互联网 发布:mac屏幕最大化快捷键 编辑:程序博客网 时间:2024/06/04 19:45
前言

如果我一个页面有多个Fragment,每个都是一个RecyclerView,然后每个item里面都有倒计时,只要有一个倒计时结束,就需要回调,刷新整个RecyclerView。要怎么处理好呢? 
我们简单分析一下:


分析


1.如果是我们把倒计时封装成一个view,那么由于RecyclerView的重用机制,必定控件会重用,如果一个控件一个线程,那么同时那么多Fragment,那么多item,会不会造成线程的不可控? 
2.由于重用机制,也就是说每次滚动RecyclerView都会去绑定倒计时数据,这个时候怎么处理呢?3.RecyclerView的重用机制,倒是列表后面的item其实并没有被创建,也就是说比如列表的第10个item的倒计时结束了,怎么通知回调呢? 

对吧,简单分析一下就发现了好多问题,主要问题就是用的地方太多了!,然后无图无真相,先上效果。 




解决思路


还是通过自定义View的形式去管理对应的倒计时


为了使用方便,我觉得还是通过自定义View的方式,让对应控件去管理对应功能为好,也方便使用,直接通过find的形式绑定数据就可以了。所以本次倒计时,我是还是喜欢通过自定义View。 
那么还有一个很重要的东西就是这个时间的概念,一个倒计时的时间应该包括小时、分钟、秒钟。有了这3个属性后,那么就是获得显示时间的文本的方法,所以我们先建一个基类,如下:

package com.fabio.countdowndemo;public abstract class BaseCountdownTime {    protected int hour;    protected int minute;    protected int second;    abstract String getTimeText();}

然后创建CountdownTime,去集成我们的时间基类。 
然后回忆一下这个问题,我们RecyclerView如果一页只能显示3个item,那么当第10个item的倒计时结束了,要怎么回调呢? 
然后我想每次在绑定数据的时候去绑定监听肯定是不现实的了,所以,我就动了一点歪脑筋。我在时间类上面添加了一个静态监听

/** * 这个真的不是我想这样做,没办法,item不展示的时候也要回调,那就索性简单粗暴点 * */ public static OnTimeCountdownOverListener onTimeCountdownOverListener;

然后到时候设置监听就这样玩:

CountdownTime.onTimeCountdownOverListener = new CountdownTime.OnTimeCountdownOverListener() {            @Override            public void onTimeCountdownOver() ->{                Log.d("Blin QueueMangerOver","回调了");            }        };

虽然有点奇怪啊,不过这是我能想到的确简单好用的办法了,也不是各种麻烦了。具体完整的CountdownTime时间类,到时候在后面贴出。


解决线程不可控的问题


我们先看看为什么要说这个问题,简单点,如果每个View里面都有一个单独的计时器管理自己的倒计时,的确可以简单的实现自己管理自己的倒计时。但是一旦重用,或者一旦数量急剧增加后,那么线程将会变得不可控。所以这个问题肯定是一个关键的地方。还有就是RecyclerView重用的时候,第十个不显示的item怎么来倒计时呢,这个时候item重用,计时器其实去管理另一个时间了。所以,最终我我决定用一个队列来管理。 
设计思路也是一样,先看需要哪些功能,设计基类,然后再去子类实现它。

public abstract class BaseCountdownTimeQueueManager {    protected ArrayList<CountdownTime> timeQueue;    protected Timer timeTimer;    protected TimerTask timeTask;    public void removeTime(CountdownTime time){        if(timeQueue != null){            for(int i = 0;i<timeQueue.size();i++){                if(TextUtils.equals(time.getId(),timeQueue.get(i).getId())){                    timeQueue.remove(i);                }            }        }    }    abstract void countdownTimeQueue();    abstract void initCountdownTimeQueueManager();    public abstract CountdownTime addTime(int time,String id,CountdownTime.OnCountdownTimeListener listener);}

看一下,上面我有一个timeQueue,有一个计时器,然后就是一些必要的,去实现的方法。removeTime()主要是删掉队列里面倒计时已经结束的对象,然后countdownTimeQueue()是每次刷新要做的事情。addTime()就是添加倒计时时间对象的方法了。 
然后看看管理类

public class CountdownTimeQueueManager extends BaseCountdownTimeQueueManager{    private static CountdownTimeQueueManager manager;    private CountdownTimeQueueManager(){}    public static CountdownTimeQueueManager getInstance(){        if (manager == null){            manager = new CountdownTimeQueueManager();            manager.initCountdownTimeQueueManager();        }        return manager;    }    @Override    void initCountdownTimeQueueManager() {        timeQueue = new ArrayList<>();        timeTimer = new Timer(true);        timeTask = new TimerTask() {            @Override            public void run() {                countdownTimeQueue();            }        };        timeTimer.schedule(timeTask,1000,1000);    }    @Override    public CountdownTime addTime(int time, String id, CountdownTime.OnCountdownTimeListener listener) {        CountdownTime countdownTime;        if(timeQueue.size() >0)            for(int i =0;i<timeQueue.size();i++){                countdownTime = timeQueue.get(i);                if(TextUtils.equals(countdownTime.getId(),id)){                    countdownTime.setListener(listener);                    return countdownTime;                }            }        countdownTime = new CountdownTime(time,id,listener);        timeQueue.add(countdownTime);        return countdownTime;    }    @Override    synchronized void countdownTimeQueue() {        if(timeQueue != null&&timeQueue.size()>0){            for(int i =0;i<timeQueue.size();i++){                if(timeQueue.get(i).countdown())                    i --;            }        }    }}


肯定要是一个单列的,对吧,然后呢,通过这个单列来管理所有的倒计时时间,并且只启动一个计时器来倒计时。


倒计时控件的实现


这个倒计时控件就是简单的显示一个时间的概念,所以我继承TextView


/**     * 多了一个id参数,实际应用中可以是订单id、流水id之类,可以保证唯一性即可     * */    public void setCountdownTime(int time,String id){        nowId = id;        if(time <= 0){            if(countdownTime != null)                countdownTime.setSeconds(0);        }else{            countdownTime = manager.addTime(time,id,this);        }        postInvalidate();    }

这个就是我们找到view之后唯一要做的事情了,其实都交给view自己去处理。我添加了一个id,主要是为了唯一定位到某个倒计时,因为RecyclerView的复用的确带来挺大的影响的,每次滑动都去调用setCountdownTime(),的确是一件很麻烦的事情。每次调用的时候,都会把自己传进去,其实是为了回调,通知绘制而已啦。结合我们上面的addTime();应该就能看懂了。

/**     * 当前控件绑定的倒计时实践对象id,由于重用,RecyclerView滚动的时候,     * 会复用view,导致里面显示的时间其实是不一样的     * */    private String nowId;

这边就是由于重用的问题,在RecyclerView里面可能一个View在不同的时刻需要显示很多个不同的倒计时。 
最终我们的CountdownView还需要去实现implements CountdownTime.OnCountdownTimeListener 
为的就是我们的管理类在倒计时的时候通知View去刷新UI。


最后就是把所有的东西整合,封装,调试。


然后怎么用呢?我们在,貌似就是非常简单了,找到View以后直接绑定数据就可以。不过有一个小点要注意,我们从服务端返回的报文里面item的倒计时是不一样的,这个时候滚动RecyclerView的时候,会重新绑定数据,但是要知道那个时间是上次我们请求服务端的时间,所以需要计算一下请求回来的时间和绑定时间的时间差,减去就可以。我是通过时间戳来实现的,还是4个字,简单粗暴!


啊哦,别忘了,干货在这里!https://github.com/Jasperben/CountdownDemo