moveTheChildren:java
moveTheChildren 首先根據 incrementalDeltaY 計算滑動後有哪些child變爲不可見狀態,而後將這些child view 加入recycleBin,而後detach掉,並將剩餘的child view挪動到新的位置;ide
最後,滑動後若是有空白區域(上滑時下方可能有空白區域,下滑時相反)再經過fillGap方法填補。動畫
計算滑動後上、下部的空白區域時須要用到 getFirstChildTop, getLastChildBottom 方法, 後面咱們還會看到getChildTop、getChildBottom、 getChildLeft、getChildRight等方法,經過這些方法獲得的值能夠肯定新的view方法在什麼位置。StaggeredGridView正是經過覆蓋這些方法實現瀑布流的邏輯的。spa
1, 向上滑動時回收頂部的view的過程:.net
第一次滑動時, mFirstPosition 是0,
code
mFirstPosition 對應的是Adapter提供的數據中第一項被顯示的, 它對應的view確定是 某一列 view的第一個,可是不能肯定具體是哪一列 , 須要用AbsListView.LayoutParams的 position 變量確認。blog
private boolean moveTheChildren(int deltaY, int incrementalDeltaY) { if (!hasChildren()) return true; //若是隻有一列的話,就是第0個child的top值, 該child部分可見時,其top小於0 ; 和下面的spaceAbove有關 final int highestChildTop = getHighestChildTop(); //若是隻有一列,就是最後一個child的bottom值, 該child部分可見時,其bottom大於parent的height; 和下面的spaceBelow有關 final int lowestChildBottom = getLowestChildBottom(); int effectivePaddingTop = 0; int effectivePaddingBottom = 0; if (mClipToPadding) { effectivePaddingTop = getListPaddingTop(); effectivePaddingBottom = getListPaddingBottom(); } final int gridHeight = getHeight(); final int spaceAbove = effectivePaddingTop - getFirstChildTop(); //getFirstChildTop() got the lowest column top final int end = gridHeight - effectivePaddingBottom; final int spaceBelow = getLastChildBottom() - end; // getLastChildBottom() got the lowest column bottom final int height = gridHeight - getListPaddingBottom() - getListPaddingTop(); if (incrementalDeltaY < 0) { incrementalDeltaY = Math.max(-(height - 1), incrementalDeltaY); } else { incrementalDeltaY = Math.min(height - 1, incrementalDeltaY); } final int firstPosition = mFirstPosition; int maxTop = getListPaddingTop(); int maxBottom = gridHeight - getListPaddingBottom(); int childCount = getChildCount(); final boolean cannotScrollDown = (firstPosition == 0 && highestChildTop >= maxTop && incrementalDeltaY >= 0); final boolean cannotScrollUp = (firstPosition + childCount == mItemCount && lowestChildBottom <= maxBottom && incrementalDeltaY <= 0); if (cannotScrollDown) { return incrementalDeltaY != 0; } if (cannotScrollUp) { return incrementalDeltaY != 0; } // isDown 爲true表示向上滑動,這裏好像命名有誤!! final boolean isDown = incrementalDeltaY < 0; final int headerViewsCount = getHeaderViewsCount(); final int footerViewsStart = mItemCount - getFooterViewsCount(); int start = 0; int count = 0; // 向上滑 if (isDown) { int newTop = -incrementalDeltaY; if (mClipToPadding) { newTop += getListPaddingTop(); } //計算每一個child view滑動後是否可見, //不可見就回收掉(放入recycleBin,可是並無從 parent detach掉);從上往下遍歷 for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getBottom() >= newTop) { break; } else { count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { mRecycleBin.addScrapView(child, position); } } } } //向下滑 else { int bottom = gridHeight - incrementalDeltaY; if (mClipToPadding) { bottom -= getListPaddingBottom(); } //計算每一個child view滑動後是否可見, //不可見就回收掉(放入recycleBin,可是並無從 parent detach掉);從下往上遍歷 for (int i = childCount - 1; i >= 0; i--) { final View child = getChildAt(i); if (child.getTop() <= bottom) { break; } else { start = i; count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { mRecycleBin.addScrapView(child, position); } } } } mBlockLayoutRequests = true; // 從 listView中移除 if (count > 0) { detachViewsFromParent(start, count); //skippedscrap是指因爲addScrap時動畫未結束而處於Transient狀態的view mRecycleBin.removeSkippedScrap(); onChildrenDetached(start, count); // 更新記錄數據!! } // invalidate before moving the children to avoid unnecessary invalidate // calls to bubble up from the children all the way to the top if (!awakenScrollBars()) { invalidate(); } // 若是是上滑動,剩餘view向上挪動 offsetChildrenTopAndBottom(incrementalDeltaY); if (isDown) { mFirstPosition += count; } //若是有空白,填充 final int absIncrementalDeltaY = Math.abs(incrementalDeltaY); if (spaceAbove < absIncrementalDeltaY || spaceBelow < absIncrementalDeltaY) { fillGap(isDown); } // TODO : touch mode selector handling mBlockLayoutRequests = false; invokeOnItemScrollListener(); return false; }
getFirstChildTop: 因爲listView剛填充完,因此 lowestpositionedTop 是 0ip
//StaggeredGridView.java @Override protected int getFirstChildTop() { if (isHeaderOrFooter(mFirstPosition)) { return super.getFirstChildTop(); } return getLowestPositionedTop(); }
在staggeredGridView的 onChildrenDetached(..) 方法中對記錄記錄數據進行了更新:rem
//StaggeredGridView.java @Override protected void onChildrenDetached(final int start, final int count) { super.onChildrenDetached(start, count); // go through our remaining views and sync the top and bottom stash. // Repair the top and bottom column boundaries from the views we still have Arrays.fill(mColumnTops, Integer.MAX_VALUE); Arrays.fill(mColumnBottoms, 0); for (int i = 0; i < getChildCount(); i++) { final View child = getChildAt(i); if (child != null) { final LayoutParams childParams = (LayoutParams) child.getLayoutParams(); if (childParams.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER && childParams instanceof GridLayoutParams) { GridLayoutParams layoutParams = (GridLayoutParams) childParams; int column = layoutParams.column; int position = layoutParams.position; final int childTop = child.getTop(); if (childTop < mColumnTops[column]) { mColumnTops[column] = childTop - getChildTopMargin(position); } final int childBottom = child.getBottom(); if (childBottom > mColumnBottoms[column]) { mColumnBottoms[column] = childBottom + getChildBottomMargin(); } } else { // the header and footer here final int childTop = child.getTop(); final int childBottom = child.getBottom(); for (int col = 0; col < mColumnCount; col++) { if (childTop < mColumnTops[col]) { mColumnTops[col] = childTop; } if (childBottom > mColumnBottoms[col]) { mColumnBottoms[col] = childBottom; } } } } } }
2, 下面看填補空白區域的方法:get
fillGap最終由fillDown 和 fillUp方法完成,在「首次填充」分析中,咱們對 fillDown方法進行過介紹,它會用child view將剩餘空間向下填滿爲止。
protected void fillGap(boolean down) { final int count = getChildCount(); if (down) { int itemPos = mFirstPosition + count; final int startOffset = getChildTop(itemPos); // 第 itemPos個數據項對應的view應處的top處置 fillDown(position, startOffset); } else { int position = mFirstPosition - 1; final int startOffset = getChildBottom(position); fillUp(position, startOffset); } adjustViewsAfterFillGap(down); }
3, 對於向上滑動的過程,一樣有view的回收、移動和記錄數據的更新, 最後經過fillUp方法對上部空白進行填充。 填充邏輯與向下填充是對稱的, 從top位置最大的(視覺上是最低的)列開始。
邏輯上,每添加一個view就將mFirstPosition的值減小1 。
private View fillUp(int pos, int nextBottom) { View selectedView = null; int end = mClipToPadding ? getListPaddingTop() : 0; while ((nextBottom > end || hasSpaceUp()) && pos >= 0) { makeAndAddView(pos, nextBottom, false, false); pos--; nextBottom = getNextChildUpsBottom(pos); } mFirstPosition = pos + 1; return selectedView; }
總結: