参考资料
背景介绍
RecyclerView由于其强大的扩展性,现在已经逐步的取代了ListView和GridView了。为了实现不同的布局效果,我们会用到官方提供的LinearLayoutManager、GridLayoutManager和StaggeredGridLayoutManager。但这些布局只能满足日常需求,在一些比较复杂的布局中,它们就力不从心了,强行拼凑实现,带来的后果就是较差的体验和性能。所以能够自定义LayoutManager还是十分必要的,它能够解放创造力,构造复杂的、流畅的滑动列表。上面几篇参考资料中就实现了一些不寻常的效果,我们可以看到,这些效果如果用常规的方案去实现将会十分蹩脚。
揭开LayoutManager中不为人知的秘密
自定义LayoutManager
主要要求我们完成三件事情:
- 计算每个ItemView的位置;
- 处理滑动事件;
- 缓存并重用ItemView;
而我们比较重要的工作是在onLayoutChildern()
这个回调方法中完成的。
下面我们就来一一解析。
预先准备
当我们extends RecyclerView.LayoutManager是,我们会被强制要求重写generateDefaultLayoutParams()
方法,如方法名字一样,我们需要提供一个默认的LayoutParams,这里为我们的每个ItemView
提供默认的LayoutParams
,所以它能够直接影响到我们的布局效果,这里我们设置成WRAP_CONTENT
,让ItemView
获得决定权。
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT,
RecyclerView.LayoutParams.WRAP_CONTENT);
}
计算ItemView的位置
1.实现简单的LayoutManager
先看效果图:
再看代码:
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
super.onLayoutChildren(recycler, state);
// 先把所有的View先从RecyclerView中detach掉,然后标记为"Scrap"状态,表示这些View处于可被重用状态(非显示中)。
// 实际就是把View放到了Recycler中的一个集合中。
detachAndScrapAttachedViews(recycler);
calculateChildrenSite(recycler);
}
private void calculateChildrenSite(RecyclerView.Recycler recycler) {
totalHeight = 0;
for (int i = 0; i < getItemCount(); i++) {
// 遍历Recycler中保存的View取出来
View view = recycler.getViewForPosition(i);
addView(view); // 因为刚刚进行了detach操作,所以现在可以重新添加
measureChildWithMargins(view, 0, 0); // 通知测量view的margin值
int width = getDecoratedMeasuredWidth(view); // 计算view实际大小,包括了ItemDecorator中设置的偏移量。
int height = getDecoratedMeasuredHeight(view);
Rect mTmpRect = new Rect();
//调用这个方法能够调整ItemView的大小,以除去ItemDecorator。
calculateItemDecorationsForChild(view, mTmpRect);
// 调用这句我们指定了该View的显示区域,并将View显示上去,此时所有区域都用于显示View,
//包括ItemDecorator设置的距离。
layoutDecorated(view, 0, totalHeight, w