RecyclerView 平滑滾動可控制滾動速度的終極解決方案

weixin_34365417發表於2017-08-09

原創 2017-08-09 認真的 小蘇

recyclerview 滑動到指定位置有好多坑,相信用過的人都遇到坑了,沒辦法遇到坑了,也要把它填上,今天遇到一個問題,要求 : recyclerview 平滑的滾動到指定位置並且位於螢幕中間,而且速度不能太快了。。。

看下實現效果圖吧,仿騰訊新聞視訊列表的滑動效果。
程式碼

2005932-80190a7de9d41cf9.gif
device-2017-08-09-180723.gif

recyclerview 常用的位置跳轉

((LinearLayoutManager) rl_video.getLayoutManager()).scrollToPositionWithOffset(itemPosition, 20);
rl_video.scrollToPosition(itemPosition);
rl_video.smoothScrollBy(0, top);
rl_video.smoothScrollToPosition(position);

這些跳轉有好多蛋疼的問題,不是跳到指定位置,要不就是滑動到指定位置不準確,要不就是滑動太快,反正就是達不到要求。
於是Google了半天,終於在stackoverflow 找到了解決思路。

看下面解決程式碼:

public class ScrollSpeedLinearLayoutManger extends LinearLayoutManager {


public ScrollSpeedLinearLayoutManger(Context context) {
super(context, VERTICAL, false);
}

public ScrollSpeedLinearLayoutManger(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}


@Override
public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
Log.e("linksu",
"smoothScrollToPosition(ScrollSpeedLinearLayoutManger.java:62)");
RecyclerView.SmoothScroller smoothScroller = new CenterSmoothScroller(recyclerView.getContext());
smoothScroller.setTargetPosition(position);
startSmoothScroll(smoothScroller);
}

private class CenterSmoothScroller extends LinearSmoothScroller {

CenterSmoothScroller(Context context) {
super(context);
}

@Nullable
@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return ScrollSpeedLinearLayoutManger.this.computeScrollVectorForPosition(targetPosition);
}

@Override
public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference) {
return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2);
}

protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return 0.2f;
}

@Override
protected int getVerticalSnapPreference() {
return SNAP_TO_START;
}
}

}

重寫 LinearLayoutManager,為什麼要重寫呢,因為我是呼叫這個方法進行滑動的 rl_video.smoothScrollToPosition(position); 看下這個方法的原始碼是如何寫的

 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;
        }
        mLayout.smoothScrollToPosition(this, mState, position);
    }

如上程式碼所示,知其然不知其所以然,正是呼叫了LinearLayoutManager 中的 smoothScrollToPosition。

實現的核心程式碼
private class CenterSmoothScroller extends LinearSmoothScroller {

CenterSmoothScroller(Context context) {
super(context);
}

@Nullable
@Override
public PointF computeScrollVectorForPosition(int targetPosition) {
return ScrollSpeedLinearLayoutManger.this.computeScrollVectorForPosition(targetPosition);
}

@Override
public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference) {
return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2);
}

protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return 0.2f;
}

@Override
protected int getVerticalSnapPreference() {
return SNAP_TO_START;
}
}

calculateDtToFit 方法是控制滑動的位置,可以在這個方法中所以的控制滑動的位置

@Override
public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference) {
return (boxStart + (boxEnd - boxStart) / 2) - (viewStart + (viewEnd - viewStart) / 2);
}

calculateSpeedPerPixel 方法是控制滑動速度的

protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return 0.2f;
}

核心的主要是這兩個方法。

呼叫方式:這樣就很簡單的實現了

layoutManager = new ScrollSpeedLinearLayoutManger(this);
rl_video.setLayoutManager(layoutManager);
 rl_video.smoothScrollToPosition(position);
另一種更簡單的方案
RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(context) {
@Override protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_START;
}};
現在設定要滾動到的位置:
smoothScroller.setTargetPosition(position);
並將SmoothScroller傳遞給LayoutManager:
layoutManager.startSmoothScroll(smoothScroller);

LinearSmoothScroller extends RecyclerView.SmoothScroller 解決思路其實是一樣的。

RecyclerView滑動速度的設定(此處以橫向的滑動為例)

自定義一個類繼承自LayoutManager,然後過載其scrollHorizontallyBy()方法,其中在該方法中,第一個引數dx即為滑動的距>
離,因此改變這個值就可以改變滑動的速度。為了方便設定其滑動的速度,可以自定義一個速度因子speedRatio,通過利用> > > dx*speedRatio來達到控制速度的目的。示例程式碼如下:

public class CustomSGLayoutManager extends StaggeredGridLayoutManager {
private double speedRatio;
public CustomSGLayoutManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}

public CustomSGLayoutManager(int spanCount, int orientation) {
super(spanCount, orientation);
}

@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
int a = super.scrollHorizontallyBy((int)(speedRatio*dx), recycler, state);//遮蔽之後無滑動效果,證明滑動的效果就是由這個函式實現
if(a == (int)(speedRatio*dx)){
return dx;
}
return a;
}

public void setSpeedRatio(double speedRatio){
this.speedRatio = speedRatio;
}
}

而後,例項化這個類,並設定為RecyclerView的佈局,程式碼如下所示

private RecyclerView skyRecyclerView;

public void doSomething(){
CustomSGLayoutManager skyLayoutManager = new CustomSGLayoutManager(1,StaggeredGridLayoutManager.HORIZONTAL);//例項化自定義類
skyLayoutManager.setSpeedRatio(0.82);//設定其速度因子
skyRecyclerView.setLayoutManager(skyLayoutManager);
}

RecyclerView拋擲速度的設定(此處以橫向的滑動為例)
自定義一個類繼承自RecyclerView,然後過載其fling()方法,在該方法中velocityX為其橫向的移動距離,velocityY為其縱向的移動距離(此處以橫向的滑動為例),改變這兩個引數,即可以改變其相應方向滑動的距離。為了方便設定,這裡同樣引入一個縮放因子scale,程式碼示例如下:

public class CustomRecyclerView extends RecyclerView {
private double scale; //拋擲速度的縮放因子

public CustomRecyclerView(Context context) {
super(context);
}

public CustomRecyclerView(Context context, AttributeSet attrs) {
super(context, attrs);
}

public CustomRecyclerView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}

public void setflingScale(double scale){
this.scale = scale;
}

@Override
public boolean fling(int velocityX, int velocityY) {
velocityX *= scale;
return super.fling(velocityX, velocityY);
}
}

而後,在RecyclerView中設定其縮放因子即可,程式碼如下:

skyLayoutManager.setSpeedRatio(0.5);

視訊列表滾動連播技術探究系列

1、仿網易/QQ空間視訊列表滾動連播炫酷效果(V1.0 挖坑之路)
2、仿網易/QQ空間視訊列表滾動連播炫酷效果(V2.0 填坑之路)
3、仿網易視訊列表滾動連播炫酷效果(v3.0 穩定版-思想改變及優化) 穩定版-進行優化和思想上的改變。
4、RecyclerView 平滑滾動可控制滾動速度的終極解決方案
5、仿網易視訊列表連播炫酷效果 - v3.1 升級版-細節優化(網路狀態切換、item點選事件等)
持續更新中.....

2005932-543b15ba17e17a16
專題封面

相關文章