时间过得飞快,距离上次写博客又过了一个半月,想了想不该偷懒下去了,然后就把前段时间写的一个 LayoutManager 拿出来写一下。RecycleView 相信大家一定十分熟悉了,相比 ListView 它具备更多级的缓存以及定向刷新的特性,除此之外也就是本文要说的自定义 LayoutManager 也是十分的靓仔,和传统使用 ViewGroup 布局相比,它增加了回收和复用子 Item 的能力,用起来是真香。
一般在做通讯录的时候,我们会为用户分组,从而绘制的时候也需要绘制各组的头部,如果在使用 RecycleView 来实现这种效果的时候,通常我们会使用 ItemDecoration ,ItemDecoration 在测量时为每个(或指定的)子 View 的 LayoutParams 设置四边的偏移量,然后 LayoutManager 在使用 layoutDecoratedWithMargins() 时会根据相应的偏移量来布局 childView ,我们再根据 childView 之间的留白在 ItemDecoration#onDraw() 时来绘制我们的分组头部,在 ItemDecoration#onDrawOver() 时绘制被移出屏幕的最近一个头部,最终达到我们要的效果。不过,如果我们想要这个头部支持点击怎么办?ItemDecoration 顾名思义就是为 Item 添加装饰物,是不支持点击交互的,那么就需要我们另外想法子了。我的方法是自定义一个 LayoutManager。
先见效果图:
先声明以下 LLM 为 LinearLayoutManager 的简写),FLM 为本文自定义LayoutManger的简称。
自定义 LayoutManager 第一步,返回自定义的 LayoutParams
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
这里我们要做的布局类似于线性布局,所以采用宽高都有view自己决定的测量参数,如果是网格布局或者是交错式网格布局,则会根据方向进行调整。
然后就是第二步,重写 onLayoutChildren 方法,我们先来看一看 LLM 在这里是怎么写的,代码很长,所以简略掉其中各种判断,我们只贴其中的关键行:
/**
* {@inheritDoc}
*/
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
......
// 不需要布局,移除并回收所有的view
if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
return;
}
}
......
// 收集计算锚点信息
......
// 预布局动画相关代码
......
// 遍历所有的 child 根据其 ViewHolder 的不同状态将 ViewVolder 添加到不同的缓存集合
detachAndScrapAttachedViews(recycler);
......
// 根据布局方向填充
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
......
fill(recycler, mLayoutState, state, false);
......
} else {
// 另外一个方向
......
}
// 修正偏差,LinearLayoutManager内部的scrap缓存池未移除的view进行布局(应该是与动画相关)
......
}
然后是 fill() 方法里面的关键行:
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
......
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
......
// 这里进行单个view进行布局
layoutChunk(recycler, state, layoutState, layoutChunkResult);
......
}
......
return start - layoutState.mAvailable;
}
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
......
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState