前言
昨天寫了RecyclerView進(jìn)階之層疊列表(上)先慷,不過只實現(xiàn)了基本的效果饮笛。今天看到很多人點贊,于是我趁熱打鐵论熙,把這個控件寫完成吧福青。沒看過前篇的同學(xué),先移步熟悉下吧脓诡。下篇的主要內(nèi)容就是實現(xiàn)層疊列表邊緣的層疊動畫和RecyclerView的回收復(fù)用无午,也是這個控件實現(xiàn)的難點所在。
層疊動畫
關(guān)于這個祝谚,先上圖吧:
對比系統(tǒng)通知欄的滑動效果宪迟,細(xì)心的同學(xué)就發(fā)現(xiàn)了,無論是頂部還是底部交惯,ItemView靠近邊緣的時候并沒有變慢次泽,從而產(chǎn)生一個多層層疊的效果,所以我們先來實現(xiàn)這個效果商玫。
先定義兩個動畫參數(shù):
private @FloatRange(from = 0.01, to = 1.0)
float edgePercent = 0.5f;//觸發(fā)邊緣動畫距離百分比
private @IntRange(from = 1)
int slowTimes = 5;//到達(dá)此距離后放慢倍數(shù)
然后在滾動時重新布局列表各個ItemView位置的方法中處理動畫箕憾,關(guān)鍵點是在ItemView到達(dá)邊界臨界點時,將原本的偏移值除以一個倍數(shù)拳昌,使其移動速度變慢:
private void addAndLayoutViewVertical(RecyclerView.Recycler recycler, RecyclerView.State state, int offset) {
//反向遍歷Recycler中保存的View取出來
for (int i = itemCount - 1; i >= 0; i--) {
Rect mTmpRect = allItemRects.get(i);
int bottomOffset = mTmpRect.bottom - offset;
int topOffset = mTmpRect.top - offset;
if (i != itemCount - 1) {//除最后一個外的底部慢速動畫
if (displayHeight - bottomOffset <= height * edgePercent) {
//到達(dá)邊界觸發(fā)距離
int edgeDist = (int) (displayHeight - height * edgePercent);
//到達(dá)邊界后速度放慢到原來5分之一袭异,計算出實際需要的底部位置
int bottom = edgeDist + (bottomOffset - edgeDist) / slowTimes;
//當(dāng)然這個位置不能超過底部
bottom=Math.min(bottom,displayHeight);
realBottomOffset = bottom;
}
} else {
// 如果是最后一個就不需要動畫了,因為已經(jīng)在底部了
realBottomOffset = totalHeight > displayHeight ? displayHeight : totalHeight;
}
}
}
上面是底部邊界動畫的處理,頂部邊界動畫的處理也是一樣的【嫣伲現(xiàn)在我們已經(jīng)可以看到有層疊的效果了:
橫向的看起來效果也不錯:
回收復(fù)用功能
這個功能如果沒有實現(xiàn)的話御铃,就對不起RecyclerView的名號了碴里。下面都只貼了核心代碼:
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
//在每次觸發(fā)滾動的時候,把所有View從RecyclerView移除掉上真,這樣recyclerView就沒有itemview了
detachAndScrapAttachedViews(recycler);
//從新布局位置咬腋、顯示View
addAndLayoutViewVertical(recycler, state, verticalScrollOffset);
return tempDy;
}
上面已經(jīng)吧所有的itemView移除了,現(xiàn)在遍歷所有的itemView睡互,判斷是否在屏幕中顯示根竿,是的話就重新添加進(jìn)去,不是的話就跳過就珠。因為有邊緣層疊動畫寇壳,我們放慢了速度,但是比較的仍是原來滾動的距離妻怎,overFlyingDist= slowTimes * height
;
private void addAndLayoutViewVertical(RecyclerView.Recycler recycler, RecyclerView.State state, int offset) {
int itemCount = getItemCount();
if (itemCount <= 0 || state.isPreLayout()) {
return;
}
int displayHeight = getVerticalSpace();
for (int i = itemCount - 1; i >= 0; i--) {
Rect mTmpRect = allItemRects.get(i);
// 遍歷Recycler中保存的View取出來
int bottomOffset = mTmpRect.bottom - offset;
int topOffset = mTmpRect.top - offset;
boolean needAdd = true;//是否
if (bottomOffset - displayHeight >= overFlyingDist) {
//超過了底部最大距離
needAdd = false;
}
if (topOffset < -overFlyingDist && i != 0 && topOverFlying
|| topOffset < -overFlyingDist && !topOverFlying) {
//超過了頂部最大距離
needAdd = false;
}
if (needAdd) {
//開始布局
..........
}
Log.d(TAG, "childCount = " + getChildCount() + " itemCount= " + itemCount);
}
打印日志看下結(jié)果壳炎,即使把列表總數(shù)加到1000項,recyclerView中的itemView數(shù)量也始終維持在實際顯示數(shù)量附近逼侦,順便也解決了因為Itemview沒有回收匿辩,兩邊陰影層疊在一起形成黑邊的問題:
完善功能
到這已經(jīng)基本實現(xiàn)了我們所期望的功能。但是和系統(tǒng)自帶的三個layoutManager相比榛丢,還欠缺很多基本的對外操作方法铲球,所以我們來依照LinearLayoutManager
實現(xiàn)幾個同名方法:
findViewByPosition(int position)
@Override
public View findViewByPosition(int position) {
final int childCount = getChildCount();
if (childCount == 0) {
return null;
}
final int firstChild = getPosition(getChildAt(0));
final int viewPosition = position - firstChild;
if (viewPosition >= 0 && viewPosition < childCount) {
final View child = getChildAt(viewPosition);
if (getPosition(child) == position) {
return child; // in pre-layout, this may not match
}
}
return super.findViewByPosition(position);
}
findViewByPosition(int position)
requestLayout()
方法會調(diào)用onLayoutChildren()
,offsetUseful
用來標(biāo)識onLayoutChildren()
時是否將參數(shù)重置
@Override
public void scrollToPosition(int position) {
Rect mTmpRect = allItemRects.get(position);
if (mTmpRect != null) {
offsetUseful = true;
if (orientation == OrientationHelper.VERTICAL) {
verticalScrollOffset = mTmpRect.top;
} else {
horizontalScrollOffset = mTmpRect.left;
}
}
requestLayout();
}
好了至于別的方法,大家如果感興趣的話自己去看源碼吧晰赞!