最近有个需求是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坐标右移。
分析的不是很细,主要是为了理清流程,了解原理。