最近,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. 《C#从现象到本质》读书笔记(八)第10章反射

    <C#从现象到本质>读书笔记(八)第10章反射 个人感觉,反射其实就是为了能够在程序运行期间动态的加载一个外部的DLL集合,然后通过某种办法找到这个DLL集合中的某个空间下的某个类的某个成 ...

  2. jmeter+Jenkins持续集成(邮件通知)

    jmeter构建后,自送发送邮件到指定的邮箱,配置如下 1)Jenkins Location配置 jenkins首页->系统管理->系统配置页面 其中Jenkins URL有默认值,最好修 ...

  3. Python学习第四章

    1.类和对象: 类对象支持两种操作:属性引用和实例化. 属性引用:obj.name 构造方法:类会定义一个名为__int__()的特殊方法如下 def  __init__(self):       s ...

  4. 还原Azure DevOps Server (TFS)中误删除的生成流水线

    流水线历史记录 DevOps Server流水线的历史记录有完善的版本日志,用户可以随时回退到修改过程中的任何一个版本,还能比较差异.这个历史记录功能可以和代码库中的版本控制媲美. 图一:生成历史记录 ...

  5. Media Queries 媒体查询常见设备断点

    按需调整断点 一.谷歌后摘抄的一部分媒体查询 /*#region SmartPhones */ /* SmartPhones */@media only screen and (min-device- ...

  6. 骚年,看我如何把 PhantomJS 图片的 XSS 升级成 SSRF/LFR

    这篇文章实在是太好了,我看了好几篇,所以极力推荐给大家 原文地址   http://buer.haus/2017/06/29/escalating-xss-in-phantomjs-image-ren ...

  7. GraphQL:你的容颜,十万光年

    What? GraphQL 是一种类似于 SQL 的结构化查询语言,由 facebook 于2012年创造,于2015年开源.SQL 在服务端定义,GraphQL 在客户端定义,也就是说 GraphQ ...

  8. Springboot中读取.yml文件

    自定义配置文件application-dev.yml spring: dataresource: druid: driver-class-name: com.mysql.jdbc.Driver url ...

  9. git关于文件权限修改引起的冲突及忽略文件权限的办法

    我们在使用git进行版本管理的时候,有时候只是修改了文件的权限,比如将pack.php修改为777,但其实文件内容并没有改变,但是git会认为此文件做了修改,原因是git把文件权限也算作文件差异的一部 ...

  10. 转载 Python中关键字global与nonlocal的区别

    转载自CSDN 雁丘1990, 原文地址: https://blog.csdn.net/xcyansun/article/details/79672634 这篇文章写的很赞, 条理清晰, 分析循序渐进 ...