最近开发中遇到了一个需求,须要RecyclerView滚动到指定位置后置顶显示,当时遇到这个问题的时候,内心第一反应是直接使用RecyclerView的smoothScrollToPosition()方法,实现对应位置的平滑滚动。可是在实际使用中发现并无到底本身想要的效果。本想着偷懒直接从网上Copy下,可是发现效果并非很好。因而就本身去研究源码。java
该系列文章分为两篇文章。git
注意!!!注意!!!注意!!! 这是使用的LinearLayoutManager且是竖直方向上的,横向的思路是同样的,只是修改的方法不同,你们必定要注意前提条件。github
若是你看了个人另外一篇文章RecyclerView.smoothScrollToPosition了解一下,你们应该会清楚,其实在你设定目标位置后,当找到目标视图后,最后让RecyclerView进行滚动的方法是其对应LinearLayoutManager中的LinearSmoothScroller的calculateDtToFit()方法。markdown
public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference) { 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; } 复制代码
也就是说在LinerlayoutManager为竖直的状况下,snapPreference默认为SNAP_ANY,那么咱们就能够获得,下面三种状况。app
同时snapPreference的值是经过LinearSmoothScroller中的getVerticalSnapPreference()与getHorizontalSnapPreference() 来设定的。ide
因此为了使滚动位置对应的目标视图在顶部显示,那么咱们建立一个新类并继承LinearLayoutManager。同时建立TopSnappedSmoothScroller继承LinearSmoothScroller,并重写它的getVerticalSnapPreference()方法就好了。(若是你是横向的,请修改getHorizontalSnapPreference方法)oop
public class LinearLayoutManagerWithScrollTop extends LinearLayoutManager { public LinearLayoutManagerWithScrollTop(Context context) { super(context); } public LinearLayoutManagerWithScrollTop(Context context, int orientation, boolean reverseLayout) { super(context, orientation, reverseLayout); } public LinearLayoutManagerWithScrollTop(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) { TopSnappedSmoothScroller topSnappedSmoothScroller = new TopSnappedSmoothScroller(recyclerView.getContext()); topSnappedSmoothScroller.setTargetPosition(position); startSmoothScroll(topSnappedSmoothScroller); } class TopSnappedSmoothScroller extends LinearSmoothScroller { public TopSnappedSmoothScroller(Context context) { super(context); } @Nullable @Override public PointF computeScrollVectorForPosition(int targetPosition) { return LinearLayoutManagerWithScrollTop.this.computeScrollVectorForPosition(targetPosition); } @Override protected int getVerticalSnapPreference() { return SNAP_TO_START;//设置滚动位置 } } } 复制代码
建立该类后,咱们接下来就只用给RecyclerView设置对应的新的布局管理器,并调用smoothScrollToPosition()方法就好了。布局
其实在RecyclerView中,滚动到指定位置是分为了两个部分,第一个是没有找到目标位置对应的视图以前的速度,一种是找到目标位置对应的视图以后滚动的速度。post
action.update((int) (mInterimTargetDx * TARGET_SEEK_EXTRA_SCROLL_RATIO), (int) (mInterimTargetDy * TARGET_SEEK_EXTRA_SCROLL_RATIO), (int) (time * TARGET_SEEK_EXTRA_SCROLL_RATIO), mLinearInterpolator); 复制代码
在开始寻找目标位置时,默认的开始距离是12000(单位:px),且这里你们注意,咱们使用了LinearInterpolator,也就是说在没有找到目标位置以前,咱们的RecyclerView速度是恒定的。this
action.update(-dx, -dy, time, mDecelerateInterpolator);
复制代码
这里咱们使用了DecelerateInterpolator。也就是说,找到目标位置以后,RecyclerView是速度是慢慢减少。
因此如今就提供了一个思路,咱们能够去修改两个部分的插值器,来改变RecyclerView的滚动速度,固然我这里并无给实例代码,由于我发现Google并无想让咱们去修改插值器的想法,由于在其LinearSmoothScroller中,他直接把两个插值器用protected修饰。(因此我以为这样改,感受不优雅)若是有兴趣的小伙伴,能够去修改。
既然以修改插值器的方式比较麻烦,那么咱们能够修改滚动时间啊!!!!!!但愿你们还记得,咱们在调用Action的update方法时,咱们不只保存了RecyclerView须要滚动的距离,咱们还保存了滑动总共须要的时间。
滑动所须要的时间是经过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;// 默认为移动一英寸须要花费25ms // protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { return MILLISECONDS_PER_INCH / displayMetrics.densityDpi; } 复制代码
也就是说,当前滚动的速度是与屏幕的像素密度相关, 经过获取当前手机屏幕每英寸的像素密度,与每英寸移动所须要花费的时间,用每英寸移动所须要花费的时间除以像素密度就能计算出移动一个像素密度须要花费的时间。
那么如今,就能够经过两个方法来修改RecyclerView的滚动速度,要么咱们修改calculateSpeedPerPixel方法修改移动一个像素须要花费的时间。要么咱们修改calculateTimeForScrolling方法。
这里我采用修改calculateSpeedPerPixel方法来改变速度。这里我修改移动一英寸须要花费为10ms,那表明着滚动速度加快了。那么对应的滚动时间就变小了
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) { return 10f / displayMetrics.densityDpi; } 复制代码
到了这里我相信你们已经明白了,怎么去修改速度与滚动位置了。好啦好啦,先睡了太困了。
对了对了,源码在这里。你们若是有兴趣,能够去研究一下。
最后,附上我写的一个基于Kotlin 仿开眼的项目SimpleEyes(ps: 其实在我以前,已经有不少小朋友开始仿这款应用了,可是我以为要作就作好。因此个人项目和其余的人应该不一样,不只仅是简单的一个应用。可是,可是。可是。重要的话说三遍。还在开发阶段,不要打我),欢迎你们follow和start.