moveTheChildren:
moveTheChildren 首先根据 incrementalDeltaY 计算滑动后有哪些child变为不可见状态,然后将这些child view 加入recycleBin,然后detach掉,并将剩余的child view挪动到新的位置;
最后,滑动后如果有空白区域(上滑时下方可能有空白区域,下滑时相反)再通过fillGap方法填补。
计算滑动后上、下部的空白区域时需要用到 getFirstChildTop, getLastChildBottom 方法, 后面我们还会看到getChildTop、getChildBottom、 getChildLeft、getChildRight等方法,通过这些方法得到的值可以确定新的view方法在什么位置。StaggeredGridView正是通过覆盖这些方法实现瀑布流的逻辑的。
1, 向上滑动时回收顶部的view的过程:
第一次滑动时, mFirstPosition 是0,
mFirstPosition 对应的是Adapter提供的数据中第一项被显示的, 它对应的view肯定是 某一列 view的第一个,但是不能确定具体是哪一列 , 需要用AbsListView.LayoutParams的 position 变量确认。
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 是 0
//StaggeredGridView.java @Override protected int getFirstChildTop() { if (isHeaderOrFooter(mFirstPosition)) { return super.getFirstChildTop(); } return getLowestPositionedTop(); }
在staggeredGridView的 onChildrenDetached(..) 方法中对记录记录数据进行了更新:
//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, 下面看填补空白区域的方法:
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; }
总结:
将"首次填充”过程分析和 上下滑动处理过程结合起来,就能明白StaggeredGridView的具体工作过程了。