- 添加 通过addView方法把子View添加进ViewGroup或直接在xml中直接添加;
- 测量 重写onMeasure方法并在这里决定自身尺寸以及每一个子View大小;
- 布局 重写onLayout方法,在里面调用子View的layout方法来确定它的位置和尺寸;
其实在自定义LayoutManager中,在流程上也是差不多的,我们需要重写onLayoutChildren方法,这个方法会在初始化或者Adapter数据集更新时回调,在这方法里面,需要做以下事情:
- 进行布局之前,我们需要调用detachAndScrapAttachedViews方法把屏幕中的Items都分离出来,内部调整好位置和数据后,再把它添加回去(如果需要的话);
- 分离了之后,我们就要想办法把它们再添加回去了,所以需要通过addView方法来添加,那这些View在哪里得到呢? 我们需要调用 Recycler的getViewForPosition(int position) 方法来获取;
- 获取到Item并重新添加了之后,我们还需要对它进行测量,这时候可以调用measureChild或measureChildWithMargins方法,两者的区别我们已经了解过了,相信同学们都能根据需求选择更合适的方法;
- 在测量完还需要做什么呢? 没错,就是布局了,我们也是根据需求来决定使用layoutDecorated还是layoutDecoratedWithMargins方法;
- 在自定义ViewGroup中,layout完就可以运行看效果了,但在LayoutManager还有一件非常重要的事情,就是回收了,我们在layout之后,还要把一些不再需要的Items回收,以保证滑动的流畅度;
以上内容出自陈小缘的自定义LayoutManager第十一式之飞龙在天。
布局实现
再看下相关参数:
如果去掉itemView的缩放,透明度动画,那么效果是这样的:
看到的效果与LinearLayoutManager一样,但本篇并不使用LinearLayoutManager,而是通过自定义LayoutManager来实现。
索引值为0的view 一次完全滑出屏幕所需要的移动距离,定位为 firstChildCompleteScrollLength
;非索引值为0的view滑出屏幕所需要移动的距离为: firstChildCompleteScrollLength
+ onceCompleteScrollLength
; item 之间的间距为 normalViewGap
我们在 scrollHorizontallyBy
方法中记录偏移量 dx
,保存一个累计偏移量 mHorizontalOffset
,然后针对索引值为0与非0两种情况,在 mHorizontalOffset
小于 firstChildCompleteScrollLength
情况下,用该偏移量除以 firstChildCompleteScrollLength
获取到已经滚动了的百分比 fraction
;同理索引值非0的情况下,偏移量需要减去 firstChildCompleteScrollLength
来获取到滚动的百分比。根据百分比,怎么布局childview就很容易了。
接下来开始写代码,先取个比较接地气的名字,就叫 StackLayoutManager
,好普通的名字,哈哈。
StackLayoutManager
继承 RecyclerView.LayoutManager
,需要重写 generateDefaultLayoutParams
方法:
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.W