RecyclerView中倒计时item的优雅方案

2023-05-16

本文介绍在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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

RecyclerView中倒计时item的优雅方案 的相关文章

随机推荐

  • C++ Primer (暂时完结)

    C 43 43 Primer C 43 43 预备知识泛型编程第二章 开始学习C 43 43 2 1 3 C 43 43 预处理器 和iostream 文件名称空间第二章 输出输入endl控制符换行符规范的书写格式c 43 43 源码风格
  • Futaba S-BUS controlled by mbed(使用mbed控制Futaba S-BUS)

    原文地址 xff1a Futaba S BUS controlled by mbed 代码地址 xff1a SBUS Library Introduction xff08 简介 xff09 The Futaba S BUS protocol
  • STorM32 BGC 相关

    下载 storm32 bgc v130电路图 xff08 http download csdn net detail hxiaohai 9901379 xff09 drv8313器件手册 xff08 http download csdn n
  • TMC5160步进电机驱动芯片开发手记

    2018年 xff0c Trinamic推出了新的型号TMC5160 xff0c 自带6点速度曲线 StealthChop和SpreadCycle静音防抖技术等很不错的功能 xff0c 目前使用官方BOB板进行开发测试 xff0c 在初步参
  • 导航过程各坐标系之间转换

    做导航最烦人的就是坐标系之间的转换 xff0c 每次都要去推相应的公式 xff0c 所以特地写点干货 xff0c 解决你坐标系转换问题 1 经纬高转地心 xff08 1 xff09 式1中 xff0c 为纬度 xff0c 为经度 xff0c
  • requestAuthentication详解

    欢迎关注我的公众号 xff1a 目前刚开始写一个月 xff0c 一共写了18篇原创文章 xff0c 文章目录如下 xff1a istio多集群探秘 xff0c 部署了50次多集群后我得出的结论 istio多集群链路追踪 xff0c 附实操视
  • 运行SLAM遇到的问题

    运行的代码来自与高翔的博客 1 绝对路径 将程序中的相对路径改成绝对路径 xff0c 否则找不到图片 2 pcl问题 在CMakeLists中要加上一行代码 list REMOVE ITEM PCL LIBRARIES 34 vtkproj
  • python tcping(ping命令)

    python tcping xff08 ping命令 使用tcping库中的ping方法使用如下 xff1a span class token keyword from span tcping span class token keywor
  • RecycledViewPool的使用和堆内存分析

    RecycledViewPool在 ViewPager 43 RecyclerView的场景下可以大放光彩 下面就来验证一下它的优点 xff1a 首先我们自定定义一个View放在ViewHolder中 xff1a public class
  • SX1261/2芯片开发那些事儿(一)时钟源选择

    相信大家第一次接触LoRa芯片时 阅读Semtech官方Demo代码或者自己进行项目开发时 xff0c 不知道对于待机模式 xff0c 是使用STDBY RC还是STDBY XOSC呢 xff1f 今天我们就来介绍射频芯片SX1261 2该
  • 解决Ubuntu20.04安装ROS过程镜像源问题

    解决Ubuntu20 04安装ROS过程镜像源问题 问题背景问题描述解决方案参考链接 问题背景 我是先安装了 Windows10 子系统 WSL wsl install 并安装Ubuntu20 04 wsl install d Ubuntu
  • 解决 qt.qpa.xcb: could not connect to display 问题

    2022 07 21更新 现在WSL2已经可以直接运行 Linux GUI 了 xff01 xff01 xff01 不再需要安装Xserver xff08 XLaunch xff09 之类的 xff01 xff01 xff01 参考微软的官
  • 解决在VScode中调试C++代码断点无效、断点错位的问题

    问题背景 最近在学习高翔博士的经典教程 视觉SLAM十四讲 xff08 第2版 xff09 xff0c 使用其配套的Github中C 43 43 代码进行学习 xff0c 在调试时发现断点无效 错位的问题 xff0c 查阅了一些资料 xff
  • Ubuntu系统安装在移动固态硬盘,实现在不同电脑即插即用

    Ubuntu系统安装在移动固态硬盘 xff0c 实现在不同电脑即插即用 一 前期准备二 制作系统启动盘2 1 Ubuntu20 04系统下载2 2 制作U盘启动盘 三 磁盘分区 xff08 重点 xff09 四 Ubuntu系统安装 xff
  • 详解C/C++代码的预处理、编译、汇编、链接全过程

    1 C C 43 43 运行的四个步骤 编写完成一个C C 43 43 程序后 xff0c 想要运行起来 xff0c 必须要经过四个步骤 xff1a 预处理 编译 汇编和链接 每个步骤都会生成对应的文件 xff0c 如下图所示 xff08
  • VSCode调试C++代码的多种方案

    以下内容均针对 Linux 操作系统 xff08 包括Windows的Linux子系统WSL2 xff09 本文是对Linux系统中使用VSCode编译调试C 43 43 代码的系列文章的总结 xff0c 前面三篇文章如下 xff1a 详解
  • 【ROS】rostopic常用命令

    记录ROS查看话题的常用命令 xff1a rostopic 查看节点列表 xff1a rostopic list 查看节点信息 xff0c 如相机节点的内参K矩阵 畸变D矩阵 分辨率等等 rostopic echo camera camer
  • 【ROS笔记】设置、修改ROS环境变量

    ROS设置环境变量 ROS安装到Ubuntu后 xff0c 默认在 opt路径下 xff0c 由于在使用过程中需要频繁在终端使用ROS命令 xff0c 需要对其环境变量进行设置 Ubuntu默认使用终端为bash xff0c 在bash中设
  • 【ROS笔记】工作空间(workspace)、功能包(package)的介绍及创建

    初接触ROS时 xff0c 对其文件结构容易产生困惑和混淆 xff0c 比如 catkin ws到底是什么 xff1f catkin ws src目录下存放的是什么 xff1f catkin ws src路径下怎么还会有其他的src 目录
  • RecyclerView中倒计时item的优雅方案

    本文介绍在RecyclerView中使用倒计时楼层 xff0c 并且每秒刷新显示倒计时 没有纠结于样式 xff0c 主要介绍代码结构和设计模式 先看一下效果 xff1a 我们采取的是观察者模式的方法 xff0c 启动一个handler xf