最近開發中遇到了一個需求,須要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.