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的具体工作过程了。