RecyclerView中提供的方法解析
关于RecyclerView滑动到指定位置,它提供了scrollTo(),scrollBy(),scrollToPosition(),smoothScrollBy(),smoothScrollToPosition()方法,下面将详细解释这些方法的作用。
1.scrollTo(int x, int y)
public void scrollTo(int x, int y) {
Log.w("RecyclerView", "RecyclerView does not support scrolling to an absolute position. Use scrollToPosition instead");
}
该方法在View中的意思是滑动到绝对位置,比如当前位置为(10,10),通过scrollTo(30,30),最后的位置为(30,30)。但从上面scrollTo方法源代码中可以看出,RecyclerView并不支持滑动到绝对位置,因为该方法为空实现,而是使用scrollToPosition(position)方法来代替。
2.scrollBy(int x, int y)
public void scrollBy(int x, int y) {
if(this.mLayout == null) {
Log.e("RecyclerView", "Cannot scroll without a LayoutManager set. Call setLayoutManager with a non-null argument.");
} else if(!this.mLayoutFrozen) {
boolean canScrollHorizontal = this.mLayout.canScrollHorizontally();
boolean canScrollVertical = this.mLayout.canScrollVertically();
if(canScrollHorizontal || canScrollVertical) {
this.scrollByInternal(canScrollHorizontal?x:0, canScrollVertical?y:0, (MotionEvent)null);
}
}
}
该方法为滑动到相对位置,比如当前位置为(10,10),通过scrollBy(30,30),最后的位置为(40,40)。在该方法中,首先会判断mLayout是否为空,mLayout就是RecyclerView持有的LayoutManager,然后再判断RecyclerView是横向还是垂直滑动,如果是横向滑动则取(x,0)传递到scrollByInternal方法中,反之垂直则取(0,y)。
在scrollByInternal方法中有几行滑动关键的代码:
if(x != 0) {
consumedX = this.mLayout.scrollHorizontallyBy(x, this.mRecycler, this.mState);
unconsumedX = x - consumedX;
}
if(y != 0) {
consumedY = this.mLayout.scrollVerticallyBy(y, this.mRecycler, this.mState);
unconsumedY = y - consumedY;
}
//......
if(!this.mItemDecorations.isEmpty()) {
this.invalidate();//刷新View
}
上面有两个重要的值consumedX/consumedY,它们决定了滑动到的具体位置。而consumedX/consumedY是由LayoutManager中scrollHorizontallyBy或scrollVerticallyBy方法计算出来的。有此看来,RecyclerView中的scrollBy方法,实际是委托给了
LayoutManager中scrollHorizontallyBy和scrollVerticallyBy方法。但是LayoutManager并不是具体实现类,而是交给了它的子类去实现相关方法。
比如经常用到的LinearLayoutManager,下面为
LinearLayoutManager中scrollHorizontallyBy和scrollVerticallyBy方法:
public int scrollHorizontallyBy(int dx, Recycler recycler, State state) {
return this.mOrientation == 1?0:this.scrollBy(dx, recycler, state);
}
public int scrollVerticallyBy(int dy, Recycler recycler, State state) {
return this.mOrientation == 0?0:this.scrollBy(dy, recycler, state);
}
上面的方法又交给了LinearLayoutManager的scrollBy方法去处理。
由此得出它们的流程是:
RecyclerView–>scrollBy–>scrollByInternal–>LayoutManager–>
scrollHorizontallyBy/scrollVerticallyBy–>LinearLayoutManager–>scrollBy–>RecyclerView
当得到新的位置后,最后RecyclerView会调用invalidate()方法,重新执行draw过程。
3.scrollToPosition(int position)
public void scrollToPosition(int position) {
if(!this.mLayoutFrozen) {
this.stopScroll();
if(this.mLayout == null) {
Log.e("RecyclerView", "Cannot scroll to position a LayoutManager set. Call setLayoutManager with a non-null argument.");
} else {
this.mLayout.scrollToPosition(position);
this.awakenScrollBars();
}
}
}
该方法为滑动到RecyclerView中的指定位置(滑动过程中没有动画),由上面代码可以看出,实际上是委托给了LayoutManager的scrollToPosition方法,在LinearLayoutManager的scrollToPosition方法为:
public void scrollToPosition(int position) {
this.mPendingScrollPosition = position;
this.mPendingScrollPositionOffset = -2147483648;
if(this.mPendingSavedState != null) {
this.mPendingSavedState.invalidateAnchor();
}
//重新请求布局,会导致执行measure,layout,draw过程
this.requestLayout();
}
其实直接调用了requestLayout,requestLayout会导致View重新执行
measure、layout、draw过程。
4.smoothScrollBy(int dx, int dy)和smoothScrollBy(int dx, int dy, Interpolator interpolator)
public void smoothScrollBy(int dx, int dy) {
this.smoothScrollBy(dx, dy, (Interpolator)null);
}
public void smoothScrollBy(int dx, int dy, Interpolator interpolator) {
if(this.mLayout == null) {
Log.e("RecyclerView", "Cannot smooth scroll without a LayoutManager set. Call setLayoutManager with a non-null argument.");
} else if(!this.mLayoutFrozen) {
if(!this.mLayout.canScrollHorizontally()) {
dx = 0;
}
if(!this.mLayout.canScrollVertically()) {
dy = 0;
}
if(dx != 0 || dy != 0) {
this.mViewFlinger.smoothScrollBy(dx, dy, interpolator);
}
}
}
这个两个方法都是平滑滑动到相对位置。smoothScrollBy(int dx, int dy)方法间接调用的smoothScrollBy(int dx, int dy, Interpolator interpolator)方法,参数interpolator为插值器,它是一个接口,和动画使用的插值器一样。它的实现有:
常用的有LinearInterpolator(线性插值器)、 AccelerateInterpolator(加速插值器) 、DecelerateInterpolator(减速插值器)等。
在滑动之前会先通过LayoutManager来判断是横向滑动或垂直滑动,如果是横向滑动则取(dx,0)传递到scrollByInternal方法中,反之垂直则取(0,dy)。
在这个方法的最后调用了mViewFlinger的smoothScrollBy方法,
class ViewFlinger implements Runnable {
private int mLastFlingX;
private int mLastFlingY;
private OverScroller mScroller;
Interpolator mInterpolator;
private boolean mEatRunOnAnimationRequest;
private boolean mReSchedulePostAnimationCallback;
///......
public void smoothScrollBy(int dx, int dy, int duration) {
this.smoothScrollBy(dx, dy, duration, RecyclerView.sQuinticInterpolator);
}
public void smoothScrollBy(int dx, int dy, Interpolator interpolator) {
this.smoothScrollBy(dx, dy, this.computeScrollDuration(dx, dy, 0, 0), interpolator == null?RecyclerView.sQuinticInterpolator:interpolator);
}
public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) {
if(this.mInterpolator != interpolator) {
this.mInterpolator = interpolator;
this.mScroller = new OverScroller(RecyclerView.this.getContext(), interpolator);
}
RecyclerView.this.setScrollState(2);
this.mLastFlingX = this.mLastFlingY = 0;
this.mScroller.startScroll(0, 0, dx, dy, duration);
if(VERSION.SDK_INT < 23) {
this.mScroller.computeScrollOffset();
}
this.postOnAnimation();//post到主线程中去执行
}
ViewFlinger实现了Runnable接口,它是一个线程。在调用它的smoothScrollBy方法时候,会把刚才的interpolator传递进来,另外再给mScroller赋值。mScroller是一个OverScroller对像,这个类封装了滑动的能力,它可以超过滑动边界。它是新版本引入用来替换android.widget.Scroller类的。
OverScroller和Scroller一样,先通过调用startScroll(int startX, int startY, int dx, int dy, int duration)方法来设置要滑动的新位置相关参数:
- startX和startY表示滑动的起点位置,当startX为正值,View内容会向左边滑动,反之向右边滑动;当startY为正值,View内容会向上边滑动,反之向下边滑动。
- dx和dy表示滑动的距离,当dx为正值,View内容会向左边滑动,反之向右边滑动;当dy为正值,View内容会向上边滑动,反之向下边滑动。
- duration表示的是滑动时间,整个滑动过程完成所需要的时间。
当设置新位置参数后,再调用computeScrollOffset方法:
public boolean computeScrollOffset() {
if (isFinished()) {
return false;
}
switch (mMode) {
case SCROLL_MODE:
long time = AnimationUtils.currentAnimationTimeMillis();
// Any scroller can be used for time, since they were started
// together in scroll mode. We use X here.
final long elapsedTime = time - mScrollerX.mStartTime;
final int duration = mScrollerX.mDuration;
if (elapsedTime < duration) {
final float q = mInterpolator.getInterpolation(elapsedTime / (float) duration);
//根据时间的流逝来计算当前的位置
mScrollerX.updateScroll(q);
mScrollerY.updateScroll(q);
} else {
abortAnimation();
}
break;
//......
void updateScroll(float q) {
mCurrentPosition = mStart + Math.round(q * (mFinal - mStart));
该方法主要是根据时间的流逝来计算当前的位置(mCurrentPosition),当返回true时,表示滑动还未结束,否则结束。
最后的postOnAnimation()方法,是将ViewFlinger线程post到主线程中去执行,当开始运行ViewFlinger线程时,会调用ViewFlinger的run()方法:
if(!RecyclerView.this.mItemDecorations.isEmpty()) {
RecyclerView.this.invalidate();
}
RecyclerView调用invalidate()方法,会重新执行draw过程,并不会执行measure和layout过程。
最后,就是通过这样不断的计算新位置然后重绘实现了平滑滑动的效果。
5.smoothScrollToPosition(int position)
if(!this.mLayoutFrozen) {
if(this.mLayout == null) {
Log.e("RecyclerView", "Cannot smooth scroll without a LayoutManager set. Call setLayoutManager with a non-null argument.");
} else {
this.mLayout.smoothScrollToPosition(this, this.mState, position);
}
}
}
该方法主要是平滑滑动到指定位置。其实也是交给LayoutManager来处理的。下面为LinearLayoutManager中的smoothScrollToPosition方法:
public void smoothScrollToPosition(RecyclerView recyclerView, State state, int position) {
LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext());
linearSmoothScroller.setTargetPosition(position);
this.startSmoothScroll(linearSmoothScroller);
}
public void startSmoothScroll(RecyclerView.SmoothScroller smoothScroller) {
if(this.mSmoothScroller != null && smoothScroller != this.mSmoothScroller && this.mSmoothScroller.isRunning()) {
this.mSmoothScroller.stop();
}
this.mSmoothScroller = smoothScroller;
this.mSmoothScroller.start(this.mRecyclerView, this);
}
在smoothScrollToPosition方法中,主要是通过
LinearSmoothScroller来实现平滑滑动的。LinearSmoothScroller继承自SmoothScroller类,SmoothScroller是平滑滑动的基类,它不仅提供了处理目标View位置的基本跟踪,还提供了触发滑动的方法。
LinearSmoothScroller使用了LinearInterpolator和DecelerateInterpolator两个插值器,在没有滑动到RecyclerView的目标position前,也就是目标position并不可见之前,使用LinearInterpolator来实现平滑滑动,在目标position可见的时候,再使用DecelerateInterpolator来实现平滑滑动。
最后调用SmoothScroller的start方法:
void start(RecyclerView recyclerView, RecyclerView.LayoutManager layoutManager) {
this.mRecyclerView = recyclerView;
this.mLayoutManager = layoutManager;
if(this.mTargetPosition == -1) {
throw new IllegalArgumentException("Invalid target position");
} else {
this.mRecyclerView.mState.mTargetPosition = this.mTargetPosition;
this.mRunning = true;
this.mPendingInitialRun = true;
this.mTargetView = this.findViewByPosition(this.getTargetPosition());
this.onStart();
this.mRecyclerView.mViewFlinger.postOnAnimation();//将mViewFlinger线程post到主线程中去执行
}
}
start方法中的关键执行mViewFlinger线程。
6.总结
- 虽然RecyclerView提供了scrollTo()方法,但是并没有提供scrollTo()方法的具体实现,而是使用scrollToPosition()方法来代替。
- scrollBy()和smoothScrollBy()都是通过指定相对位置来滑动,而scrollToPosition()和smoothScrollToPosition()方法是通过RecyclerView中的position来滑动,其中smoothScrollBy()和smoothScrollToPosition()方法实现了平滑滑动的效果。
- 虽然smoothScrollBy()和smoothScrollToPosition()方法都实现了平滑滑动的效果,但是它们的实现方式是不一样的。smoothScrollBy()方法是通过
OverScroller类实现的,而smoothScrollToPosition()是通过SmoothScroller类实现的。 - scrollBy(),scrollToPosition()和smoothScrollToPosition()方法都是通过LayoutManager来计算位置的,只有smoothScrollBy()是通过OverScroller来计算位置。
- 使用scrollBy(),smoothScrollBy()和smoothScrollToPosition()方法滑动到指定位置,只会重新执行draw过程,而scrollToPosition()方法滑动到指定位置,会重新执行measure,layout,draw过程,这将是一个消耗性能的操作,所以建议不使用。