博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
教你控制 RecyclerView 滑动的节奏
阅读量:5362 次
发布时间:2019-06-15

本文共 7718 字,大约阅读时间需要 25 分钟。

最近,PM升级改版落地页,其中有一个很奇怪的交互需求,需求是这样的:

  用户在该页面可以上下无限滑动,但是,在上拉滑动过程中,当内容切换为另一个内容的时候,新的内容先吸顶,然后停止滑动,让用户知道他已经滑到一个新的内容区了。同一个内容里面,没有该约束。下拉滑动过程也没有这种约束。

  或者用户没有滑动,但是点击到了新的内容区,也需要将新的内容缓慢吸顶,方便用户阅读。

作为RD的我,表示非常蛋疼啊,既然需求是这样的,作为万能的RD的我,当然得想办法去解决呢。

首先,选择 RecyclerView 作为滑动的容器,难点就是怎么在滑动过程中,将新的内容页吸顶,停止滑动。对于 RecyclerView, 当用户滑动后,最终通过 fling 方法来实现惯性滑动的,因此,必须拦截该方法,做一些特有的操作。

试了试 RecyclerView 自身的方法和管理器,都不能实现缓慢吸顶,看来只能重写其中的一些方法了。

public class MyLinearLayoutManger extends LinearLayoutManager {    private float MILLISECONDS_PER_INCH = 0.03f;    private Context contxt;    public MyLinearLayoutManger(Context context) {        super(context);        this.contxt = context;       // setSpeedSlow();    }    @Override    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {        LinearSmoothScroller linearSmoothScroller =                new LinearSmoothScroller(recyclerView.getContext()) {                    @Override                    public PointF computeScrollVectorForPosition(int targetPosition) {                        return MyLinearLayoutManger.this.computeScrollVectorForPosition(targetPosition);                    }                    //This returns the milliseconds it takes to                    //scroll one pixel.                    @Override                    protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {                        return MILLISECONDS_PER_INCH / displayMetrics.density;                        //返回滑动一个pixel需要多少毫秒                    }                    @Override                    public int calculateDtToFit(int viewStart, int viewEnd, int boxStart, int boxEnd, int snapPreference) {                        return boxStart - viewStart;                    }                };        linearSmoothScroller.setTargetPosition(position);        startSmoothScroll(linearSmoothScroller);    }}

我们来看 LinearSmoothScroller 中的一段源码:

/**     * Helper method for {
@link #calculateDxToMakeVisible(android.view.View, int)} and * {
@link #calculateDyToMakeVisible(android.view.View, int)} */ 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; }

可以看到,SNAP_TO_START 就是上边或者左边吸顶的意思。这样只要重写 calculateDtToFit 方法就实现了缓慢吸顶。

/**     * Align child view's left or top with parent view's left or top     *     * @see #calculateDtToFit(int, int, int, int, int)     * @see #calculateDxToMakeVisible(android.view.View, int)     * @see #calculateDyToMakeVisible(android.view.View, int)     */    public static final int SNAP_TO_START = -1;

那什么时候调用呢?一个是点击新的内容区时候调用,另一个是再滑动过程中判断是否要切换内容区了。这时候就要重写 

package com.sjq.recycletest;import android.content.Context;import android.hardware.SensorManager;import android.support.annotation.Nullable;import android.support.v7.widget.LinearLayoutManager;import android.support.v7.widget.RecyclerView;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.ViewConfiguration;/** * Created by shenjiaqi on 2018/7/18. */public class MyRecyclerView extends RecyclerView {    private static float DECELERATION_RATE = (float) (Math.log(0.78) / Math.log(0.9));    private static final float INFLEXION = 0.35f; // Tension lines cross at (INFLEXION, 1)    private float mFlingFriction = ViewConfiguration.getScrollFriction();    private float mPhysicalCoeff;    private static final String TAG = "MyRecyclerView";    private final int height;    public MyRecyclerView(Context context) {        this(context, null);    }    public MyRecyclerView(Context context, @Nullable AttributeSet attrs) {        this(context, attrs, 0);    }    public MyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);        final float ppi = context.getResources().getDisplayMetrics().density * 160.0f;        mPhysicalCoeff = SensorManager.GRAVITY_EARTH // g (m/s^2)                * 39.37f // inch/meter                * ppi                * 0.84f; // look and feel tuning        height = context.getResources().getDisplayMetrics().heightPixels;    }    public double getSplineDeceleration(int velocity) {        return Math.log(INFLEXION * Math.abs(velocity) / (mFlingFriction * mPhysicalCoeff));    }  // 惯性滑动的距离    public double getSplineFlingDistance(int velocity) {        final double l = getSplineDeceleration(velocity);        final double decelMinusOne = DECELERATION_RATE - 1.0;        return mFlingFriction * mPhysicalCoeff * Math.exp(DECELERATION_RATE / decelMinusOne * l);    }    /* Returns the duration, expressed in milliseconds */    public int getSplineFlingDuration(int velocity) {        final double l = getSplineDeceleration(velocity);        final double decelMinusOne = DECELERATION_RATE - 1.0;        return (int) (1000.0 * Math.exp(l / decelMinusOne));    }    @Override    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {        super.onNestedPreScroll(target, dx, dy, consumed);    }    @Override    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {        return super.onNestedPreFling(target, velocityX, velocityY);    }    @Override    public boolean fling(int velocityX, int velocityY) {        if (velocityY > 0) {            if (getLayoutManager() != null                    && getLayoutManager() instanceof LinearLayoutManager) {                LinearLayoutManager manager = (LinearLayoutManager) getLayoutManager();                final int firstPosition = manager.findFirstVisibleItemPosition();                final int lastPosition = manager.findLastVisibleItemPosition();           // 假设一个item高度为 500, 通过惯性滑动距离和高度可以计算出来会经过多少个item                int position = (int) (firstPosition + getSplineFlingDistance((int) velocityY) / 500);           // 以8个item为一个内容区                int s1 = firstPosition / 8;                int s = lastPosition > position ? lastPosition / 8 : position / 8;                if (s > s1) {                    s = s1 + 1;                }                int pos = s * 8;int top = height;                if (s > s1 && lastPosition >= pos && pos > firstPosition) {                    top = getChildAt(pos - firstPosition).getTop();                }                if (s > 0 && s > s1) {                    smoothScrollToPosition(pos);                    return true;                }            }        }   return super.fling(velocityX, velocityY);    }    public int getDisplyHeight() {        return height;    }}

但是大家会发现,如果 item 高度值估计过小,会导致,一滑动就会立马切换到新的内容区,体验还是不好。

眼尖的人一定会发现,我在前面 MyLinearLayoutManger 构造函数中注释掉了一个方法。这个方法就是用来改善滑动效果的。

public void setSpeedSlow() {        //自己在这里用density去乘,希望不同分辨率设备上滑动速度相同        //0.3f是自己估摸的一个值,可以根据不同需求自己修改        MILLISECONDS_PER_INCH = contxt.getResources().getDisplayMetrics().density * 0.03f;    }

调用该方法后,大家会发现效果好多了,但是其实默认值  MILLISECONDS_PER_INCH 是 25f;

你会发现效果似乎更好了。

 

希望本文对大家进一步了解 RecyclerView 有帮助。

转载于:https://www.cnblogs.com/huansky/p/9382689.html

你可能感兴趣的文章
Ubuntu:让桌面显示回收站
查看>>
Android上传头像代码,相机,相册,裁剪
查看>>
git 安装体验
查看>>
Oracle 给已创建的表增加自增长列
查看>>
《DSP using MATLAB》Problem 2.17
查看>>
if 循环
查看>>
uva 111 History Grading(lcs)
查看>>
Python学习week2-python介绍与pyenv安装
查看>>
php判断网页是否gzip压缩
查看>>
一个有意思的js实例,你会吗??[原创]
查看>>
sql server中bit字段实现取反操作
查看>>
Part3_lesson2---ARM指令分类学习
查看>>
jQuery拖拽原理实例
查看>>
JavaScript 技巧与高级特性
查看>>
Uva 11729 Commando War
查看>>
增强学习(一) ----- 基本概念
查看>>
ubuntu下USB连接Android手机
查看>>
C# 语句 分支语句 switch----case----.
查看>>
lseek函数
查看>>
反射获取 obj类 的属性 与对应值
查看>>