前言
这篇文章实现了一个简单的LayoutManager,重点在于从原理方面一步一步去了解如何自定义一个LayoutManager。麻雀虽小,但五脏俱全。从自定义LayoutManager的layout布局,到scroll滑动,再到添加简单的动画效果。 其实,自定义一个LayoutManager也没那么难。
基本概念
Recycler
LayoutManafger调用 getViewForPosition 获取一个item,Recycler会决定是从缓存返回还是生成新的item。在自定义LayoutManger的时候,要保证不可见的视图被传递给Recycler。
Scrap 或 Recycler
Recycler是二级缓存,一个scrap heap 和一个 recyle pool, scrap 中的数据是正确的数据,比如我们快速上下滑动列表时,在边缘的栏目一会显示一会消失,所以会放在scrap中。 而已经消失并不使用的item,会被放在recyle中,其中的数据也是不正确的。
每次LayoutManager去请求一个视图调用getViewForPosition的时候,会先从scrap heap中找,存在直接返回。否则去recyle pool 中找一个视图,然后重新在adapter中绑定数据。最终如果还没有缓存,调用我们在adapter中重写的onCreateViewHolder,生成一个新的ViewHolder绑定数据并返回。
使用 detachAndScrapView 将视图放进scrap中去,使用removeAndRecycleView 将可能不会再用的视图放回recycler并且后续如果使用,还要进行rebind
小结
其实,上面这些都是废话,只要知道要获得一个view和用完一个view,都要通过recycler。常用的方法有getViewForPosition ,detachAndScrapView 和 removeAndRecycleView
自定义LayoutManager
generateDefaultLayoutParams
作用:控制每个item的layoutParams
为每一个childView设置的LayoutParams在这个方法中返回。很简单,一般我们都直接返回一个WrapContent的lp
初始布局 onLayoutChildren
这个方法会在一个view 第一次执行layout的时候调用,同时也会在adaper的数据集改变并通知观察者(也就是view)的时候调用。所以在其中每一次布局的时候,要先将之前放置的无用的View放回recycler中,因为这些View我们在后续还可能使用,为了减少初始化以及bind的时间,我们调用detachAndScrapAttachedViews。此外,对于不会再用到的View,可以调用removeAndRecycleView进行回收。
if (getItemCount() == 0) {
offset = 0;
detachAndScrapAttachedViews(recycler);
return;
}
这里自定义的LayoutManager比较简单,假定全部的item都是相同的大小。所以可以在一开始进行测绘:
if (getChildCount() == 0) {
View scrap = recycler.getViewForPosition(0);
addView(scrap);
measureChildWithMargins(scrap, 0, 0);
mDecoratedChildWidth = getDecoratedMeasuredWidth(scrap);
mDecoratedChildHeight = getDecoratedMeasuredHeight(scrap);
startLeft = (getHorizontalSpace() - mDecoratedChildWidth) / 2;
startTop = (getVerticalSpace() - mDecoratedChildHeight) / 2;
interval = 10;
detachAndScrapView(scrap, recycler);
}
这里注意getItemCount和getChildCount的区别:前者是adapter中添加的数据的数目,而后者是当前recyclerView中已经添加的子View的数目。所以上述代码的含义就是,如果没有添加过子View,那么从recycler中取出一个并完成测绘:
recycler.getViewForPosition(0);
addView(scrap);
测绘完成后,再重新放回recycler中,调用
detachAndScrapView(scrap, recycler);
最后,再将之前添加的全部子View放回recycler中,因为一会还要使用,为了避免rebind,调用
detachAndScrapAttachedViews(recycler);
然后就可以进行layoutChildren的过程了。
先来一个简单的,如下:
int left = 100, top = 0;
for (int i = 0; i< getItemCount(); i++) {
if (outOfRange(top)) continue;
View scrap = recycler.getViewForPosition(i);
measureChildWithMargins(scrap, 0, 0);
addView(scrap);
layoutDecorated(scrap, left, top, left + mDecoratedChildWidth, top + mDecoratedChildHeight);
top += mDecoratedChildHeight + interval;
}
基本效果就是这样: