对LayoutManager中的基本的几个集合和方法了解了,自定义LayoutManager就完成一半了。
对集合和方法的解释:
LayoutManager:
1.getViewForPosition():是Recycler的方法,在需要获取新的View时直接申请,返回的View可能是之前回收的垃圾View,也可能是new出来的新View(RecyclerView完成);
取View时,
(1)先从Scrap中取;
(2)取不到就去Recycle中取,但是Recycle取到之后要调用Adapter的onBindViewHolder方法更新数据;
(3)如果还是取不到,则通过Adapter的onCreateViewHolder取新生成然后onBindViewHolder绑定数据。
2.Scrap:是指里面缓存的View是接下来需要用到的,即里面的绑定的数据无需更改,可以直接拿来用的,是一个轻量级缓存;
3.Recycle:缓存的View为里面的数据需要重新绑定,即需要通过Adapter重新绑定数据;
4.移除子View:
(1)Detach:Detach的View放在Scrap缓存中;
调用方式:detachAndScrapView() 描述:Detch方式只是让子View不显示在RecyclerView中,但是并不移除子View对象。适用于需要改变item位置的时候,移除但马上需要显示。
(2)Remove:Remove掉的View放在Recycle缓存中。
调用方式:removeAndRecycleView() 描述:Remove方式是从RecyclerView的子View集合中移除,但是这些View可以用作循环利用。适用于该子View暂时不需要显示了。
5.detachAndScrapAttachedViews(recycler):将所有的子View先Detach掉,放入到Scrap缓存中。
自定义LayoutManager,直接上代码,代码的注释足够详细:
public class MyLayoutManager extends RecyclerView.LayoutManager {
/****** 必须实现的方法 ******/
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
/****** 滑动时,滚动的距离 ******/
private int verticalOffset = 0;
/****** 在LayoutChild时,计算出所有item的高度和 ******/
private int totalHeight = 0;
/****** 记录在lauoutChild时,每个item的初始位置 ******/
private SparseArray itemFrames = new SparseArray<>();
/****** 记录item是否已经addView并且layout了
* (这里很关键,如果滑动到最顶部时,每次滑动,虽然距离是0,但是会一直将View加入RecyclerView中,导致oom,这里主要主要是用来规避这一点)
* 详情见recycleAndFillChild方法******/
private SparseArray hasLayout = new SparseArray<>();
/****** 通过layout所有item,记录每个item的初始位置和是否addView和layout了 ******/
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
detachAndScrapAttachedViews(recycler);
int offsetY = 0;
int itemCount = getItemCount();
totalHeight = 0;
for (int i = 0; i < itemCount; i++) {
View view = recycler.getViewForPosition(i);
addView(view);
measureChildWithMargins(view, 0, 0);
int width = getDecoratedMeasuredWidth(view);
int height = getDecoratedMeasuredHeight(view);
layoutDecorated(view, 0, offsetY, width, offsetY + height);
Rect frame = itemFrames.get(i);
if (frame == null) {
itemFrames.put(i, new Rect(0, offsetY, width, offsetY + height));
} else {
frame.set(0, offsetY, width, offsetY + height);
}
Boolean isLayout = hasLayout.get(i);
if (isLayout == null) {
hasLayout.put(i, false);
} else {
hasLayout.setValueAt(i, false);
}
offsetY += height;
totalHeight += height;
}
/****** 如果调用了RequestLayout,会导致RecyclerView重绘,从而重新布局子View,那么布局完子View后,将其回滚至原来的位置。 ******/
offsetChildrenVertical(-verticalOffset);
}
/****** 返回true表示可以垂直滑动 ******/
@Override
public boolean canScrollVertically() {
return true;
}
/****** RecyclerView在滑动时调用该方法 ******/
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
int travel = dy;
/****** 滑动到顶部时,限制不能滑出去 ******/
if (verticalOffset + dy < 0) {
travel = -verticalOffset;
}
/****** 滑动到底部时,限制不能滑出去 ******/
else if (verticalOffset + dy > totalHeight - getVerticalSpace()) {
travel = totalHeight - getVerticalSpace() - verticalOffset;
}
verticalOffset += travel;
/****** 滑动内容 ******/
offsetChildrenVertical(-travel);
/****** 处理回收机制 ******/
recycleAndFillChild(recycler, state);
Log.e("MyLayoutManager", " childView count:" + getChildCount());
return travel;
}
/****** 回收机制 ******/
private void recycleAndFillChild(RecyclerView.Recycler recycler, RecyclerView.State state) {
if (state.isPreLayout()) {
return;
}
Rect displayArea = new Rect(0, verticalOffset, getHorizontalSpace(), verticalOffset + getVerticalSpace());
/****** 将已经加入到RecyclerView并且显示的View中过滤出将要移出界面的View ******/
Rect childFrame = new Rect();
for (int i = 0; i < getChildCount(); i++) {
/****** getChildAt表示获取通过addView加入到RecyclerView中的View ******/
View child = getChildAt(i);
childFrame.set(getDecoratedLeft(child),
getDecoratedTop(child),
getDecoratedRight(child),
getDecoratedBottom(child));
if (!Rect.intersects(displayArea, childFrame)) {
int removePosition = getPosition(child);
hasLayout.setValueAt(removePosition, false);
/****** 不需要显示的View,先放到Recyle里面待循环利用 ******/
removeAndRecycleView(child, recycler);
}
}
/****** 主要到这里是getItemCount,跟上面(getChildCount)不一样 ******/
for (int i = 0; i < getItemCount(); i++) {
if (Rect.intersects(displayArea, itemFrames.get(i))) {
/****** 这里是避免重复addView同一个View ******/
if (!hasLayout.get(i)) {
View child = recycler.getViewForPosition(i);
measureChildWithMargins(child, 0, 0);
addView(child);
childFrame = itemFrames.get(i);
layoutDecorated(child, childFrame.left, childFrame.top - verticalOffset, childFrame.right, childFrame.bottom - verticalOffset);
hasLayout.setValueAt(i, true);
}
}
}
}
/****** 获取RecyclerView内容显示高度 ******/
private int getVerticalSpace() {
return getHeight() - getPaddingBottom() - getPaddingTop();
}
/****** 获取RecyclerView内容显示宽度 ******/
private int getHorizontalSpace() {
return getWidth() - getPaddingLeft() - getPaddingRight();
}
}
6.四级缓存:
各级缓存功能
RV 之所以要将缓存分成这么多块,是为了在功能上进行一些区分,并分别对应不同的使用场景。
a 第一级缓存 mAttachedScrap&mChangedScrap:
是两个名为 Scrap 的 ArrayList,这两者主要用来缓存屏幕内的 ViewHolder。为什么屏幕内的 ViewHolder 需要缓存呢?做过 App 开发的应该都熟悉下面的布局场景:
通过下拉刷新列表中的内容,当刷新被触发时,只需要在原有的 ViewHolder 基础上进行重新绑定新的数据 data 即可,而这些旧的 ViewHolder 就是被保存在 mAttachedScrap 和 mChangedScrap 中。实际上当我们调用 RV 的 notifyXXX 方法时,就会向这两个列表进行填充,将旧 ViewHolder 缓存起来。
b 第二级缓存 mCachedViews:
它用来缓存移除屏幕之外的 ViewHolder,默认情况下缓存个数是 2,不过可以通过 setViewCacheSize 方法来改变缓存的容量大小。如果 mCachedViews 的容量已满,则会根据 FIFO 的规则将旧 ViewHolder 抛弃,然后添加新的 ViewHolder,如下所示:
通常情况下刚被移出屏幕的 ViewHolder 有可能接下来马上就会使用到,所以 RV 不会立即将其设置为无效 ViewHolder,而是会将它们保存到 cache 中,但又不能将所有移除屏幕的 ViewHolder 都视为有效 ViewHolder,所以它的默认容量只有 2 个。
c 第三级缓存 ViewCacheExtension
这是 RV 预留给开发人员的一个抽象类,在这个类中只有一个抽象方法,如下:
开发人员可以通过继承 ViewCacheExtension,并复写抽象方法 getViewForPositionAndType 来实现自己的缓存机制。只是一般情况下我们不会自己实现也不建议自己去添加缓存逻辑,因为这个类的使用门槛较高,需要开发人员对 RV 的源码非常熟悉。
d 第四级缓存 RecycledViewPool
RecycledViewPool 同样是用来缓存屏幕外的 ViewHolder,当 mCachedViews 中的个数已满(默认为 2),则从 mCachedViews 中淘汰出来的 ViewHolder 会先缓存到 RecycledViewPool 中。ViewHolder 在被缓存到 RecycledViewPool 时,会将内部的数据清理,因此从 RecycledViewPool 中取出来的 ViewHolder 需要重新调用 onBindViewHolder 绑定数据。这就同最早的 ListView 中的使用 ViewHolder 复用 convertView 的道理是一致的,因此 RV 也算是将 ListView 的优点完美的继承过来。
RecycledViewPool 还有一个重要功能,官方对其有如下解释:
RecycledViewPool lets you share Views between multiple RecyclerViews.
可以看出,多个 RV 之间可以共享一个 RecycledViewPool,这对于多 tab 界面的优化效果会很显著。需要注意的是,RecycledViewPool 是根据 type 来获取 ViewHolder,每个 type 默认最大缓存 5 个。因此多个 RecyclerView 共享 RecycledViewPool 时,必须确保共享的 RecyclerView 使用的 Adapter 是同一个,或 view type 是不会冲突的。