RecyclerView中倒计时item的优雅方案

本文探讨在RecyclerView中实现倒计时item的解决方案,采用观察者模式,通过Handler每秒更新。利用WeakHashMap避免内存泄露,同时提供两种策略应对更新频率,确保CPU效率。详细介绍了代码实现,包括Adapter和ViewHolder的交互,以及如何在不同场景下选择合适方案。

本文介绍在RecyclerView中使用倒计时楼层,并且每秒刷新显示倒计时。
没有纠结于样式,主要介绍代码结构和设计模式。

先看一下效果:

在这里插入图片描述

我们采取的是观察者模式的方法,启动一个handler,每隔一秒去刷新所有注册过的item楼层。观察者模式的大概关系如下图:
在这里插入图片描述
我们并没有使用JAVA中的Observable,因为在释放Holder的时机比较难处理存在内存泄露的风险,所以我们采用WeakHashMap去保存所有Holder,但是在GC不频繁的情况下,弱引用也可以访问到无效的Holder对象,如果RecyclerView频繁更新,GC又不频繁进行,将会有通知到很多无效的Holder。
为此我们采用了均衡的两套方案,
1.在RecyclerView更新不频繁的场景下我们只在Adapter的OnCreate中注册Holder,并且除非页面销毁否则不去管理WeakHashMap中的内容,一切交给GC处理。
2在RecyclerView更新频繁的时候,我们会在onbind方法里判断是否注册了Holder,没有则加入,并且在notifyDataChange的时候解除所有Holder的注册,这样就算Holder没有被GC回收,也收不到我们的更新指令了。
选用哪种方案将在Observable初始化的时候得到确认:

 /*
    * @param beatTime 更新频率
    * @param changeable 易变得,如果为true
    * 会在bind方法里访问Map,不存在则加入Map
    * 如果存在频繁的下拉刷新,分页加载建议设为true
    * 
    * 如果列表数据不频繁更新请设置为false,
    * 只会在onCreate时候注册观察者。
    * Holder会在GC发生后自动停止更新。
    *
    *
    * */
    private final int BEAT_TIME;
    private final boolean CHANGEABLE;
    ...
    public Center(int beatTime, boolean changeable) {
        BEAT_TIME = beatTime;
        CHANGEABLE = changeable;
    }

注册方法:

 private WeakHashMap<Observer, Object> weakHashMap = new WeakHashMap();
 ...
 @Override
    public void addObserver(Observer observer) {
        weakHashMap.put(observer, null);
    }

通过bindRecyclerView获取到当前展示的item位置范围:

public void bindRecyclerView(RecyclerView recyclerView){
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);
            }

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                int first=-1;
                int last=-1;

                RecyclerView.LayoutManager layoutManager=recyclerView.getLayoutManager();
                if (layoutManager instanceof GridLayoutManager){
                     first=((GridLayoutManager)layoutManager).findFirstVisibleItemPosition();
                     last=((GridLayoutManager)layoutManager).findLastVisibleItemPosition();
                }
                if (layoutManager instanceof LinearLayoutManager){
                     first=((LinearLayoutManager)layoutManager).findFirstVisibleItemPosition();
                     last=((LinearLayoutManager)layoutManager).findLastVisibleItemPosition();
                }
                postionFL.frist=first;
                postionFL.last=last;

                Log.e("lmtlmt2","frist:"+first+"last:"+last);
            }
        });
    }

通知所有holder更新数据并将位置访问传递给所有观察者,观察者拿到显示范围,并和自己做比较,决定是否更新UI:

 private void notifyObservers() {

        Iterator iter = weakHashMap.entrySet().iterator();
        while (iter.hasNext()) {
            Map.Entry entry = (Map.Entry) iter.next();
            Observer observer = (Observer) entry.getKey();
            if (observer != null) {
                if (postionFL.frist>-1&&postionFL.last>-1)
                observer.update(null, postionFL);
                else observer.update(null, null);
            }


        }
        Log.e("lmtlmt", "weakHashMap size" + weakHashMap.size());

    }

停止和开始倒计时:

 @Override
    public void startCountdown() {
        if (!isStart) {
            handler.sendEmptyMessage(0);
            isStart = true;
        }

    }

    @Override
    public void stopCountdown() {
        isStart = false;
        handler.removeCallbacksAndMessages(null);

    }

通知更新和判断是否加入Map,仅在易变模式下有效:

 @Override
    public boolean containHolder(Observer observer) {
        //如果不是易变的 直接屏蔽次方法。
        if (!CHANGEABLE) return true;
        if (weakHashMap.containsKey(observer)) return true;
        return false;
    }

    @Override
    public void notifyAdapter() {
        if (!CHANGEABLE) return;
        deleteObservers();
    }

倒计时model代码,这里注意我们使用的是elapsedRealtime(可以理解为开机后cpu的增长时钟),不会因为用户调整系统时间而导致倒计时不准确。

public class TimeBean {
    private long elapsedRealtime;

    TimeBean(long remainTime) {
        elapsedRealtime = remainTime + SystemClock.elapsedRealtime()/1000;
    }


    public long getRainTime() {
        return elapsedRealtime - SystemClock.elapsedRealtime()/1000;
    }
}

下面是Adapter的代码:
ViewHolder实现Observer接口等待更新信号,只更新在范围内的ViewHolder

static class ViewHolder extends RecyclerView.ViewHolder implements Observer {
        int lastBindPositon = -1;
        TextView textView;
        TimeBean timeBean;

        public ViewHolder(View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.tv1);

        }

        @Override
        public void update(Observable o, Object arg) {
          if (arg!=null&&arg instanceof Center.PostionFL){
                Center.PostionFL postionFL=(Center.PostionFL)arg;
                if (lastBindPositon>=postionFL.frist&&lastBindPositon<=postionFL.last){
                    Log.e("lmtlmtupdate", "update" + lastBindPositon);
                    bindCountView(textView, timeBean);
                }
            }

        }

两个关键方法:
注意在非易变模式下,bind中的containHolder方法恒为true。

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

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.lastBindPositon = position;
        holder.timeBean = list.get(position);
        bindCountView(holder.textView, holder.timeBean);
        if (!countDownCenter.containHolder(holder)){
            countDownCenter.addObserver(holder);
        }

    }

测试代码片段:

 for (int i = 0; i < 100; i++) {
            list.add(new TimeBean(i*10));
        }
        countDownCenter=new Center(1000,false);
        listAdapter=new ListAdapter(list,countDownCenter);
        recyclerView.setAdapter(listAdapter);
        final FrameLayout frameLayout=findViewById(R.id.f1);
        frameLayout.addView(recyclerView);

方案总结:
1.倒计时信号恒定时间准确。
2.使用弱引用即便操作不当也不会产生内存泄露。
3.提供两种权衡方法,根据项目自身情况最大程度避免CPU无效开销。
4.可根据Activity或Fragment生命周期进行暂停和重开。
5.入侵性小,介入简便。

代码地址:
https://github.com/AndroidMsky/RecycleViewFloorCountDown

欢迎关注我的博客和GitHub

内容概要:本文系统研究了基于粒子群算法(PSO)的微网优化调度方法,并重点引入需求响应机制以提升微电网运行的经济性与可靠性。研究构建了一个综合考虑风能、光伏、储能、柴油发电机、燃气轮机等多种分布式能源的微电网模型,建立了以最小化系统综合运行成本为目标,涵盖燃料成本、环境污染惩罚、购售电费用及需求响应激励成本的多目标优化模型。文中详细阐述了粒子群算法的数学原理及其在求解此类非线性、多维度、带约束的复杂优化问题中的独特优势,通过Matlab编程实现了完整的算法流程与仿真分析,验证了所提方法在有效降低系统运行成本、平抑负荷波动、提高可再生能源消纳率以及增强微网应对需求侧变化灵活性方面的显著效果。; 适合人群:具备一定电力系统、优化理论基础和Matlab编程能力的高校研究生、从事新能源、智能电网及相关领域研究的科研人员,以及致力于微电网规划与运行优化的工程技术人员。; 使用场景及目标:①用于教学与科研,深入理解微网优化调度的核心挑战、数学建模方法及智能优化算法的应用;②为实际微电网项目的规划设计、运行策略制定提供可靠的算法工具和技术参考;③作为智能优化算法(如PSO)在综合能源系统中成功应用的典型案例,推动先进算法在能源互联网领域的研究与实践。; 阅读建议:建议读者在学习过程中,务必结合文中的Matlab代码进行上机实践,重点关注目标函数的设计逻辑、各类物理约束(如功率平衡、设备出力、储能容量等)的数学表达,以及PSO算法中关键参数(如种群规模、惯性权重、学习因子)的设置与调优技巧,可通过改变参数或扩展模型(如加入更多能源形式或转化为多目标优化)来加深理解和掌握。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值