作者编写的一个高效的多媒体支持操作开源库,可多方面的简单配置操作拍照、相册、录制、录音等功能。
该篇文章只讲嵌套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级缓存机制
使用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更新
关于这方面已经在代码上微调了一下更深层的解释
- 有同学提到为何设置后,还是会跑onCreateViewHolder呢?
- 是因为recycledViewPool默认为5的最大存储,当超出缓存后不会缓存,所以我们稍微改大点,setMaxRecycledViews设置20
- 但是设置20以后为什么还是会跑onCreateViewHolder呢,很简单,因为列表可能你只看到屏幕上的10条,实际上在内存上Android已经取出20条出来了,多余的,还是会跑onCreateViewHolder
- 设置到25设置30就不会看到onCreateViewHolder了,但是手机如果一直存在30个view甚至更多,真的合适吗,这个需要更多的场景合理决定