RecyclerView
if (smoothScroller != null) {if (smoothScroller.isPendingInitialRun()) {smoothScroller.onAnimation(0, 0);}if (!mReSchedulePostAnimationCallback) {smoothScroller.stop(); //stop if it does not trigger any scroll}}
最后发现,只是走了一个 onAnimation(0,0),继续走该方法。
private void onAnimation(int dx, int dy) {final RecyclerView recyclerView = mRecyclerView;if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION || recyclerView == null) {stop();}mPendingInitialRun = false;if (mTargetView != null) {//判断目标视图是否存在,如果存在则计算移动到位置需要移动的距离 if (getChildPosition(mTargetView) == mTargetPosition) {onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction);mRecyclingAction.runIfNecessary(recyclerView);stop();} else {Log.e(TAG, "Passed over target position while smooth scrolling.");mTargetView = null;}}if (mRunning) {//如果不存在,继续去找 onSeekTargetStep(dx, dy, recyclerView.mState, mRecyclingAction);boolean hadJumpTarget = mRecyclingAction.hasJumpTarget();mRecyclingAction.runIfNecessary(recyclerView);if (hadJumpTarget) {// It is not stopped so needs to be restartedif (mRunning) {mPendingInitialRun = true;recyclerView.mViewFlinger.postOnAnimation();} else {stop(); // done}}}}
在 onAnimation 方法中,判断了目标视图是否为空,大家应该还记得上文中,我们对目标视图的查找。如果当前位置不在可见范围之内,那么 mTargetView =null,就不回走对应的判断语句。继续查看 onSeekTargetStep()。
protected void onSeekTargetStep(int dx, int dy, RecyclerView.State state, Action action) {if (getChildCount() == 0) {stop();return;}//noinspection PointlessBooleanExpressionif (DEBUG && mTargetVector != null&& ((mTargetVector.x * dx < 0 || mTargetVector.y * dy < 0))) {throw new IllegalStateException("Scroll happened in the opposite direction"
" of the target. Some calculations are wrong");}mInterimTargetDx = clampApplyScroll(mInterimTargetDx, dx);mInterimTargetDy = clampApplyScroll(mInterimTargetDy, dy);
if (mInterimTargetDx == 0 && mInterimTargetDy == 0) {updateActionForInterimTarget(action);} // everything is valid, keep going
}
直接通过代码,发现并不理解改函数要做什么样的工作,这里我们只知道第一次发生滚动时,mInterimTargetDx=0 与 mInterimTargetDy =0,那么会走 updateActionForInterimTarget()方法。
protected void updateActionForInterimTarget(Action action) {// find an interim target positionPointF scrollVector = computeScrollVectorForPosition(getTargetPosition());...省略部分代码 normalize(scrollVector);mTargetVector = scrollVector;
mInterimTargetDx = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.x);mInterimTargetDy = (int) (TARGET_SEEK_SCROLL_DISTANCE_PX * scrollVector.y);
//计算需要滚动的时间, 默认滚动距离,TARGET_SEEK_SCROLL_DISTANCE_PX = 10000;final int time = calculateTimeForScrolling(TARGET_SEEK_SCROLL_DISTANCE_PX);
//为了避免在滚动的时候出现停顿,我们会跟踪 onSeekTargetStep 中的回调距离,实际上不会滚动超出实际的距离 action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO),(int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO),//这里存入的时间要比实际花费的时间大一点。(int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator);}
根据官方文档进行翻译:当目标滚动位置对应视图不在 RecyclerView 的可见范围内,该方法计算朝向该视图的方向向量并触发平滑滚动。默认滚动的距离为 12000(单位:px),(也就是说了为了滚动到目标位置,会让 Recycler 至多滚动 12000 个像素)。
既然该方法计算了时间,那么我们就看看 calculateTimeForScrolling()方法,通过方法名我们就应该了解了该方法是计算给定距离在默认速度下需要滚动的时间。
protected int calculateTimeForScrolling(int dx) {//这里对时间进行了四舍五入操作。return (int) Math.ceil(Math.abs(dx) * MILLISECONDS_PER_PX);}
其中 MILLISECONDS_PER_PX 会在 LinearSmoothScroller 初始化的时候创建。
public LinearSmoothScroller(Context context) {MILLISECONDS_PER_PX = calculateSpeedPerPixel(context.getResources().getDisplayMetrics());}
查看 calculateSpeedPerPixel()方法
private static final float MILLISECONDS_PER_INCH = 25f;// 默认为移动一英寸需要花费 25msprotected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;}
也就是说,当前滚动的速度是与屏幕的像素密度相关, 通过获取当前手机屏幕每英寸的像素密度,与每英寸移动所需要花费的时间,用每英寸移动所需要花费的时间除以像素密度就能计算出移动一个像素密度需要花费的时间。OK,既然我们已经算出了移动一个像素密度需要花费的时间,那么直接乘以像素,就能算出移动该像素所需要花费的时间了。
既然现在我们算出了时间,我们现在只用关心 Action 的 update()方法到底是干什么的就好了,
//保存关于 SmoothScroller 滑动距离信息 public static class Action {...省略代码 public void update(int dx, int dy, int duration, Interpolator interpolator) {mDx = dx;mDy = dy;mDuration = duration;mInterpolator = interpolator;mChanged = true;}}
这里我们发现 Action,只是存储关于 SmoothScroller 滑动信息的一个类,那么初始时保存了横向与竖直滑动的距离(12000px)、滑动时间,插值器。同时记录当前数据改变的状态。
现在我们已经把 Action 的 onSeekTargetStep 方法走完了,那接下来,我们继续看 Action 的 runIfNecessary()方法。
void runIfNecessary(RecyclerView recyclerView) {....省略代码 if (mChanged) {validate();if (mInterpolator == null) {if (mDuration == UNDEFINED_DURATION) {recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy);} else {//这里传入的 mDx,mDy,mDuration.是 Action 之前 update()方法。保存的信息 recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration);}} else {recyclerView.mViewFlinger.smoothScrollBy(mDx, mDy, mDuration, mInterpolator);}mChanged = false;....省略代码}
TNND,调来调去最后又把 Action 存储的信息传给了 ViewFlinger 的 smoothScrollBy()方法。这里需要注意:一旦调用该方法会将 mChanged 置为 false,下次再次进入该方法时,那么就不会调用 ViewFlinger 的滑动方法了。
public void smoothScrollBy(int dx, int dy, int duration, Interpolator interpolator) {//判断是否是同一插值器,如果不是,重新创建 mScrollerif (mInterpolator != interpolator) {mInterpolator = interpolator;mScroller = new OverScroller(getContext(), interpolator);}setScrollState(SCROLL_STATE_SETTLING);mLastFlingX = mLastFlingY = 0;mScroller.startScroll(0, 0, dx, dy, duration);if (Build.VERSION.SDK_INT < 23) {mScroller.computeScrollOffset();}postOnAnimation();}
这里 mScroller 接受到 Acttion 传入的滑动信息开始滑动后。最后会调用 postOnAnimation(),又将 ViewFiinger 的 run()法发送出去。那么最终我们又回到了 ViewFiinger 的 run()方法。
public void run() {...省略部分代码 if (scroller.computeScrollOffset()) {final int[] scrollConsumed = mScrollConsumed;final int x = scroller.getCurrX();final int y = scroller.getCurrY();int dx = x - mLastFlingX;int dy = y - mLastFlingY;int hresult = 0;int vresult = 0;mLastFlingX = x;mLastFlingY = y;int overscrollX = 0, overscrollY = 0;...省略部分代码 if (mAdapter != null) {startInterceptRequestLayout();onEnterLayoutOrScroll();TraceCompat.beginSection(TRACE_SCROLL_TAG);fillRemainingScrollValues(mState);if (dx != 0) {//如果横向方向大于 0,开始让 RecyclerView 滚动 hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);overscrollX = dx - hresult;}if (dy != 0) {//如果竖直方向大于 0,开始让 RecyclerView 滚动,获得当前滚动的距离 vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState);overscrollY = dy - vresult;}TraceCompat.endSection();repositionShadowingViews();
onExitLayoutOrScroll();stopInterceptRequestLayout(false);if (smoothScroller != null && !smoothScroller.isPendingInitialRun()&& smoothScroller.isRunning()) {final int adapterSize = mState.getItemCount();if (adapterSize == 0) {smoothScroller.stop();} else if (smoothScroller.getTargetPosition() >= adapterSize) {smoothScroller.setTargetPosition(adapterSize - 1);//传入当前 RecylerView 滚动的距离 dx dysmoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);} else {//传入当前 RecylerView 滚动的距离 dx dysmoothScroller.onAnimation(dx - overscrollX, dy - overscrollY);}}}enableRunOnAnimationRequests();}
这里 scroller(拿到之前 Action 传入的滑动距离信息)已经开始滑动了,故 if (scroller.computeScrollOffset()) 条件为 true, 那么 scroller 拿到当前竖直方向的值就开始让 RecyclerView 滚动了,也就是代码 mLayout.scrollVerticallyBy(dy, mRecycler, mState);接着又让 smoothScroller 执行 onAnimation()方法。其中传入的参数是 RecyclerView 已经滚动的距离。那我们现在继续看 onAnimation 方法。
private void onAnimation(int dx, int dy) {final RecyclerView recyclerView = mRecyclerView;if (!mRunning || mTargetPosition == RecyclerView.NO_POSITION || recyclerView == null) {stop();}mPendingInitialRun = false;if (mTargetView != null) {// verify target positionif (getChildPosition(mTargetView) == mTargetPosition) {onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction);mRecyclingAction.runIfNecessary(recyclerView);stop();} else {Log.e(TAG, "Passed over target position while smooth scrolling.");mTargetView = null;}}if (mRunning) {//获得当前 Recycler 需要滚动的距离 onSeekTargetStep(dx, dy, recyclerView.mState, mRecyclingAction);boolean hadJumpTarget = mRecyclingAction.hasJumpTarget();mRecyclingAction.runIfNecessary(recyclerView);if (hadJumpTarget) {// It is not stopped so needs to be restartedif (mRunning) {mPendingInitialRun = true;recyclerView.mViewFlinger.postOnAnimation();} else {stop(); // done}}}}
那么现在代码就明了了,RecylerView 会判断在滚动的时候,目标视图是否已经出现,如果没有出现,会调用 onSeekTargetStep 保存当前 RecylerView 滚动距离,然后判断 RecyclerView 是否需要滑动,然后又通过 postOnAnimation()将 ViewFlinger 发送出去了。那么直到找到目标视图才会停止。
那什么情况下,目标视图不为空呢,其实在 RecylerView 内部滚动的时候。会判断目标视图是否存在,如果存在会对 mTargetView 进行赋值操作。由于篇幅限制,这里就不对目标视图的查找进行介绍了,有兴趣的小伙伴可以自己看一下源码。
那接下来,我们就假如当前已经找到了目标视图,那么接下来程序会走 onTargetFound()方法。
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {//计算让目标视图可见的,需要滚动的横向距离 final int dx = calculateDxToMakeVisible(targetView, getHorizontalSnapPreference());//计算让目标视图可见的,需要滚动的横向距离 final int dy = calculateDyToMakeVisible(targetView, getVerticalSnapPreference());final int distance = (int) Math.sqrt(dx * dx + dy * dy);final int time = calcu
lateTimeForDeceleration(distance);if (time > 0) {//更新需要滚动的距离。action.update(-dx, -dy, time, mDecelerateInterpolator);}}
当目标视图被找到以后,会计算让目标视图出现在可见范围内,需要移动的横向与纵向距离。并计算所需要花费的时间。然后重新让 RecyclerView 滚动一段距离。
这里我们着重看 calculateDyToMakeVisible。
public int calculateDyToMakeVisible(View view, int snapPreference) {final RecyclerView.LayoutManager layoutManager = getLayoutManager();if (layoutManager == null || !layoutManager.canScrollVertically()) {return 0;}final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)view.getLayoutParams();//获取当前 view 在其父布局的开始位置 final int top = layoutManager.getDecoratedTop(view) - params.topMargin;//获取当前 View 在其父布局结束位置 final int bottom = layoutManager.getDecoratedBottom(view) + params.bottomMargin;//获取当前布局的开始位置 final int start = layoutManager.getPaddingTop();//获取当前布局的结束位置 final int end = layoutManager.getHeight() - layoutManager.getPaddingBottom();return calculateDtToFit(top, bottom, start, end, snapPreference);}
这里我们会根据当前 view 的 top、bottom 及当前布局的 start、end 等坐标信息,然后调用了 calculateDtToFit()方法。现在最重要的出现了,也是我们那三个问题出现的原因!!
public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, intsnapPreference) {switch (snapPreference) {case SNAP_TO_START:return boxStart - viewStart;case SNAP_TO_END:return boxEnd - viewEnd;case SNAP_TO_ANY:final int dtStart = boxStart - viewStart;if (dtStart > 0) {//滚动位置在可见范围之前 return dtStart;}final int dtEnd = boxEnd - viewEnd;if (dtEnd < 0) {//滚动位置在可见范围之后 return dtEnd;}break;default:throw new IllegalArgumentException("snap preference should be one of the"
" constants defined in SmoothScroller, starting with SNAP_");}return 0;//在可见范围之内,直接返回}
我们会根据 snapPreference 对应的值来计算相应的距离,同时 snapPreference 的具体值与 getVerticalSnapPreference(这里我们是竖直方向)所以我们看该方法。
protected int getVerticalSnapPreference() {return mTargetVector == null || mTargetVector.y == 0 ? SNAP_TO_ANY :mTargetVector.y > 0 ? SNAP_TO_END : SNAP_TO_START;}
其中 mTargetVector 与 layoutManager.computeScrollVectorForPosition 有关。
@Overridepublic PointF computeScrollVectorForPosition(int targetPosition) {if (getChildCount() == 0) {return null;}final int firstChildPos = getPosition(getChildAt(0));final int direction = targetPosition < firstChildPos != mShouldReverseLayout ? -1 : 1;if (mOrientation == HORIZONTAL) {
评论