真正的嵌套表格RecyclerView,最大性能优化,内有源码

作者编写的一个高效的多媒体支持操作开源库,可多方面的简单配置操作拍照、相册、录制、录音等功能。

该篇文章只讲嵌套RecyclerView,有关其余知识可以去搜索其他RecyclerView相关文章。

什么是嵌套列表

嵌套列表顾名思义就是最外层是一个列表,然后列表的每一个项里面又包含一个表格

为什么单独拿RecyclerView嵌套表格来说,是因为现在网上的有关文章要么并没讲的透彻,要么大部分完全误导(功能实现了,性能相当差),至于国外文章就更少了,因为嵌套表格一直是一种避免的设计。

那么说到这是一种避免的设计,但是像朋友圈,QQ空间,新浪微博,他们是怎么实现的呢?

方案1: 采用RecyclerView共享View pool。
方案2: 封装一个九宫格的Layout,可以根据图片数量layout相应样式。 注意内部的View需要自定义View pool。(目前微信采用此方案)
方案3: 自定义ImageView,在绘制的时候按照九宫格绘制图片,添加九宫格各个区域的事件相应以及绘制相应按下效果。(微博采用此方案)。

RecyclerView是随着14年10月份5.0发布出来的,按照国内的5.0普及大概是16年,也就是说16年前大部分Android用的是ListView,而ListView是没有RecyclerView的View pool的功能,所以国内各大公司不同的解决方案是可以理解的。

那么现在该篇划重点内容:

RecycledViewPool

关于RecycledViewPool,官方文档是这样说的:

Recycled view pools allow multiple RecyclerViews to share a common pool of scrap views. This can be useful if you have multiple RecyclerViews with adapters that use the same view types, for example if you have several data sets with the same kinds of item views displayed by a ViewPager. RecyclerView automatically creates a pool for itself if you don’t provide one.

意思就是RecyclerView可以设置一个ViewHolder的对象池,这个池称为RecycledViewPool,这个对象池可以节省你创建ViewHolder的开销,更能避免GC。即便你不给它设置,它也会自己创建一个。同时,支持多个RecylerView间共用一个RecycledViewPool

缓存策略

在进一步解说RecycledViewPool前,先说说Recyclerview的缓存策略。
Recyclerview有4级缓存

缓存类 是否需要回调createView 是否需要回调bindView 生命周期 备注
mChangedScrap和mAttachedScrap onLayout函数周期内 用于屏幕内ItemView快速重用。mChangedScrap 表示数据已经改变的viewHolder列表。mAttachedScrap表示未与RecyclerView分离的ViewHolder列表
mCachedViews 与mAdapter一致,当mAdapter被更换时,mCachedViews即被缓存至mRecycledPool 默认缓存2个,即缓存屏幕外2个itemView
mViewCacheExtension 这是开发者可以控制的ViewHolder缓存的帮助类,默认不实现
mRecyclerPool 与自身生命周期一致,不再被引用时即被释放 默认上限为5个,代码可以设置所有都公用同一个RecycledViewPool

接下来我从网上找来2个图片讲解这个4级缓存机制


原图链接https://zhooker.github.io/2017/08/14/%E5%85%B3%E4%BA%8ERecyclerview%E7%9A%84%E7%BC%93%E5%AD%98%E6%9C%BA%E5%88%B6%E7%9A%84%E7%90%86%E8%A7%A3/
原图链接https://zhuanlan.zhihu.com/p/23339185

使用RecycledViewPool

仔细看了上文流程,会发现我们只需要让互相嵌套的RecyclerView的item都进入同一个共享池即可

外层的RecyclerView初始化时,加入RecycledViewPool

    /**
     * 初始化view
     */
    private void initView() {
        // 创建 ViewHolder的缓存共享池
        RecyclerView.RecycledViewPool recycledViewPool = new RecyclerView.RecycledViewPool();
        RecyclerView recyclerView = findViewById(R.id.rlParent);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        // 需要注意:要使用RecycledViewPool的话,如果使用的LayoutManager是LinearLayoutManager或其子类(如GridLayoutManager),需要手动开启这个特性
        layoutManager.setRecycleChildrenOnDetach(true);
        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setRecycledViewPool(recycledViewPool);
        // 传递RecycledViewPool共享池进父适配器,让父适配器里面的子适配器也共用同一个共享池
        ParentInfoAdapter adapter = new ParentInfoAdapter(this, dataInfoList, recycledViewPool);
        recyclerView.setAdapter(adapter);
    }

外层的RecyclerView初始化内层的RecyclerView的时候,也加入RecycledViewPool

    /**
     * ViewHolder
     */
    static class ViewHolder extends RecyclerView.ViewHolder {

        TextView mTitle;//标题
        RecyclerView mRecyclerView; //子RecyclerView

        ViewHolder(View itemView, RecyclerView.RecycledViewPool recycledViewPool) {
            super(itemView);
            mTitle = itemView.findViewById(R.id.tvParentTitle);
            mRecyclerView = itemView.findViewById(R.id.rlChild);
            LinearLayoutManager manager = new LinearLayoutManager(itemView.getContext());
            // 需要注意:要使用RecycledViewPool的话,如果使用的LayoutManager是LinearLayoutManager或其子类(如GridLayoutManager),需要手动开启这个特性
            manager.setRecycleChildrenOnDetach(true);
            // 嵌套的子RecyclerView,需要将LinearLayoutManager设置setAutoMeasureEnabled(true)成自适应高度
            manager.setAutoMeasureEnabled(true);
            // 子RecyclerView没必要滚动本身
            mRecyclerView.setNestedScrollingEnabled(false);
            mRecyclerView.setLayoutManager(manager);
            // 子RecyclerView现在和父RecyclerView在同一个共享池了
            mRecyclerView.setRecycledViewPool(recycledViewPool);
        }
    }
    // 其实RecycledViewPool的内部维护了一个Map,里面以不同的viewType为Key存储了各自对应的ViewHolder集合,所以这边设置了常量防止父适配器和子适配器的ViewType冲突
    public static final int PARENT_VIEW_TYPE = 0;
    public static final int CHILD_VIEW_TYPE = 1;

核心代码在上面了,具体效果请下载我写的一个demo,有非常详细的注释
https://github.com/zhongjhATC/NestingRecyclerView

以下为后续更新

1.嵌套列表如何取消里层模块的点击事件

2018-12-20更新
原因:
有些需求是里层列表是不需要点击事件的,如果不做一些特殊设置的话,会拦截掉外层列表的点击事件。
直接上代码:

    @Override
    public VH onCreateViewHolder(ViewGroup parent, int viewType) {
        final View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_wifi_management, parent, false);
        ViewHolder viewHolder = new ViewHolder(view, recycledViewPool);
        // 点击事件
        viewHolder.rootView.setOnClickListener(v -> {
            XXXXXX……
        });
        // 里层的列表点击事件
        viewHolder.recyclerview.setOnTouchListener((v, event) -> viewHolder.rootView.onTouchEvent(event));
    }

2. 为什么我弄了共享缓存,为什么还是会跑入onCreateViewHolder

2021-4-30更新
关于这方面已经在代码上微调了一下更深层的解释

  1. 有同学提到为何设置后,还是会跑onCreateViewHolder呢?
  2. 是因为recycledViewPool默认为5的最大存储,当超出缓存后不会缓存,所以我们稍微改大点,setMaxRecycledViews设置20
  3. 但是设置20以后为什么还是会跑onCreateViewHolder呢,很简单,因为列表可能你只看到屏幕上的10条,实际上在内存上Android已经取出20条出来了,多余的,还是会跑onCreateViewHolder
  4. 设置到25设置30就不会看到onCreateViewHolder了,但是手机如果一直存在30个view甚至更多,真的合适吗,这个需要更多的场景合理决定

喜欢就点一下star,欢迎有更多相关问题交流

其实还有能更进一步的优化滑动流畅空间,等我再研究一波再更新代码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,588评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,456评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,146评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,387评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,481评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,510评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,522评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,296评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,745评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,039评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,202评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,901评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,538评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,165评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,415评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,081评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,085评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,973评论 25 707
  • 这篇文章分三个部分,简单跟大家讲一下 RecyclerView 的常用方法与奇葩用法;工作原理与ListView比...
    LucasAdam阅读 4,383评论 0 27
  • 简介: 提供一个让有限的窗口变成一个大数据集的灵活视图。 术语表: Adapter:RecyclerView的子类...
    酷泡泡阅读 5,159评论 0 16
  • 儿子今天放学归来,满腹高兴告诉我,妈妈,我今天下午课堂作业,全部完成。我说,妈妈还以为你又要挨留呢!儿子笑道,你对...
    文文吧阅读 173评论 0 0
  • 001生活需要仪式感 002生气不可到日落 003每天都必须特别认真地活着。 004春天真好 活着真好 00...
    视觉笔记找文莹阅读 601评论 2 8