RecyclerView的scrollToPosition和sm
2017-11-01 本文已影响0人
Frank_Kivi
在使用RecyclerView的过程中,不可避免的会遇到这样的需求,指定滚动到某个位置。通过查阅API我们知道有scrollToPosition和smoothScrollToPosition两个方法可以实现。
看方法名感觉差别不大,实际在使用过程中他们的效果完全不同。我们通过源码来分析一下。
scrollToPosition
/**
* Convenience method to scroll to a certain position.
*
* RecyclerView does not implement scrolling logic, rather forwards the call to
* {@link android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)}
* @param position Scroll to this adapter position
* @see android.support.v7.widget.RecyclerView.LayoutManager#scrollToPosition(int)
*/
public void scrollToPosition(int position) {
if (mLayoutFrozen) {
return;
}
stopScroll();
if (mLayout == null) {
Log.e(TAG, "Cannot scroll to position a LayoutManager set. "
+ "Call setLayoutManager with a non-null argument.");
return;
}
<!--调用了LayoutManager的方法-->
mLayout.scrollToPosition(position);
awakenScrollBars();
}
/**
* <p>Scroll the RecyclerView to make the position visible.</p>
*
* <p>RecyclerView will scroll the minimum amount that is necessary to make the
* target position visible. If you are looking for a similar behavior to
* {@link android.widget.ListView#setSelection(int)} or
* {@link android.widget.ListView#setSelectionFromTop(int, int)}, use
* {@link #scrollToPositionWithOffset(int, int)}.</p>
*
* <p>Note that scroll position change will not be reflected until the next layout call.</p>
*
* @param position Scroll to this adapter position
* @see #scrollToPositionWithOffset(int, int)
*/
<!--注释说的很有意思,大概是两点,1是滚动的距离是使position可见的最小值。2是位置的变化需要在下次布局完成的时候才能显现出来。-->
@Override
public void scrollToPosition(int position) {
mPendingScrollPosition = position;
mPendingScrollPositionOffset = INVALID_OFFSET;
if (mPendingSavedState != null) {
mPendingSavedState.invalidateAnchor();
}
<!--这个方法是关键。-->
requestLayout();
}
/**
* Calls {@code RecyclerView#requestLayout} on the underlying RecyclerView
*/
public void requestLayout() {
if (mRecyclerView != null) {
mRecyclerView.requestLayout();
}
}
@Override
public void requestLayout() {
if (mEatRequestLayout == 0 && !mLayoutFrozen) {
super.requestLayout();
} else {
mLayoutRequestEaten = true;
}
}
最后就是调用view的requestLayout了。我们在前边的View源码分析上(View的生命周期,源码解析(上))里边提到过,requestLayout是一个非常霸道的方法,会把UI线程全部锁死直到自己完成才会再次解锁。所以这个方法经常调用会出现卡顿。
smoothScrollToPosition
/**
* Starts a smooth scroll to an adapter position.
* <p>
* To support smooth scrolling, you must override
* {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} and create a
* {@link SmoothScroller}.
* <p>
* {@link LayoutManager} is responsible for creating the actual scroll action. If you want to
* provide a custom smooth scroll logic, override
* {@link LayoutManager#smoothScrollToPosition(RecyclerView, State, int)} in your
* LayoutManager.
*
* @param position The adapter position to scroll to
* @see LayoutManager#smoothScrollToPosition(RecyclerView, State, int)
*/
public void smoothScrollToPosition(int position) {
if (mLayoutFrozen) {
return;
}
if (mLayout == null) {
Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. "
+ "Call setLayoutManager with a non-null argument.");
return;
}
<!--也是调用LayoutManager的方法-->
mLayout.smoothScrollToPosition(this, mState, position);
}
@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state,
int position) {
LinearSmoothScroller linearSmoothScroller =
new LinearSmoothScroller(recyclerView.getContext());
linearSmoothScroller.setTargetPosition(position);
<!--新建了一个Scroller对象,然后调用startSmoothScroll-->
startSmoothScroll(linearSmoothScroller);
}
/**
* <p>Starts a smooth scroll using the provided SmoothScroller.</p>
* <p>Calling this method will cancel any previous smooth scroll request.</p>
* @param smoothScroller Instance which defines how smooth scroll should be animated
*/
public void startSmoothScroll(SmoothScroller smoothScroller) {
if (mSmoothScroller != null && smoothScroller != mSmoothScroller
&& mSmoothScroller.isRunning()) {
mSmoothScroller.stop();
}
mSmoothScroller = smoothScroller;
<!--调用了Scroller的start方法-->
mSmoothScroller.start(mRecyclerView, this);
}
void start(RecyclerView recyclerView, LayoutManager layoutManager) {
mRecyclerView = recyclerView;
mLayoutManager = layoutManager;
if (mTargetPosition == RecyclerView.NO_POSITION) {
throw new IllegalArgumentException("Invalid target position");
}
mRecyclerView.mState.mTargetPosition = mTargetPosition;
mRunning = true;
mPendingInitialRun = true;
mTargetView = findViewByPosition(getTargetPosition());
onStart();
<!--最后是关键,mViewFlinger其实也是一个Scroller,但是它是通过滚动播放动画的方式来实现的-->
mRecyclerView.mViewFlinger.postOnAnimation();
}
还可以补充一下,smoothScrollToPosition 其实可以理解成一个模拟的滑动操作,会回调那个滑动监听的回调方法。而 scrollToPosition 相当于直接把你想要的东西再重绘到界面上。
最后提醒两点:
1,smoothScrollToPosition和scrollToPosition都一样,并不是说让position处于第一个位置,而只是保证它可见。AbsListView 的对应方法也是一样的效果。
2,scrollToPosition的性能差了很多,不太建议使用,这也许就是为什么 AbsListView 只有一个 smoothScrollToPosition方法的原因。