昨天寫了RecyclerView進階之層疊列表(上),不過只實現了基本的效果。今天看到不少人點贊,因而我趁熱打鐵,把這個控件寫完成吧。沒看過前篇的同窗,先移步熟悉下吧。下篇的主要內容就是實現層疊列表邊緣的層疊動畫和RecyclerView的回收複用,也是這個控件實現的難點所在。java
關於這個,先上圖吧:git
對比系統通知欄的滑動效果,細心的同窗就發現了,不管是頂部仍是底部,ItemView靠近邊緣的時候並無變慢,從而產生一個多層層疊的效果,因此咱們先來實現這個效果。 先定義兩個動畫參數:private @FloatRange(from = 0.01, to = 1.0)
float edgePercent = 0.5f;//觸發邊緣動畫距離百分比
private @IntRange(from = 1)
int slowTimes = 5;//到達此距離後放慢倍數
複製代碼
而後在滾動時從新佈局列表各個ItemView位置的方法中處理動畫,關鍵點是在ItemView到達邊界臨界點時,將本來的偏移值除以一個倍數,使其移動速度變慢:github
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) {
//到達邊界觸發距離
int edgeDist = (int) (displayHeight - height * edgePercent);
//到達邊界後速度放慢到原來5分之一,計算出實際須要的底部位置
int bottom = edgeDist + (bottomOffset - edgeDist) / slowTimes;
//固然這個位置不能超過底部
bottom=Math.min(bottom,displayHeight);
realBottomOffset = bottom;
}
} else {
// 若是是最後一個就不須要動畫了,由於已經在底部了
realBottomOffset = totalHeight > displayHeight ? displayHeight : totalHeight;
}
}
}
複製代碼
上面是底部邊界動畫的處理,頂部邊界動畫的處理也是同樣的。如今咱們已經能夠看到有層疊的效果了:bash
橫向的看起來效果也不錯:這個功能若是沒有實現的話,就對不起RecyclerView的名號了。下面都只貼了核心代碼:ide
@Override
public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
//在每次觸發滾動的時候,把全部View從RecyclerView移除掉,這樣recyclerView就沒有itemview了
detachAndScrapAttachedViews(recycler);
//重新佈局位置、顯示View
addAndLayoutViewVertical(recycler, state, verticalScrollOffset);
return tempDy;
}
複製代碼
上面已經吧全部的itemView移除了,如今遍歷全部的itemView,判斷是否在屏幕中顯示,是的話就從新添加進去,不是的話就跳過。由於有邊緣層疊動畫,咱們放慢了速度,可是比較的還是原來滾動的距離,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);
}
複製代碼
打印日誌看下結果,即便把列表總數加到1000項,recyclerView中的itemView數量也始終維持在實際顯示數量附近,順便也解決了由於Itemview沒有回收,兩邊陰影層疊在一塊兒造成黑邊的問題:post
到這已經基本實現了咱們所指望的功能。可是和系統自帶的三個layoutManager相比,還欠缺不少基本的對外操做方法,因此咱們來依照LinearLayoutManager
實現幾個同名方法:動畫
@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);
}
複製代碼
requestLayout()
方法會調用onLayoutChildren()
,offsetUseful
用來標識onLayoutChildren()
時是否將參數重置ui
@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();
}
複製代碼
好了至於別的方法,你們若是感興趣的話本身去看源碼吧!這個控件還有待完善的地方,我會持續補充,歡迎你們提Issues,或者給個star!this