最近加入一个Android每日一问的知识小组,对技术查漏补缺。
Android 每日一问
正好看到自定义LayoutManager流程?这个问题,虽然这前知道LayoutManager自定义可以做出各种各样的效果,但是从来没有去撸代码实践过。借这个机会,去了解学习一下。
参考文档:
打造属于你的LayoutManager
由旋转画廊,看自定义RecyclerView.LayoutManager
由于第一个参考文档已经写明了详情的过程,我也只是根据其自己撸了一遍而已,代码就不再处理,只把流程写一下,作为这个知识点记录。
1. 复写获得默认的LayoutParams对象。
generateDefaultLayoutParams()。新建继承类就必须复写的
2. 复写子View 的摆放。
onLayoutChildren()。需要在里面计算每一个子View摆放的位置。
- 通过addView()将View添加到RecyclerView里面。
- measureChildWithMargins(),测量view的布局。
- layoutDecorated(),将view 真正摆放到相应的位置。
3. 允许RecyclerView水平或竖直滑动
- canScrollVertically() 或 canScrollHorizontally(),这两个默认是false,不允许滑动。
- scrollVerticallyBy(),处理整体滑动时view的回收显示。
4. 添加缓存,从而快速复用
detachAndScrapAttachedViews()移除所有,重新添加(复用),通过addView,measureChildWithMargins,layoutDecorated添加到RecyclerView 里面。 这里使用的最简单的方法,其实还可以在里面做一个当前可见的集合,然后根据上滑下滑添加某一个,性能会高,但是本质一样,都是复用。
遇到的一个小坑,由于RecyclerView设置高度的wrap_content,导致无法显示出子View,在设置为match_parent之可见。
原因: wrap_content 在LayoutManager.onMeasure()时,其mode 为 AT_MOST。而AT_MOST在RecyclerView.onMeasure()时,使用defalutOnMeasure(),该函数在获取 高度其实取的最小值,若没有设置miniHeight,及padingtop padingbutton,那么就必定为0,这也就是wrap_content显示不出来的原因。
void defaultOnMeasure(int widthSpec, int heightSpec) {
// calling LayoutManager here is not pretty but that API is already public and it is better
// than creating another method since this is internal.
final int width = LayoutManager.chooseSize(widthSpec,
getPaddingLeft() + getPaddingRight(),
ViewCompat.getMinimumWidth(this));
final int height = LayoutManager.chooseSize(heightSpec,
getPaddingTop() + getPaddingBottom(),
ViewCompat.getMinimumHeight(this));
setMeasuredDimension(width, height);
}
public static int chooseSize(int spec, int desired, int min) {
final int mode = View.MeasureSpec.getMode(spec);
final int size = View.MeasureSpec.getSize(spec);
switch (mode) {
case View.MeasureSpec.EXACTLY:
return size;
case View.MeasureSpec.AT_MOST:
return Math.min(size, Math.max(desired, min));
case View.MeasureSpec.UNSPECIFIED:
default:
return Math.max(desired, min);
}
}
解决方法:可以在LayoutManager的onMeasure里面,将其Mode及高度都强制修改即可。我的修改方案:
//LayoutManager.onMeasure()
@Override
public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
final int heightMode = View.MeasureSpec.getMode(heightSpec);
int height = View.MeasureSpec.getSize(heightSpec);
if (heightMode == View.MeasureSpec.AT_MOST) {
if(totalHeight == 0){
for (int i = 0; i < getItemCount(); i++) {
View viewItem = recycler.getViewForPosition(i);
measureChildWithMargins(viewItem, 0, 0);
int w = getDecoratedMeasuredWidth(viewItem);
int h = getDecoratedMeasuredHeight(viewItem);
totalHeight +=h;
}
}
if(totalHeight< height){
height = totalHeight;
}
height = (height & ~(0x3 << 30)) | (View.MeasureSpec.EXACTLY & (0x3 << 30));
}
super.onMeasure(recycler, state, widthSpec, height);
}