好了,下面开始正经的写它吧,
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
//位移0、没有子View 当然不移动
if (dy == 0 || getChildCount() == 0) {
return 0;
}
int realOffset = dy;//实际滑动的距离, 可能会在边界处被修复
//边界修复代码
if (mVerticalOffset + realOffset < 0) {//上边界
realOffset = -mVerticalOffset;
} else if (realOffset > 0) {//下边界
//利用最后一个子View比较修正
View lastChild = getChildAt(getChildCount() - 1);
if (getPosition(lastChild) == getItemCount() - 1) {
int gap = getHeight() - getPaddingBottom() - getDecoratedBottom(lastChild);
if (gap > 0) {
realOffset = -gap;
} else if (gap == 0) {
realOffset = 0;
} else {
realOffset = Math.min(realOffset, -gap);
}
}
}
realOffset = fill(recycler, state, realOffset);//先填充,再位移。
mVerticalOffset += realOffset;//累加实际滑动距离
offsetChildrenVertical(-realOffset);//滑动
return realOffset;
}
这里用realOffset
变量保存实际的位移,也是return 回去的值。大部分情况下它=dy。
在边界处,为了防止越界,做了一些处理,realOffset 可能不等于dy。
和别的文章不同的是,我参考了LinearLayoutManager的源码,先考虑滑动位移进行View的回收、填充(fill()
函数),然后再真正的位移这些子Item。
在fill()
的过程中
流程:
一 会先考虑到dy,回收界面上不可见的Item。
二 填充布局子View
三 判断是否将dy都消费掉了,如果消费不掉:例如滑动距离太多,屏幕上的View已经填充完了,仍有空白,那么就要修正dy给realOffset。
注意事项一:考虑滑动的方向
在填充布局子View的时候,还要考虑滑动的方向,即填充的顺序,是从头至尾填充,还是从尾至头部填充。
如果是向底部滑动,那么是顺序填充,显示底端position更大的Item。( dy>0)
如果是向顶部滑动,那么是逆序填充,显示顶端positon更小的Item。(dy<0)
注意事项二:流式布局 逆序布局子View的问题
再啰嗦最后一点,我们想象一下这个逆序填充的过程:
正序过程可以自上而下,自左向右layout 子View,每次layout之前判断当前这一行宽度+子View宽度,是否超过父控件宽度,如果超过了就另起一行。
逆序时,有两种方案:
1 利用Rect保存子View边界
正序排列时,保存每个子View的Rect,
逆序时,直接拿出来,layout。
2 逆序化
自右向左layout子View,每次layout之前判断当前这一行宽度+子View宽度,是否超过父控件宽度,
如果超过了就另起一行。并且判断最后一个子View距离父控件左边的offset,平移这一行的所有子View,较复杂,采用方案1.
(我个人认为这两个方案都不太好,希望有朋友能提出更好的方案。)
下面上码:
private SparseArray