最近,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. AJAX html 传输json字符串&&巧妙运用eval()来解析返回的JSON字符串

    1.AJAX html 传输json字符串: js方法如下: function saveRetYwlsh(){ var xmbh = document.getElementById("xmb ...

  2. git stash错误小记

    git出错小记 想要push代码,我们经常这样做. 1.查看状态 git status 2.隐藏本地编辑的新内容 git stash 3.拉远程的代码 git pull 这一步操作有的时候会报错,没有 ...

  3. Axure RP Extension for Chrome 插件安装

    描述 我的chmod浏览器上不去谷歌商店,我用的是蓝灯,登上商店后搜索Axure RP Extension for Chrome,下载安装,完成后进入这个插件的详细信息: 使用 打开用axure生成的 ...

  4. 73.解决Xcode10 library not found for -lstdc++ 找不到问题

    Xcode10 彻底废除了libstdc++,相关文件libstdc++.6.0.9.dylib.libstdc++.6.dylib.libstdc++.dylib.libstdc++.6.0.9.t ...

  5. Python语言中的按位运算

    (转)位操作是程序设计中对位模式或二进制数的一元和二元操作. 在许多古老的微处理器上, 位运算比加减运算略快, 通常位运算比乘除法运算要快很多. 在现代架构中, 情况并非如此:位运算的运算速度通常与加 ...

  6. spring+shiro+ehcache整合

    1.导入jar包(pom.xml文件) <!-- ehcache缓存框架 --> <dependency> <groupId>net.sf.ehcache</ ...

  7. java实现随机产生6位数的方法总结

    package com.yin.test; import java.util.Random; import org.junit.Test; /** * @author v_yinyl * @date ...

  8. HSmartWindowControl之安装篇 (Visual Studio 2013 & Halcon 18)

    1.环境简介 Visual Studio 2013社区版 Halcon18.05 2.使用Nuget在VS工程中安装Halcon插件 搜索栏输入关键字halcon,出现两个插件,分别是halcon语言 ...

  9. LINK1104:无法打开文件"\.obj"原因及解决办法

    那是由于具有空文件名的库导致的,只需要检查一下配置信息把出现空文件名的地方进行改正就可以了. 作者:耑新新,发布于  博客园 转载请注明出处,欢迎邮件交流:zhuanxinxin@foxmail.co ...

  10. [UWP]在UWP平台中使用Lottie动画

    最近QQ影音久违的更新了,因为记得QQ影音之前体验还算不错(FFmepg的事另说),我也第一时间去官网下载体验了一下,结果发现一些有趣的事情. 是的,你没看错,QQ影音主界面上这个动画效果是使用Lot ...