StaggeredGridView 实现分析--滑动处理(二)计算、移动、回收,以及填充

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

你可能感兴趣的:(StaggeredGridView 实现分析--滑动处理(二)计算、移动、回收,以及填充)