Android——RecyclerView.scrollBy源碼分析

最近有個需求是RecyclerView左右滾動時,如果焦點View超過屏幕中間就把焦點View滾到屏幕中間。實現思路爲獲取焦點View在屏幕上的座標,並根據屏幕寬度/2來計算滾動距離。代碼如下:

FocusLinearLayoutManager layoutManager = (FocusLinearLayoutManager) provincesList.getLayoutManager();
int firstVisiblePos = layoutManager.findFirstVisibleItemPosition();
View view = provincesList.getChildAt(position - firstVisiblePos);
int[] location = new int[2];
view.getLocationOnScreen(location);
int half_width = DensityUtil.getScreenWidth(this) / 2;
provincesList.scrollBy(location[0] - half_width, 0);

今天要分析的問題,並不是上面的需求如何實現,而是分析下scrollBy如何實現的。
scrollBy會執行到scrollByInternal方法中,下面是scrollByInternal的一段代碼。

 int unconsumedX = 0, unconsumedY = 0;
        int consumedX = 0, consumedY = 0;

        consumePendingUpdateOperations();
        if (mAdapter != null) {
            eatRequestLayout();
            onEnterLayoutOrScroll();
            TraceCompat.beginSection(TRACE_SCROLL_TAG);
            fillRemainingScrollValues(mState);
            if (x != 0) {
                consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState);
                unconsumedX = x - consumedX;
            }
            if (y != 0) {
                consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState);
                unconsumedY = y - consumedY;
            }
            TraceCompat.endSection();
            repositionShadowingViews();
            onExitLayoutOrScroll();
            resumeRequestLayout(false);
        }

mLayout.scrollHorizontallyBy(x, mRecycler, mState);可見,RecyclerView把滾動交給了Layoutmanager來實現,我們繼續跟進。(這裏以橫向滾動爲例)

LinearlayoutManager.scrollHorizontalBy:

    public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
            RecyclerView.State state) {
        if (mOrientation == VERTICAL) {
            return 0;
        }
        return scrollBy(dx, recycler, state);
    }

int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
        if (getChildCount() == 0 || dy == 0) {
            return 0;
        }
        mLayoutState.mRecycle = true;
        ensureLayoutState();
        final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
        final int absDy = Math.abs(dy);
        updateLayoutState(layoutDirection, absDy, true, state);
        final int consumed = mLayoutState.mScrollingOffset
                + fill(recycler, mLayoutState, state, false);
        if (consumed < 0) {
            if (DEBUG) {
                Log.d(TAG, "Don't have any more elements to scroll");
            }
            return 0;
        }
        final int scrolled = absDy > consumed ? layoutDirection * consumed : dy;
        mOrientationHelper.offsetChildren(-scrolled);
        if (DEBUG) {
            Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled);
        }
        mLayoutState.mLastScrollDelta = scrolled;
        return scrolled;
    }

scrollBy方法中mOrientationHelper.offsetChildren(-scrolled);表明了,接下要進行View的移動。

public void offsetChildren(int amount) {
	mLayoutManager.offsetChildrenHorizontal(amount);
}

由上述代碼可見,最終代碼的執行又回到了Layoutmanager。

public void offsetChildrenHorizontal(int dx) {
       if (mRecyclerView != null) {
             mRecyclerView.offsetChildrenHorizontal(dx);
        }
}

public void offsetChildrenHorizontal(int dx) {
        final int childCount = mChildHelper.getChildCount();
        for (int i = 0; i < childCount; i++) {
            mChildHelper.getChildAt(i).offsetLeftAndRight(dx);
        }
}

這裏敲黑板了!!!真想即將大白,最後的最後還是回到了View中處理

   public void offsetLeftAndRight(int offset) {
        if (offset != 0) {
            final boolean matrixIsIdentity = hasIdentityMatrix();
            if (matrixIsIdentity) {
                if (isHardwareAccelerated()) {
                    invalidateViewProperty(false, false);
                } else {
                    final ViewParent p = mParent;
                    if (p != null && mAttachInfo != null) {
                        final Rect r = mAttachInfo.mTmpInvalRect;
                        int minLeft;
                        int maxRight;
                        if (offset < 0) {
                            minLeft = mLeft + offset;
                            maxRight = mRight;
                        } else {
                            minLeft = mLeft;
                            maxRight = mRight + offset;
                        }
                        r.set(0, 0, maxRight - minLeft, mBottom - mTop);
                        p.invalidateChild(this, r);
                    }
                }
            } else {
                invalidateViewProperty(false, false);
            }
        }
    }

其中最重要的是這幾行,重新計算View的座標。

 if (offset < 0) {
	minLeft = mLeft + offset;
    maxRight = mRight;
} else {
    minLeft = mLeft;
    maxRight = mRight + offset;
}

看到這裏,想必文章開頭中RecyclerView如何計算滾動距離也不用解釋。向左滾動x座標左移,向右滾動x座標右移。

分析的不是很細,主要是爲了理清流程,瞭解原理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章