LayoutState是LinearLayoutManager中管理布局的对象,记录了布局的方向,位置等信息
static class LayoutState {
static final String TAG = "LLM#LayoutState";
//布局方向,新增的子view放在前
static final int LAYOUT_START = -1;
//布局方向,新增的子view放在后
static final int LAYOUT_END = 1;
static final int INVALID_LAYOUT = Integer.MIN_VALUE;
//取数据的方向,从头往后
static final int ITEM_DIRECTION_HEAD = -1;
//取数据的方向,从尾部往前
static final int ITEM_DIRECTION_TAIL = 1;
static final int SCROLLING_OFFSET_NaN = Integer.MIN_VALUE;
//布局过程中是否回收多余的子控件
boolean mRecycle = true;
//开始布局的位置,根据布局方向,距离参照物的左边界或者右边界
int mOffset;
//需要填充的距离,大于0才需要进行填充
int mAvailable;
//当前布局的子view在数据源中的位置
int mCurrentPosition;
//从源数据中取数据的方向,从头或者尾
int mItemDirection;
//布局的方向,往前或者往后
int mLayoutDirection;
//可滑动的距离,使得不需要创建新的子view
int mScrollingOffset;
//跟pre-layout有关
int mExtraFillSpace = 0;
//回收子view时,需要额外去掉的距离
int mNoRecycleSpace = 0;
//是否是pre-layout
boolean mIsPreLayout = false;
//scrollBy中实际滑动的距离
int mLastScrollDelta;
//来自Recycler中的mUnmodifiableAttachedScrap
List<RecyclerView.ViewHolder> mScrapList = null;
//只要源数据还有,就可以一直布局下去,不考虑布局上的空间限制
boolean mInfinite;
}
手势滑动时的布局
没有创建新的子View,滑动已有的子View
即滑动的距离不足以使最后一个子View完全可见
以下图为例:
RV宽度200px,其中有3个子view,位置对应源数据分别是0,1,2
每个子view的宽度为40px,最后一个子view超出边界60px
RV及子view均未设置padding和margin
从右往左滑动40px
1.调用scrollBy()方法:
其中delta=40
得到layoutDirection=LayoutState.LAYOUT_END
1.1.调用updateLayoutState()方法:
重新计算mLayoutState的属性:
mLayoutState.mInfinite=false
mLayoutState.mLayoutDirection=LayoutState.LAYOUT_END
1.1.1.调用calculateExtraLayoutSpace()
计算extraForStart和extraForEnd,如果没有调用smoothScroll都为0
1.2.继续计算mLayoutState的各个属性值
mLayoutState.mExtraFillSpace=extraForEnd=0
mLayoutState.mNoRecycleSpace=0
由于layoutToEnd为true,再计算
mLayoutState.mExtraFillSpace+=mRecyclerView.getPaddingRight(),此处为0
//getChildClosestToEnd()得到最右的child
mLayoutState.mItemDirection=LayoutState.ITEM_DIRECTION_TAIL
mLayoutState.mCurrentPosition=开始布局的子view的位置=2+1=3
mLayoutState.mOffset=最右的子view的右边界=260px
scrollingOffset=最右侧子view的右边界-RecyclerView的右边界=260-200=60px
mLayoutState.mAvailable=手势滑动的距离=40px
因为canUseExistingSpace=true
所以mLayoutState.mAvaliable -= scrollingOffset=40-60=-20px
mLayoutState.mScrollingOffset=scrollingOffset=60
2.consumed = mLayoutState.mScrollingOffset + fill()
2.1.调用fill()方法进行填充,并返回填充的长度(添加子view消耗的空间):
start=mLayoutState.mAvailable=-20px
mLayoutState.mScrollingOffset+=mAvailable=60-20=40px
2.1.1.调用recycleByLayoutState()回收会移出RecyclerView的子view
回收了子view中right大于mScrollingOffset-mNoRecycleSpace = 40
第一个就满足,但是不会被回收,因为start=end
2.2.继续填充
计算剩余空间remainingSpace=mLayoutState.mAvailable+mLayoutState.mExtraFillSpace=-20px
不满足while条件,所以不会调用layoutChunk(),即没有增加子view,
并返回的start-mAvailable是消耗的空间,此处为0px
3.计算需要水平滑动的距离
consumed=60+0=60px
absDelta=40px
scrolled=40px,取实际增加的距离和手势滑动的距离的较小值,考虑方向
所以调用mOrientationHelper.offsetChildren(-scrolled);滑动40px
总结:水平滑动时,会计算滑动的距离是否超过了同方向上子view完全展示的距离,如果不超过,说明不需要增加新的子view,只需要控制已有的子view平移即可。
滑动距离较大,会把第3个子view完全展示出来,且会出现第4个
以下图为例:
RV宽200px,item每个都是100px,
当前第3个item滑动到只有10px不可见,
从右往左滑动14px
1.调用scrollBy()方法:
其中delta=14px
得到layoutDirection=LayoutState.LAYOUT_END
1.1.调用updateLayoutState()方法:
重新计算mLayoutState的属性:
mLayoutState.mInfinite=false
mLayoutState.mLayoutDirection=LayoutState.LAYOUT_END
1.1.1.调用calculateExtraLayoutSpace()
计算extraForStart和extraForEnd,如果没有调用smoothScroll都为0
1.2.继续计算mLayoutState的各个属性值
mLayoutState.mExtraFillSpace=extraForEnd=0
mLayoutState.mNoRecycleSpace=0
由于layoutToEnd为true,再计算
mLayoutState.mExtraFillSpace+=mRecyclerView.getPaddingRight(),此处为0
//getChildClosestToEnd()得到最右的child
mLayoutState.mItemDirection=LayoutState.ITEM_DIRECTION_TAIL
mLayoutState.mCurrentPosition=开始布局的子view的位置=2+1=3
mLayoutState.mOffset=最右的子view的右边界=210px
scrollingOffset=最右侧子view的右边界-RecyclerView的右边界=210-200=10px
mLayoutState.mAvailable=手势滑动的距离=14px
因为canUseExistingSpace=true
所以mLayoutState.mAvaliable -= scrollingOffset=14-10=4px
mLayoutState.mScrollingOffset=scrollingOffset=10px
2.consumed = mLayoutState.mScrollingOffset + fill()
2.1.调用fill()方法进行填充,并返回填充的长度(添加子view消耗的空间):
start=mLayoutState.mAvailable=4px
2.1.1.调用recycleByLayoutState()回收会移出RecyclerView的子view
回收了子view中right大于mScrollingOffset-mNoRecycleSpace =10px
当前的子view中,第二个就满足,所以回收了第一个子view
2.2.继续填充
计算剩余空间remainingSpace=mLayoutState.mAvailable+mLayoutState.mExtraFillSpace=4px
2.2.1.调用layoutChunk(),在尾部增加一个子view,并把它放在最后
计算新增子view的位置:
left=mLayoutState.mOffset=210px
right=mLayoutState.mOffset+子view的宽=210px+100px=310px
2.3计算剩余空间
mLayoutState.mAvailable -= 宽 = -96px
remainingSpace -= 宽 = -96px
mLayoutState.mScrollingOffset += 宽 = 10 + 100 = 110px
由于mLayoutState.mAvaliable<0,所以mLayoutState.mScrollingOffset += mLayoutState.mAvailable = 110-96=14px
由于剩余空间小于0,不继续填充,并返回的start-mLayoutState.mAvailable=4-(-96)=100px
3.计算需要水平滑动的距离
consumed=10+100=110px
absDelta=14px
scrolled=14px,取实际增加的距离和手势滑动的距离的较小值,考虑方向
所以调用mOrientationHelper.offsetChildren(-scrolled);滑动14px
总结:水平滑动时,会计算滑动的距离是否超过了同方向上子view完全展示的距离,如果超过,说明需要增加新的子view,需要计算增加了子view后的滑动距离。
滑动到边界,无法继续滑动
以下图为例
数据源只有3条,
RV宽200px,item每个都是100px,
当前第3个item滑动到只有8px不可见
从右往左滑动24px
1.调用scrollBy()方法:
其中delta=24px
得到layoutDirection=LayoutState.LAYOUT_END
1.1.调用updateLayoutState()方法:
重新计算mLayoutState的属性:
mLayoutState.mInfinite=false
mLayoutState.mLayoutDirection=LayoutState.LAYOUT_END
1.1.1.调用calculateExtraLayoutSpace()
计算extraForStart和extraForEnd,如果没有调用smoothScroll都为0
1.2.继续计算mLayoutState的各个属性值
mLayoutState.mExtraFillSpace=extraForEnd=0
mLayoutState.mNoRecycleSpace=0
由于layoutToEnd为true,再计算
mLayoutState.mExtraFillSpace+=mRecyclerView.getPaddingRight(),此处为0
//getChildClosestToEnd()得到最右的child
mLayoutState.mItemDirection=LayoutState.ITEM_DIRECTION_TAIL
mLayoutState.mCurrentPosition=开始布局的子view的位置=2+1=3
mLayoutState.mOffset=最右的子view的右边界=208px
scrollingOffset=最右侧子view的右边界-RecyclerView的右边界=208-200=8px
mLayoutState.mAvailable=手势滑动的距离=24px
因为canUseExistingSpace=true
所以mLayoutState.mAvaliable -= scrollingOffset=24-8=16px
mLayoutState.mScrollingOffset=scrollingOffset=8px
2.consumed = mLayoutState.mScrollingOffset + fill()
2.1.调用fill()方法进行填充,并返回填充的长度(添加子view消耗的空间):
start=mLayoutState.mAvailable=16px
2.1.1.调用recycleByLayoutState()回收会移出RecyclerView的子view
回收了子view中right大于mScrollingOffset-mNoRecycleSpace =8px
当前的子view中,第二个就满足,所以回收了第一个子view
2.2.继续填充
计算剩余空间remainingSpace=mLayoutState.mAvailable+mLayoutState.mExtraFillSpace=16px
由于没有更多的数据,所以不需要调用layoutChunk()进行填充子view
并返回的start-mLayoutState.mAvailable=0px,即没有新增子view进行填充
3.计算需要水平滑动的距离
consumed=8+0=8px
absDelta=24px
scrolled=8px,取实际增加的距离和手势滑动的距离的较小值,考虑方向
所以调用mOrientationHelper.offsetChildren(-scrolled);滑动8px
总结:水平滑动时,会计算滑动的距离是否超过了同方向上子view完全展示的距离,如果超过,说明需要增加新的子view,但是如果没有数据了,则只需要把不可见的那部分子view滑到到完全可见。