StaggeredGridView 實現分析--滑動處理(二)計算、移動、回收,以及填充

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;
    }



總結:

將"首次填充」過程分析和 上下滑動處理過程結合起來,就能明白StaggeredGridView的具體工做過程了。

相關文章
相關標籤/搜索