【Android】掌握自定义LayoutManager(一) 系列开篇 常见误区、问题、注意事项,常用API。

文章详细解释了RecyclerView中自定义LayoutManager如何管理ItemView的复用、detach和recycle过程,特别强调了ViewHolder的回收与内存管理,澄清了关于RecyclerView复用的常见误区。
摘要由CSDN通过智能技术生成

A4:不是,实际上这是自定义LayoutManager的重头戏之一,要做到在合适的时机回收 不可见的旧子View ,复用子View layout 新的子View,以及Q2提及的在LayoutManager的初始化时合理布局可见数量的子View等,才算是复用了ItemView。

注意,这里的回收是recycle,而不是detach。

如果你只detach了ItemView,并没有recycle它们,它们会一直被保存在Recycler的mAttachedScrap里,它是一个ArrayList,保存了被detach但还没有recycle的ViewHolder。

public final class Recycler {

final ArrayList mAttachedScrap = new ArrayList<>();

(实际上Recycler内部的缓存机制远不止一个mAttachedScrap 。)

Q5 用RecyclerView就等于ItemView复用?


A5:显然也不是。除了Q4的因素外,这里还有一个很大的误区:很多人认为使用了RecyclerView,ItemView就都回收复用了。

这里出个题:基本上APP都有个TopBanner在,它放在RecyclerView里作为HeaderView(通过特殊的ItemViewType实现),剩下都是普通的ItemView,那么列表滚动,当Banner早已不可见时,它的View(ViewHolder)会被回收被其他ItemView复用吗?

如下图:

这里写图片描述

答案:Banner的ViewHolder 会被回收,但该ViewHolder的内存空间 不会被释放不会被其他的ItemView复用

回收都好理解,在屏幕上不可见时,LayoutManager会把它回收至RecyclerViewPool里。

然而却不会给normalItem复用,因为它们的ItemViewType不同

所以它的内存空间不会被释放,将一直被RecyclerViewPool持有着,等待着需求相同ItemViewType的ViewHolder的请求到来。

即,当页面滚动回顶部,显示Banner时,这个View会被复用。

先说为什么,再说如何去验证。

为什么?

这涉及到Recycler、RecyclerViewPool的知识,(小安利,我在http://blog.csdn.net/zxt0601/article/details/52267325 这篇文章的第四节里对RecyclerViewPool的源码进行过全解,不过大家也可以自己去查看,源码很短。)

在LayoutManager里,获取childView是通过如下方法得到:

View child = recycler.getViewForPosition(i);

该方法内部,先通过position去获取是否有detach掉的scrapView(ViewHolder),

holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);

如果没有则根据position去获取itemViewType,

final int type = mAdapter.getItemViewType(offsetPosition);

根据itemViewType获取在RecyclerViewPool里是否有该ViewHolder,

holder = getRecycledViewPool().getRecycledView(type);

这里由于我们的Banner的viewType和normalItem的viewType不一样即使Banner被回收进了RecyclerViewPool,但是由于itemViewtype和普通的ItemView不同,它也无法被取出、从而复用,(发散一下,另外一点,它也无法被释放,被强引用在内存里,http://blog.csdn.net/zxt0601/article/details/52267325 这篇文章有详细分析)。

再往下由于holder还是空的,最终便会调用Adapter的onCreateViewHolder()方法create一个新的ViewHolder。

holder = mAdapter.createViewHolder(RecyclerView.this, type);

验证:

感兴趣的人去重写任意Adapter的getItemViewType()方法:

@Override

public int getItemViewType(int position) {

return position;

}

这样每一个ItemViewType都不一样,RecyclerView不会有任何的复用,因为每一个ItemView在RecyclerViewPool里都找不到可以复用的holder,ItemView有n个,onCreateViewHolder方法会执行n次。

看到这里就能回答Q2一的问题:

因为在初始化时,Recycler(scrapCache)和RecyclerViewPool里的缓存都是空的,所以此时得到的ViewHolder都是通过onCreateViewHolder(),new 出的ViewHolder。如果此时get了整个itemCount数量的View,那么也会new出itemCount数量的ViewHolder,此时这些ViewHolder都存在内存里,和普通ViewGroup毫无分别,也更容易OOM。

Q6 RecyclerView的缓存机制简述


A6: 上面BB了这么多,涉及到Recycler、RecyclerViewPool以及scrap,detach,remove,recycle等概念。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这张图摘自(http://kymjs.com/code/2016/07/10/01),源头应该是Google官方的视频里。

我理解图上的cache是被detach掉的ViewHolder存放的区域,即scrapCache区域。

这个区域由

final ArrayList mAttachedScrap = new ArrayList<>();

ArrayList mChangedScrap = null;

final ArrayList mCachedViews = new ArrayList();

这三个ArrayList组成。

而被remove掉的ViewHolder会按照ViewType分组被存放在RecyclerViewPool里,默认最大缓存每组(ViewType)5个。

private SparseArray<ArrayList> mScrap =

new SparseArray<ArrayList>();

Q7 detach 和recycle的时机。


一个View只是暂时被清除掉,稍后立刻就要用到,使用detach。它会被缓存进scrapCache的区域。

一个View 不再显示在屏幕上,需要被清除掉,并且下次再显示它的时机目前未知 ,使用remove。它会被以viewType分组,缓存进RecyclerViewPool里。

**注意:一个View只被detach,没有被recycle的话,不会放进RecyclerViewPool里,会一直存在recycler的scrap 中。**网上有人的Demo就是如此,因此View也没有被复用,有多少ItemCount,就会new出多少个ViewHolder。

Q8 初始化时,onLayoutChildren()为什么会执行两次?


答 :参看RecyclerView源码,onLayoutChildren 会执行两次,一次RecyclerView的onMeasure() 一次onLayout()。

李菊福:RecyclerView的onMeasure(),会调用dispatchLayoutStep2()方法,该方法内部会调用 mLayout.onLayoutChildren(mRecycler, mState); ,这是第一次。如下:

@Override

protected void onMeasure(int widthSpec, int heightSpec) {

dispatchLayoutStep2();

}

/**

  • The second layout step where we do the actual layout of the views for the final state.

  • This step might be run multiple times if necessary (e.g. measure).

*/

private void dispatchLayoutStep2() {

mLayout.onLayoutChildren(mRecycler, mState);

}

onLayout()方法会调用dispatchLayout();,该方法内部又调用了dispatchLayoutStep2();,这是第二次。

Q9 基于上个问题,我们要注意什么?


答:即使是在写onLayoutChildren()方法时,也要考虑将屏幕上的View(如果有),detach掉,否则屏幕初始化时,同一个position的ViewHolder,也会onCreateViewHolder两次。因此childCount也会翻倍。

最后也是最重要的


LayoutManager API 支持强大且复杂的布局回收,正因为它API强大,所以我们需要实现大量的代码才能完成功能。不要过度封装、过度优化你的代码,只要能完成你的需求即可。(当然最基本的要求:ViewHolder复用 要满足

原话如下:

这里写图片描述

文章链接:http://wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/

该文章是我见过学习自定义LayoutManager最好的资料。

二 常用API:

========

布局API:


//找recycler要一个childItemView,我们不管它是从scrap里取,还是从RecyclerViewPool里取,亦或是onCreateViewHolder里拿。

View view = recycler.getViewForPosition(xxx); //获取postion为xxx的View

addView(view);//将View添加至RecyclerView中,

addView(child, 0);//将View添加至RecyclerView中,childIndex为0,但是View的位置还是由layout的位置决定,该方法在逆序layout子View时有大用

measureChildWithMargins(scrap, 0, 0);//测量View,这个方法会考虑到View的ItemDecoration以及Margin

//将ViewLayout出来,显示在屏幕上,内部会自动追加上该View的ItemDecoration和Margin。此时我们的View已经可见了

layoutDecoratedWithMargins(view, leftOffset, topOffset,

leftOffset + getDecoratedMeasuredWidth(view),

topOffset + getDecoratedMeasuredHeight(view));

回收API:


detachAndScrapAttachedViews(recycler);//detach轻量回收所有View

detachAndScrapView(view, recycler);//detach轻量回收指定View

// recycle真的回收一个View ,该View再次回来需要执行onBindViewHolder方法

removeAndRecycleView(View child, Recycler recycler)

removeAndRecycleAllViews(Recycler recycler);


detachView(view);//超级轻量回收一个View,马上就要添加回来

attachView(view);//将上个方法detach的View attach回来

recycler.recycleView(viewCache.valueAt(i));//detachView 后 没有attachView的话 就要真的回收掉他们

移动子ViewAPI:


offsetChildrenVertical(-dy); // 竖直平移容器内的item

offsetChildrenHorizontal(-dx);//水平平移容器内的item

工具API:

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

最后

以前一直是自己在网上东平西凑的找,找到的东西也是零零散散,很多时候都是看着看着就没了,时间浪费了,问题却还没得到解决,很让人抓狂。

后面我就自己整理了一套资料,还别说,真香!

资料有条理,有系统,还很全面,我不方便直接放出来,大家可以先看看有没有用得到的地方吧。

系列教程图片

2020Android复习资料汇总.png

flutter

NDK

设计思想开源框架

微信小程序

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

面我就自己整理了一套资料,还别说,真香!

资料有条理,有系统,还很全面,我不方便直接放出来,大家可以先看看有没有用得到的地方吧。

[外链图片转存中…(img-essv5aE5-1712683232701)]

[外链图片转存中…(img-IlgeJBpb-1712683232701)]

[外链图片转存中…(img-Vw1NroNq-1712683232701)]

[外链图片转存中…(img-z55WFzlI-1712683232701)]

[外链图片转存中…(img-LuevNjIX-1712683232702)]

[外链图片转存中…(img-giz7nXVe-1712683232702)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值