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滑到到完全可見。