最近,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 有帮助。

教你控制 RecyclerView 滑动的节奏的更多相关文章

  1. 手把手教你实现RecyclerView的下拉刷新和上拉加载更多

    手把手教你实现RecyclerView的下拉刷新和上拉加载更多     版权声明:本文为博主原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接和本声明. 本文链接:https:// ...

  2. 监听RecyclerView滑动到末端

    监听RecyclerView滑动到末端 效果图 实现 1. 添加滑动到末端的接口 package com.kongqw.recyclerviewdemo; /** * Created by kongq ...

  3. Android ScrollView嵌套Recyclerview滑动卡顿,松手即停问题解决;

    假如你的布局类似这样的: <ScrollView android:layout_width="match_parent" android:layout_height=&quo ...

  4. Android RecyclerView 滑动时图片加载的优化

    RecyclerView 滑动时的优化处理 在滑动时停止加载图片,在滑动停止时开始加载图片,这里用了Glide.pause 和Glide.resume.这里为了避免重复设置增加开销,设置了一个标志变量 ...

  5. RecyclerView滑动到指定位置,并置顶

    一般我们用 mRecycleview.smoothScrollToPosition(0)滑动到顶部,具有滚动效果,但是如果我们想滚动到任意指定位置,那么smoothScrollToPosition() ...

  6. Android开发学习之路-RecyclerView滑动删除和拖动排序

    Android开发学习之路-RecyclerView使用初探 Android开发学习之路-RecyclerView的Item自定义动画及DefaultItemAnimator源码分析 Android开 ...

  7. RecyclerView 滑动检测 (上滑 up)(下滑 down)(顶部 top)(底部 bottom)

      RecyclerView 给我们的可以检测滑动事件的接口 只有 一个方法 recyclerview.setOnScrollListener()或者 recyclerview.addOnScroll ...

  8. 安卓多个RecyclerView滑动与显示问题

    最近在项目遇到这样的问题:在一线性垂直布局内,有两个垂直的RecyclerView,如果直接高度直接设置wrap-content, 通常会导致滑动冲突或是内容显示不全. 首先说下解决的思路,就是在最外 ...

  9. Android 如何获取Android RecyclerView滑动的距离

    如何获取 RecyclerView 的滑动距离? RecyclerView 虽然有getScrollX() 和 getScrollY(), 但是测试发现这两个函数总是返回0,太无语了.因此想到了下面几 ...

随机推荐

  1. 简易数据库实现 UNIX环境高级编程(APUE)第二十章 A Database Library

    将课程的源代码 使用C++写了一部分 LINUX WINDOW均可运行 #ifndef MYDB_H #define MYDB_H #include <iostream> #include ...

  2. HTML语言

    复习: 1.Web项目的部署结构  静态Web技术(客户端技术):提供的内容任何人在任何时间访问都是一样的 HTML/CSS/JS/Flash.... 动态Web技术(服务器端技术):提供的内容不同人 ...

  3. hibernate的Could not execute JDBC batch update错误原因及处理

    http://blog.csdn.net/derpvailzhangfan/article/details/2332795\ 上述问题: 一设置关联 二包含关键字 三 映射文件设置 catalog=“ ...

  4. #10072. 「一本通 3.2 例 1」Sightseeing Trip(floyd求最小环+路径)

    https://loj.ac/problem/10072 针对无向图 因为Floyd是按照结点的顺序更新最短路的,所以我们在更新最短路之前先找到一个连接点k,当前的点k肯定不存在于已存在的最短路f[i ...

  5. java生成pdf文件 --- Table

    Java利用itext实现导出PDF文件 所需要的jar包:com.lowagie.text_2.1.7.v201004222200.jar jar包下载地址:http://cn.jarfire.or ...

  6. UnionFind问题总结

    UnionFind就是acm中常用的并查集... 并查集常用操作 另外补充一下STL常用操作 相关问题: 547. Friend Circles 纯裸题噢... class Solution { pu ...

  7. HDMI EDID 处理过程

    DDC的参数 EDID是一种VESA 标准数据格式,其中包含有关监视器及其性能的参数,包括供应商信息.最大图像大小.颜色设置.厂商预设置.频率范围的限制以及显示器名和序列号的字符串.EDID数据标准: ...

  8. Struts2学习第四天——拦截器及文件上传

    1.概述 Struts2的很多核心功能都是由拦截器完成的. 拦截器很好的实现了AOP的编程思想,在动作的执行之前和结果的返回之后,做拦截处理. 2.struts2的默认拦截器栈 3.自定义拦截器 St ...

  9. 中国移动物联网ONENET平台数据本地采集工具

    吧从中国移动物联网平台上接收的数据 实时按天保存为CSV文件或者是SQL SERVER数据库中方便进行数据处理 还可设置显示最大值,最小值,报警值,报警推送,tts语音报警等贴心功能

  10. RabbitMQ基本理论

    本节内容 一  RabbitMQ介绍 二  RabbitMQ安装配置 三  RabbitMQ的Python实现-pika 1. 生产者消费者 2. 工作队列 3. 持久化和公平分发 4. 发布与订阅 ...