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坐标右移。

分析的不是很细,主要是为了理清流程,了解原理。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
抱歉,我的回答还是有误。这是因为scrollBy是LinearLayoutManager的一个非公开的方法,只能在LinearLayoutManager内部使用,不能从外部访问。因此,我们需要通过其他方式来实现RecyclerView的平滑滚动。以下是一个基于Scroller类的实现方法: ``` public void smoothScrollBy(int dx, int dy) { RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(getContext()) { @Override public PointF computeScrollVectorForPosition(int targetPosition) { return LinearLayoutManager.this.computeScrollVectorForPosition(targetPosition); } }; smoothScroller.setTargetPosition(getPositionForVelocity(dx, dy)); startSmoothScroll(smoothScroller); } protected int getPositionForVelocity(int dx, int dy) { if (getChildCount() == 0) { return 0; } final View nearestChild = findNearestChild(); if (nearestChild == null) { return 0; } final int currentPosition = getPosition(nearestChild); if (currentPosition == RecyclerView.NO_POSITION) { return 0; } final int direction = computeScrollDirectionForPosition(currentPosition, dx, dy); if (direction == RecyclerView.NO_POSITION) { return 0; } final int targetPosition = currentPosition + direction; if (targetPosition < 0 || targetPosition >= getItemCount()) { return 0; } return targetPosition; } protected int computeScrollDirectionForPosition(int targetPosition, int dx, int dy) { if (getChildCount() == 0) { return RecyclerView.NO_POSITION; } final View firstChild = getChildAt(0); if (firstChild == null) { return RecyclerView.NO_POSITION; } final int currentPosition = getPosition(firstChild); if (currentPosition == RecyclerView.NO_POSITION) { return RecyclerView.NO_POSITION; } final int direction = getScrollDirectionForPosition(currentPosition, targetPosition); if (direction == RecyclerView.NO_POSITION) { return RecyclerView.NO_POSITION; } final int distance = computeDistanceToPosition(targetPosition); if (distance == 0) { return RecyclerView.NO_POSITION; } return (int) Math.ceil((double) distance / (double) getDistancePerChild()); } protected int getDistancePerChild() { return Math.round(getChildAt(0).getHeight() * 1.0f); } protected int computeDistanceToPosition(int targetPosition) { final int targetChildIndex = targetPosition - getPosition(getChildAt(0)); return targetChildIndex * getDistancePerChild(); } protected int getScrollDirectionForPosition(int currentPosition, int targetPosition) { return targetPosition < currentPosition ? -1 : 1; } protected View findNearestChild() { final int childCount = getChildCount(); if (childCount == 0) { return null; } View nearestChild = null; int nearestDistance = Integer.MAX_VALUE; final int centerX = getWidth() / 2; final int centerY = getHeight() / 2; for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); final int distanceX = Math.abs(getLeftDecorationWidth(child) + getRightDecorationWidth(child) + child.getMeasuredWidth() / 2 - centerX); final int distanceY = Math.abs(getTopDecorationHeight(child) + getBottomDecorationHeight(child) + child.getMeasuredHeight() / 2 - centerY); final int distance = distanceX * distanceX + distanceY * distanceY; if (distance < nearestDistance) { nearestChild = child; nearestDistance = distance; } } return nearestChild; } ``` 这个实现方法是通过LinearSmoothScroller来实现的,首先创建一个LinearSmoothScroller对象,然后设置目标位置,最后启动平滑滚动。在getPositionForVelocity、computeScrollDirectionForPosition、computeDistanceToPosition和findNearestChild等函数中,实现了计算滚动距离、滚动方向、目标位置和最近的子View等功能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值