RecyclerView 平滑滚动可控制滚动速度 及 滚动的距离-终极解决方案

原创 2017-08-09 认真的 小苏

recyclerview 滑动到指定位置有好多坑,相信用过的人都遇到坑了,没办法遇到坑了,也要把它填上,今天遇到一个问题,要求 : recyclerview 平滑的滚动到指定位置并且位于屏幕中间,而且速度不能太快了。。。

看下实现效果图吧,仿腾讯新闻视频列表的滑动效果。
代码
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;
}
}

}

    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

重写 LinearLayoutManager,为什么要重写呢,因为我是调用这个方法进行滑动的 rl_video.smoothScrollToPosition(position); 看下这个方法的源码是如何写的

 public void smoothScrollToPosition(int position) {
        if (mLayoutFrozen) {
            return;
        }
        if (mLayout == ) {
            Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " +
                    "Call setLayoutManager with a non-null argument.");
            return;
        }
        mLayout.smoothScrollToPosition(this, mState, position);
    }
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

如上代码所示,知其然不知其所以然,正是调用了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;
}
}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

calculateDtToFit 方法是控制滑动的位置,可以在这个方法中所以的控制滑动的位置

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

calculateSpeedPerPixel 方法是控制滑动速度的

protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return 0.2f;
}
    
    
  • 1
  • 2
  • 3

核心的主要是这两个方法。

调用方式:这样就很简单的实现了

layoutManager = new ScrollSpeedLinearLayoutManger(this);
rl_video.setLayoutManager(layoutManager);
 rl_video.smoothScrollToPosition(position);
    
    
  • 1
  • 2
  • 3
另一种更简单的方案
RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(context) {
@Override protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_START;
}};
现在设置要滚动到的位置:
smoothScroller.setTargetPosition(position);
并将SmoothScroller传递给LayoutManager:
layoutManager.startSmoothScroll(smoothScroller);

    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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;
}
}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

而后,实例化这个类,并设置为RecyclerView的布局,代码如下所示

private RecyclerView skyRecyclerView;

public void doSomething(){
CustomSGLayoutManager skyLayoutManager = new CustomSGLayoutManager(1,StaggeredGridLayoutManager.HORIZONTAL);//实例化自定义类
skyLayoutManager.setSpeedRatio(0.82);//设置其速度因子
skyRecyclerView.setLayoutManager(skyLayoutManager);
}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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);
}
}
    
    
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

而后,在RecyclerView中设置其缩放因子即可,代码如下:

skyLayoutManager.setSpeedRatio(0.5);

视频列表滚动连播技术探究系列

1、仿网易/QQ空间视频列表滚动连播炫酷效果(V1.0 挖坑之路)
2、仿网易/QQ空间视频列表滚动连播炫酷效果(V2.0 填坑之路)
3、仿网易视频列表滚动连播炫酷效果(v3.0 稳定版-思想改变及优化) 稳定版-进行优化和思想上的改变。
4、RecyclerView 平滑滚动可控制滚动速度的终极解决方案
5、仿网易视频列表连播炫酷效果 - v3.1 升级版-细节优化(网络状态切换、item点击事件等)
持续更新中…..
专题封面

        <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/markdown_views-ea0013b516.css">
            </div>

原创 2017-08-09 认真的 小苏

recyclerview 滑动到指定位置有好多坑,相信用过的人都遇到坑了,没办法遇到坑了,也要把它填上,今天遇到一个问题,要求 : recyclerview 平滑的滚动到指定位置并且位于屏幕中间,而且速度不能太快了。。。

看下实现效果图吧,仿腾讯新闻视频列表的滑动效果。
代码
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;
}
}

}

  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

重写 LinearLayoutManager,为什么要重写呢,因为我是调用这个方法进行滑动的 rl_video.smoothScrollToPosition(position); 看下这个方法的源码是如何写的

 public void smoothScrollToPosition(int position) {
        if (mLayoutFrozen) {
            return;
        }
        if (mLayout == ) {
            Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. " +
                    "Call setLayoutManager with a non-null argument.");
            return;
        }
        mLayout.smoothScrollToPosition(this, mState, position);
    }
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

如上代码所示,知其然不知其所以然,正是调用了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;
}
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

calculateDtToFit 方法是控制滑动的位置,可以在这个方法中所以的控制滑动的位置

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

calculateSpeedPerPixel 方法是控制滑动速度的

protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return 0.2f;
}
  
  
  • 1
  • 2
  • 3

核心的主要是这两个方法。

调用方式:这样就很简单的实现了

layoutManager = new ScrollSpeedLinearLayoutManger(this);
rl_video.setLayoutManager(layoutManager);
 rl_video.smoothScrollToPosition(position);
  
  
  • 1
  • 2
  • 3
另一种更简单的方案
RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(context) {
@Override protected int getVerticalSnapPreference() {
return LinearSmoothScroller.SNAP_TO_START;
}};
现在设置要滚动到的位置:
smoothScroller.setTargetPosition(position);
并将SmoothScroller传递给LayoutManager:
layoutManager.startSmoothScroll(smoothScroller);

  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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;
}
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

而后,实例化这个类,并设置为RecyclerView的布局,代码如下所示

private RecyclerView skyRecyclerView;

public void doSomething(){
CustomSGLayoutManager skyLayoutManager = new CustomSGLayoutManager(1,StaggeredGridLayoutManager.HORIZONTAL);//实例化自定义类
skyLayoutManager.setSpeedRatio(0.82);//设置其速度因子
skyRecyclerView.setLayoutManager(skyLayoutManager);
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

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);
}
}
  
  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

而后,在RecyclerView中设置其缩放因子即可,代码如下:

skyLayoutManager.setSpeedRatio(0.5);

视频列表滚动连播技术探究系列

1、仿网易/QQ空间视频列表滚动连播炫酷效果(V1.0 挖坑之路)
2、仿网易/QQ空间视频列表滚动连播炫酷效果(V2.0 填坑之路)
3、仿网易视频列表滚动连播炫酷效果(v3.0 稳定版-思想改变及优化) 稳定版-进行优化和思想上的改变。
4、RecyclerView 平滑滚动可控制滚动速度的终极解决方案
5、仿网易视频列表连播炫酷效果 - v3.1 升级版-细节优化(网络状态切换、item点击事件等)
持续更新中…..
专题封面

        <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/markdown_views-ea0013b516.css">
            </div>

猜你喜欢

转载自blog.csdn.net/yzj_0722/article/details/80393216