目录

一、前言

二、Scroller

三、VelocityTracker

四、实战——带惯性滑动的柱状图

五、写在最后

一、前言

自定义控件中,难免会遇到需要滑动的场景。而Canvas提供的scrollTo和scrollBy方法只能达到移动的效果,需要达到真正的滑动便需要我们今天分享的两把基础利器ScrollerVelocityTracker。老规矩,先上实战图,再进行分享。

带惯性滑动的柱状图

二、Scroller

1、作用

童鞋们可以先看下下面这段官方的英文类注释。小盆友以自己的理解给出这个类的作用是,Scroller 是一个让视图 滚动起来的工具类,负责根据我们提供的数据计算出相应的坐标,但是具体的滚动逻辑还是由我们程序猿来进行 移动内容 实现(?为啥说是移动内容,我们在实战一节中便知道了,稍安勿躁)。

* <p>This class encapsulates scrolling. You can use scrollers ({@link Scroller}
* or {@link OverScroller}) to collect the data you need to produce a scrolling
* animation&mdash;for example, in response to a fling gesture. Scrollers track
* scroll offsets for you over time, but they don't automatically apply those
* positions to your view. It's your responsibility to get and apply new
* coordinates at a rate that will make the scrolling animation look smooth.</p>

2、API讲解

这一小节是对 Scroller构造方法常用的公有方法 进行讲解,如果您已经对这些方法很熟悉,可以跳过。

构造方法

(1)Scroller(Context context)

public Scroller(Context context)

方法描述:
创建一个 Scroller 实例。

参数解析:
第一个参数 context: 上下文;

(2) Scroller(Context context, Interpolator interpolator)

public Scroller(Context context, Interpolator interpolator)

方法描述:
创建一个 Scroller 实例。

参数解析:
第一个参数 context: 上下文;
第二个参数 interpolator: 插值器,用于在 computeScrollOffset 方法中,并且是在 SCROLL_MODE 模式下,根据时间的推移计算位置。为null时,使用默认 ViscousFluidInterpolator 插值器。

(3) Scroller(Context context, Interpolator interpolator, boolean flywheel)

public Scroller(Context context, Interpolator interpolator, boolean flywheel)

方法描述:
创建一个 Scroller 实例。
参数解析:
第一个参数 context: 上下文;
第二个参数 interpolator: 插值器,用于在 computeScrollOffset 方法中,并且是在 SCROLL_MODE 模式下,根据时间的推移计算位置。为null时,使用默认 ViscousFluidInterpolator 插值器。
第三个参数 flywheel: 支持渐进式行为,该参数只作用于 FLING_MODE 模式下。

常用公有方法

(1) setFriction(float friction)

public final void setFriction(float friction)

方法描述:
用于设置在 FLING_MODE 模式下的摩擦系数

参数解析:
第一个参数 friction: 摩擦系数

(2) isFinished()

public final boolean isFinished()

方法描述:
滚动是否已结束,用于判断 Scroller 在滚动过程的状态,我们可以做一些终止或继续运行的逻辑分支。

(3) forceFinished(boolean finished)

public final void forceFinished(boolean finished)

方法描述:
强制的让滚动状态置为我们所设置的参数值 finished 。

(4) getDuration()

public final int getDuration()

方法描述:
返回 Scroller 将持续的时间(以毫秒为单位)。

(5) getCurrX()

public final int getCurrX()

方法描述:
返回滚动中的当前X相对于原点的偏移量,即当前坐标的X坐标。

(6) getCurrY()

public final int getCurrY()

方法描述:
返回滚动中的当前Y相对于原点的偏移量,即当前坐标的Y坐标。

(7) getCurrVelocity()

public float getCurrVelocity()

方法描述:
获取当前速度。

(8) computeScrollOffset()

public boolean computeScrollOffset()

方法描述:
计算滚动中的新坐标,会配合着 getCurrXgetCurrY 方法使用,达到滚动效果。值得注意的是,如果返回true,说明动画还未完成。相反,返回false,说明动画已经完成或是被终止了。

(9) startScroll

public void startScroll(int startX, int startY, int dx, int dy) public void startScroll(int startX, int startY, int dx, int dy, int duration)

方法描述:
通过提供起点,行程距离和滚动持续时间,进行滚动的一种方式,即 SCROLL_MODE。该方法可以用于实现像ViewPager的滑动效果。

参数解析:
第一个参数 startX: 开始点的x坐标
第二个参数 startY: 开始点的y坐标
第三个参数 dx: 水平方向的偏移量,正数会将内容向左滚动。
第四个参数 dy: 垂直方向的偏移量,正数会将内容向上滚动。
第五个参数 duration: 滚动的时长

(10) fling

public void fling(int startX, int startY, int velocityX, int velocityY,int minX, int maxX, int minY, int maxY)

方法描述:
用于带速度的滑动,行进的距离将取决于投掷的初始速度。可以用于实现类似 RecycleView 的滑动效果。

参数解析:
第一个参数 startX: 开始滑动点的x坐标
第二个参数 startY: 开始滑动点的y坐标
第三个参数 velocityX: 水平方向的初始速度,单位为每秒多少像素(px/s)
第四个参数 velocityY: 垂直方向的初始速度,单位为每秒多少像素(px/s)
第五个参数 minX: x坐标最小的值,最后的结果不会低于这个值;
第六个参数 maxX: x坐标最大的值,最后的结果不会超过这个值;
第七个参数 minY: y坐标最小的值,最后的结果不会低于这个值;
第八个参数 maxY: y坐标最大的值,最后的结果不会超过这个值;

值得一说:
minX <= 终止值的x坐标 <= maxX
minY <= 终止值的y坐标 <= maxY

(11) abortAnimation()

public void abortAnimation()

方法描述:
停止动画,值得注意的是,此时如果调用 getCurrX()getCurrY() 移动到的是最终的坐标,这一点和通过 forceFinished 直接将动画停止是不相同的。

3、小结

从上面的 API 讲解中,我们会发现,至始至终都没有对我们需要作用的View有任何的关联,而是通过计算,然后获取当前时间点对应的坐标,如此而已。这也就印证了前面的定义,至于怎么真正的使用,我们留到实战篇。

三、VelocityTracker

1、作用

同样先给出官方的英文类注释。小盆友以自己的理解给出这个的定义,VelocityTracker 是一个根据我们手指的触摸事件,计算出滑动速度的工具类,我们可以根据这个速度自行做计算进行视图的移动,达到粘性滑动之类的效果。

 * Helper for tracking the velocity of touch events, for implementing* flinging and other such gestures.

2、API讲解

这一小节是对 VelocityTracker 公有方法 进行讲解,如果您已经对这些方法很熟悉,可以跳过。

(1) obtain()

static public VelocityTracker obtain()

方法描述:
获取一个 VelocityTracker 对象。VelocityTracker的构造函数是私有的,也就是不能通过new来创建。

(2) recycle()

public void recycle()

方法描述:
回收 VelocityTracker 实例。

(3) clear()

public void clear()

方法描述:
重置 VelocityTracker 回其初始状态。

(4) addMovement(MotionEvent event)

public void addMovement(MotionEvent event)

方法描述:
为 VelocityTracker 传入触摸事件(包括ACTION_DOWNACTION_MOVEACTION_UP等),这样 VelocityTracker 才能在调用了 computeCurrentVelocity 方法后,正确的获得当前的速度。

(5) computeCurrentVelocity(int units)

public void computeCurrentVelocity(int units)

方法描述:
根据已经传入的触摸事件计算出当前的速度,可以通过getXVelocitygetYVelocity进行获取对应方向上的速度。值得注意的是,计算出的速度值不超过Float.MAX_VALUE

参数解析:
第一个参数 units: 速度的单位。值为1表示每毫秒像素数,1000表示每秒像素数。

(6) computeCurrentVelocity(int units, float maxVelocity)

public void computeCurrentVelocity(int units, float maxVelocity)

方法描述:
根据已经传入的触摸事件计算出当前的速度,可以通过getXVelocitygetYVelocity进行获取对应方向上的速度。值得注意的是,计算出的速度值不超过maxVelocity

参数解析:
第一个参数 units: 速度的单位。值为1表示每毫秒像素数,1000表示每秒像素数。
第二个参数 maxVelocity: 最大的速度,计算出的速度不会超过这个值。值得注意的是,这个参数必须是正数,且其单位就是我们在第一参数设置的单位。

(7) getXVelocity()

public float getXVelocity()

方法描述:
获取最后计算的水平方向速度,使用此方法前需要记得先调用computeCurrentVelocity

(8) getYVelocity()

 public float getYVelocity()

方法描述:
获取最后计算的垂直方向速度,使用此方法前需要记得先调用computeCurrentVelocity

(9) getXVelocity(int id)

public float getXVelocity(int id)

方法描述:
获取对应的手指id最后计算的水平方向速度,使用此方法前需要记得先调用computeCurrentVelocity

参数解析:
第一个参数 id: 触碰的手指的id

(10) getYVelocity(int id)

public float getYVelocity(int id)

方法描述:
获取对应的手指id最后计算的垂直方向速度,使用此方法前需要记得先调用computeCurrentVelocity

参数解析:
第一个参数 id: 触碰的手指的id

3、小结

VelocityTracker 的 API 简单明了,我们可以用记住一个套路。

  1. 在触摸事件为 ACTION_DOWN 或是进入 onTouchEvent 方法时,通过 obtain 获取一个 VelocityTracker ;
  2. 在触摸事件为 ACTION_UP 时,调用 recycle 进行释放 VelocityTracker;
  3. 在进入 onTouchEvent 方法或将 ACTION_DOWNACTION_MOVEACTION_UP 的事件通过 addMovement 方法添加进 VelocityTracker;
  4. 在需要获取速度的地方,先调用 computeCurrentVelocity 方法,然后通过 getXVelocitygetYVelocity 获取对应方向的速度;

四、实战——带惯性滑动的柱状图

1、效果图

github 地址:传送门

虽然我们是 ScrollerVelocityTracker 的实战,但我们还是有必要先略提一下柱子和点的绘制,以及其动画的大致思路。然后再加入 ScrollerVelocityTracker

2、绘制思路

我们来看下面这张小盆友手绘的解析图?,黑色的框代表CANVAS,蓝色的框代表用户看到的手机屏幕,深蓝色的框是我们真正每次需要绘制的区域。

从上图中,我们其实会发现一个规律,就是每隔一个 BarInterval 就绘制一个下图所示的柱子,循环的次数则由传入的数据量的个数决定。

但是,(敲黑板啦!!)值得注意的,在屏幕之外的柱子,其实对于用户来说是看不到的,我们也就没必要耗费这部分的资源来进行绘制,可以通过下面这段代码,判断柱子是否在可视区域内,可视区域的范围为屏幕的宽度各自往左和往右扩一个柱子的间隔 mBarInterval。这样做的原因是,描述的文字或小红点刚好在屏幕的左边界或右边界时,不会出现没有绘制的情况。

/*** 是否在可视的范围内** @param x* @return true:在可视的范围内;false:不在可视的范围内*/
private boolean isInVisibleArea(float x) {float dx = x - getScrollX();return -mBarInterval <= dx && dx <= mViewWidth + mBarInterval;
}

至此,图像的绘制问题就解决了,代码就不粘贴出来了,童鞋们可以进入传送门 跟着思路捋一捋。

还有一个问题,就是如何让画面跟着手指 移动 起来,这就需要重写 onTouchEvent 方法了,计算出手指的水平移动距离,然后通过 scrollBy 方法让内容移动起来。

值得一提,scrollToscrollBy 方法,都是针对 内容 或是说 canvas 进行移动。

至于如何让小红点动起来,这里使用了 ValueAnimator 进行从零至一的增加,达到不断接近目标坐标的效果。

对属性动画源码感兴趣的童鞋,可以移步小盆友的另一片博文:带有活力的属性动画源码分析与实战

3、如何惯性滑动起来

经过上一小节,我们已经知道如何绘制这一简单却又常见的柱形图了,但美中不足的就是没有 fling 的效果。所以我们需要先借住 VelocityTracker 进行获取我们当前手指的滑动速度,但这里需要注意的是,要限制其最大和最小速度。因为速度过快和过慢,都会导致交互效果不佳。获取代码如下

mMaximumVelocity = ViewConfiguration.get(context).getScaledMaximumFlingVelocity();
mMinimumVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();

然后根据我们在 VelocityTracker小结 中的套路,进行获取手指离屏时的水平速度。以下是只保留 VelocityTracker 相关代码

/*** 控制屏幕不越界** @param event* @return*/
@Override
public boolean onTouchEvent(MotionEvent event) {// 省略无关代码...if (mVelocityTracker == null) {mVelocityTracker = VelocityTracker.obtain();}mVelocityTracker.addMovement(event);if (MotionEvent.ACTION_DOWN == event.getAction()) {// 省略无关代码...} else if (MotionEvent.ACTION_MOVE == event.getAction()) {// 省略无关代码...} else if (MotionEvent.ACTION_UP == event.getAction()) {// 计算当前速度, 1000表示每秒像素数等mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);// 获取横向速度int velocityX = (int) mVelocityTracker.getXVelocity();// 速度要大于最小的速度值,才开始滑动if (Math.abs(velocityX) > mMinimumVelocity) {// 省略无关代码...}if (mVelocityTracker != null) {mVelocityTracker.recycle();mVelocityTracker = null;}}return super.onTouchEvent(event);
}

获取完水平的速度,接下来我们需要进行真正的 fling 效果。通过一个线程来进行不断的 移动 画布,从而达到滚动效果(RecycleView中的滚动也是通过线程达到效果,有兴趣的同学可以进入RecycleView 的源码进行查看,该线程类的名字为 ViewFlinger )。

/*** 滚动线程*/
private class FlingRunnable implements Runnable {private Scroller mScroller;private int mInitX;private int mMinX;private int mMaxX;private int mVelocityX;FlingRunnable(Context context) {this.mScroller = new Scroller(context, null, false);}void start(int initX,int velocityX,int minX,int maxX) {this.mInitX = initX;this.mVelocityX = velocityX;this.mMinX = minX;this.mMaxX = maxX;// 先停止上一次的滚动if (!mScroller.isFinished()) {mScroller.abortAnimation();}// 开始 flingmScroller.fling(initX, 0, velocityX,0, 0, maxX, 0, 0);post(this);}@Overridepublic void run() {// 如果已经结束,就不再进行if (!mScroller.computeScrollOffset()) {return;}// 计算偏移量int currX = mScroller.getCurrX();int diffX = mInitX - currX;// 用于记录是否超出边界,如果已经超出边界,则不再进行回调,即使滚动还没有完成boolean isEnd = false;if (diffX != 0) {// 超出右边界,进行修正if (getScrollX() + diffX >= mCanvasWidth - mViewWidth) {diffX = (int) (mCanvasWidth - mViewWidth - getScrollX());isEnd = true;}// 超出左边界,进行修正if (getScrollX() <= 0) {diffX = -getScrollX();isEnd = true;}if (!mScroller.isFinished()) {scrollBy(diffX, 0);}mInitX = currX;}if (!isEnd) {post(this);}}/*** 进行停止*/void stop() {if (!mScroller.isFinished()) {mScroller.abortAnimation();}}
}

最后就是使用起这个线程,而使用的地方主要有两个点,一个手指按下时(即MotionEvent.ACTION_DOWN)和手指抬起时(即 MotionEvent.ACTION_UP ),删除了不相关代码,剩余代码如下。

public boolean onTouchEvent(MotionEvent event) {// 省略不相关代码...if (MotionEvent.ACTION_DOWN == event.getAction()) {// 省略不相关代码...mFling.stop();} else if (MotionEvent.ACTION_MOVE == event.getAction()) {// 省略不相关代码...} else if (MotionEvent.ACTION_UP == event.getAction()) {// 省略不相关代码...// 速度要大于最小的速度值,才开始滑动if (Math.abs(velocityX) > mMinimumVelocity) {int initX = getScrollX();int maxX = (int) (mCanvasWidth - mViewWidth);if (maxX > 0) {mFling.start(initX, velocityX, initX, maxX);}}// 省略不相关代码...}return super.onTouchEvent(event);}

当我们 MotionEvent.ACTION_DOWN 时,我们需要停止滚动的效果,达到立马停止到手指触碰的地方。

当我们 MotionEvent.ACTION_UP 时,我们需要计算 fling 方法所需的最小值和最大值。根据我们在线程中的计算方式,所以我们的最小值初始值getScrollX() 的值 而最大值mCanvasWidth - mViewWidth

最后开启线程,便达到了我们看到的效果。

完整代码的github 地址:传送门

五、写在最后

ScrollerVelocityTracker 的搭配使用,能让我们的控件使用起来更加丝滑,交互感更强,当然用户体验就越好。最后如果你从这篇文章有所收获,请给我个赞❤️,并关注我吧。文章中如有理解错误或是晦涩难懂的语句,请评论区留言,我们进行讨论共同进步。你的鼓励是我前进的最大动力。

让控件如此丝滑Scroller和VelocityTracker的API讲解与实战——Android高级UI相关推荐

  1. Android中自定义农历日历,CalendarView Android 上一个优雅、万能自定义 UI、性能高效的日历控件,热插拔!热插拔!热插拔!重要的事 @codeKK Android开源站...

    An elegant CalendarView on Android platform. Freely draw UI with canvas, fast.efficient and low memo ...

  2. 第一站小红书图片裁剪控件之二,自定义CoordinatorLayout联动效果

    本篇续: 第一站小红书图片裁剪控件,深度解析大厂炫酷控件 先来看看几张效果图: emmmm,想感受高清丝滑的动画效果,有以下两种方式: https://github.com/HpWens/MeiWid ...

  3. 像小红书一样的图片裁剪控件联动效果

    今日科技快讯 据CNBC报道,美国法官已经要求特斯拉首席执行官埃隆·马斯克(Elon Musk)在未来两周内设法与美国证券交易委员会(SEC)达成和解协议.否则,法院将决定是否判马斯克犯有藐视法庭罪. ...

  4. Android 最丝滑的动画--第二篇MotionLayout概述(后面陆续更新)(带效果图)

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 一. MotionLayout概述 MotionLayout 是谷歌推出的一种布局类型,可帮助管理应用中的运动和微件动画.Motion ...

  5. 粘性控件,滑动停留StickLayout(导航栏滑动停留)

    我们平时在使用APP的时候,经常可以见到一些导航栏滑到顶端就停留,而下面的控件可以接着滑动:今天,我就给大家介绍一个非常好用的滑动粘性控件StickLayout,它不仅可以让其任意一个直接子控件滑动停 ...

  6. android 固定底部 布局_Android系统列表控件

    在android系统控件中,有多个控件可以展示列表数据. 一.ListView 该组件是android中最常用的一个UI组件,用于实现在屏幕上显示多个内容,以便于我们用手指进行滑动. ListView ...

  7. JavaFX UI控件教程(二)之JavaFX UI控件

    翻译自  JavaFX UI控件 本章概述了通过API提供的JavaFX UI控件. JavaFX UI控件是使用场景图中的节点构建的.因此,控件可以使用JavaFX平台的视觉丰富功能.由于JavaF ...

  8. android listview添加数据_Android系统列表控件

    在android系统控件中,有多个控件可以展示列表数据. 一.ListView 该组件是android中最常用的一个UI组件,用于实现在屏幕上显示多个内容,以便于我们用手指进行滑动. ListView ...

  9. Android仿同花顺自选股列表控件

    介绍 RecyclerView的开发中,我们通常会遇到一行显示不下内容的情况,产品会要求我们的item是可以滚动的,并且头部是固定的.特别在股票行情类相关的app上,这样的场景是非常多的,所以封装了如 ...

  10. Android开源控件收集整理

    一 .基本控件 TextView HTextView 一款支持TextView文字动画效果的Android组件库.GitHub - hanks-zyh/HTextView: Animation eff ...

最新文章

  1. jquery操作select取值赋值与设置选中[转]
  2. 产品诞生过程--导图
  3. Item 14 In public classes, use accessor methods, not public fields
  4. RabbitMQ从入门到精通
  5. java包裹邮费计算_GitHub - honghailiang/FreightSystem: 基于Java Swing编写的简易运费计算工具...
  6. Qt:Windows编程—代码注入
  7. 云原生解决了什么问题?
  8. 执行DBMS_METADATA.get_ddl报ORA-39212的解决方法
  9. 每日总结 神州数码DCWS
  10. 深度学习中的 Attention 机制总结与代码实现(2017-2021年)
  11. 物联网时代的技术迷雾
  12. 同时开多个独立窗口Visio 2003/2007版本的软件
  13. 软件测试中的接口分析,软件测试接口测试之管理类—叩丁狼分享
  14. ABB变频器维修,ABB变频器,ABB变频器配件FS300R12KE3/AGDR-61C 驱动模块APOW-01C 电源板AINP-01C 可控硅触发板
  15. ARCGIS小工具(插件)免费版_自取_GIS插件_工具_其他
  16. 基于NNIE神经网络引擎_海思hi3516DV300方案硬件平台适合做哪些开发
  17. hacker 入门指南
  18. python中Try的运用及意义
  19. 计算机excel教程,电脑安装excel教程的方法步骤详解
  20. 访问ip不在白名单中,请参考FAQ:

热门文章

  1. java对字符串集合按字符串长度排序
  2. Audition 如何录制电脑内部声音
  3. 《Unity Shader入门精要》冯乐乐著 书中彩图
  4. dwg格式的计算机图,dwg是什么文件 怎么打开【图文】
  5. 3d max 快捷键
  6. 普通化学三个单元总结
  7. soap报文解析 php,soap 返回报文解析
  8. gRPC接口性能测试
  9. ncm在线转换mp3格式
  10. 数字时钟word clock Mac设置教程