真正的嵌套表格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,欢迎有更多相关问题交流

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

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