1、scroller概念

scroller是对滑动操作的一种封装。它记录滑动过程中view应有的偏移量,但不主动作用于view。需要额外的操作将这些偏移量设置给view,从而产生滑动现象。如果不进行这些操作的话是看不到滑动现象的。
这个类有点类似 ValueAnimator 动画, 只产生动画过程中对应的值,需要我们手动将这些值设置到对应的view上从而产生动画。

2、view内容移动

上面说过scroller是辅助view进行移动内容的,那么view是如何实际移动内容的呢?通过查看官方文档及代码可以知道,view提供了两个滚动的方法,一个是scrollTo,一个是scrollBy,方法截图如下:

图一、scrollTo方法

图二、scrollBy方法

scrollTo 顾名思义,该方法是将内容移动到指定的地方,scrollTo的参数指定的绝对坐标(从参数名也可以看出端倪)。即把我移动到xx位置。 多次调用都是一样的效果。
而 scrollBy 是每次移动一定的距离,像挤牙膏一样。调用一次移动(dx,dy)距离。从源码中也可以看出是在现有的滚动量基础之上进行累加的。
但是从源码中看,并没有改变绘制的逻辑。那么是如何移动的内容的呢?原来这两个方法改变的主要是mScrollX与mScrollY这两个变量,然后触发重绘,然后在draw方法中使用这两个变量来移动画布实现内容的移动。 View的draw方法部分代码截图如下:

图三、view的绘制方法
从代码中可以看出,在确定绘制区域时使用了mScrollX和mScrollY,所以在绘制时内容实现相应的移动。 从这里也可以知道view本身并没有被移动,移动的是view的内容。

通过上面两个方法,我们就可以对view的内容进行移动了,但是如果我们要平滑移动view的内容该怎么办呢?android系统为我们提供了Scroller类来实现view平滑的移动。

3、scroller的使用

scroller有如下两个构造函数。

/*** Create a Scroller with the default duration and interpolator.*/
public Scroller(Context context) {this(context, null);
}/*** Create a Scroller with the specified interpolator. If the interpolator is* null, the default (viscous) interpolator will be used. "Flywheel" behavior will* be in effect for apps targeting Honeycomb or newer.*/
public Scroller(Context context, Interpolator interpolator) {this(context, interpolator,context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.HONEYCOMB);
}

这二者的区别在于一个提供了插值器, 一个使用了默认的。
在Scroller类中提供了两个比较重要的方法,一个是startScroll,一个是fling。方法源码如下:

图四、startScroll方法

图五、fling方法

从源码中可以看到,这两个方法主要是对一些成员变量进行赋值,如起始点x、y,终点x、y ,动画时间等等。 那Scroller是如何计算平滑过程中的中间值的呢?这就要靠Scroller类中一个比较重要的 computeScrollOffset() 方法了。

4、Scroller类的computeScrollOffset方法分析

在scroller的 computeScrollOffset 方法中,根据滚动的起始位置、速度大小、滚动时间等参数,计算出当前位置值。 computeScrollOffset方法源码如下:

public boolean computeScrollOffset() {if (mFinished) {return false;}int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);if (timePassed < mDuration) {switch (mMode) {case SCROLL_MODE:final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);// mDeltaX 与 mDeltaY是要移动的距离。 mCurrX = mStartX + Math.round(x * mDeltaX);mCurrY = mStartY + Math.round(x * mDeltaY);break;case FLING_MODE:final float t = (float) timePassed / mDuration;final int index = (int) (NB_SAMPLES * t);float distanceCoef = 1.f;float velocityCoef = 0.f;if (index < NB_SAMPLES) {// 这一坨估计是模拟物理衰减的计算过程.final float t_inf = (float) index / NB_SAMPLES;final float t_sup = (float) (index + 1) / NB_SAMPLES;final float d_inf = SPLINE_POSITION[index];final float d_sup = SPLINE_POSITION[index + 1];velocityCoef = (d_sup - d_inf) / (t_sup - t_inf);distanceCoef = d_inf + (t - t_inf) * velocityCoef;}mCurrVelocity = velocityCoef * mDistance / mDuration * 1000.0f;mCurrX = mStartX + Math.round(distanceCoef * (mFinalX - mStartX));// 保证当前值在约定的最大值与最小值之间。 // Pin to mMinX <= mCurrX <= mMaxXmCurrX = Math.min(mCurrX, mMaxX);mCurrX = Math.max(mCurrX, mMinX);mCurrY = mStartY + Math.round(distanceCoef * (mFinalY - mStartY));// Pin to mMinY <= mCurrY <= mMaxYmCurrY = Math.min(mCurrY, mMaxY);mCurrY = Math.max(mCurrY, mMinY);if (mCurrX == mFinalX && mCurrY == mFinalY) {mFinished = true;}break;}} else {mCurrX = mFinalX;mCurrY = mFinalY;mFinished = true;}return true;
}

从源码中看,主要是计算出mCurrX与mCurrY这两个变量。这两个变量是根据startScroll或者fling方法给定的参数结合动画时间计算出来的。 后面View要依据这两个变量进行移动。 同时,这个方法返回了一个boolean的返回值,标识滚动是否已结束。
但是这个方法调用一次,只回计算一次当前的位置值,那么如何持续的调用并产生连续的位置值呢?有没有合适的地方持续调用呢?

在View中有一个空方法computeScroll(), 该方法在界面重绘时便会调用。故我们可以在此处调用Scroller的computeScrollOffset方法。示例如下:

@Override
public void computeScroll() {  super.computeScroll();  if (mScroller.computeScrollOffset()) {// 如果滚动没结束,就一直调用scrollTo方法,改变view的mScrollX与mScrollY。然后在触发重绘。// 重绘后又会调用computeScroll,直到滚动结束为止。scrollTo(mScroller.getCurrX(),mScroller.getCurrY());invalidate();  }
}

这个过程有点像在一个Runnable中使用Handler来post自身一样,形成一个持续的过程,直到某个条件不符合时终止。

3、fling问题

在使用startScroll方法时,我们只需要传入起始点坐标及需要滑动的距离就可以进行平滑移动了。 但是对于fling就没这么简单了。
fling方法的签名如下:

startX为开始fling时的x坐标
startY为开始fling时的y坐标
velocityX为fling时x方向上的速度
velocityY为fling时y方向上的速度
minX、maxX为fling停止时x坐标的最小值与最大值,当计算出来的最终值超出这个范围时会取这两个边界值。
minY、maxY,与x方向的最大值、最小值同理。

如上的参数中,起始坐标及最大最小值比较容易确定,但是两个速度就不太容易确定了。
fling时的速度问题可以通过如下两种方式获取。
1、velocitytractor来手动计算
2、使用GestureDectector类计算。

在使用时推荐使用第二种方式来计算,该类是android系统提供的,能提供比较好的用户体验。

4、demo演示

在自定义View中加入如下代码,便可以基本实现随手势移动和抛掷的效果了。

private void init(Context context) {mScroller = new Scroller(context);mGestureDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {@Overridepublic boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {scrollBy((int) distanceX, (int) distanceY);return super.onScroll(e1, e2, distanceX, distanceY);}@Overridepublic boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {// 这个地方的速度与实际手势操作是相反的,故取反了。mScroller.fling(getScrollX(), getScrollY(), (int) -velocityX, (int) -velocityY, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE);return super.onFling(e1, e2, velocityX, velocityY);}@Overridepublic boolean onDown(MotionEvent e) {if (!mScroller.isFinished()) {mScroller.abortAnimation();}// 保证消费了down事件,后续的事件才会下发到该控件。return true;}});
}@Override
public boolean onTouchEvent(MotionEvent event) {boolean res = mGestureDetector.onTouchEvent(event);return res || super.onTouchEvent(event);
}@Override
public void computeScroll() {super.computeScroll();if (mScroller != null && mScroller.computeScrollOffset()) {scrollTo(mScroller.getCurrX(), mScroller.getCurrY());// 注意这里一定要主动调用一次invalidate。invalidate();}
}

5、注意事项

1、view的scrollX、scrollY的正负与实际的感官是相反的。以水平方向为例,向右滚动时,滚动量是负值。向左滚动时,滚动量是正值。
结合图二中View的绘制源码可以看到,当向右滚动时,为了显示左边的内容,left只有变小才能让canvas绘制左边的内容,所以scrollX必须为负值。同理向右、向上及向下。
2、在使用时注意, mScroller.getCurrY()及mScroller.getCurrX() 这个值在停止滑动后会保持在最终值的状态。要注意业务逻辑,是否在合理的范围内使用该值。比如,在停止滑动后仍通过该方法获取值就要注意了。
3、在一些复杂场景下,可能尝试将fling操作转化了scroll,将滚动量分成一小段一小段,然后调用scrollBy实现抛掷效果。
4、当view不可见时,调用invalidate是不会触发重绘的,故也无法调用computeScroll。

6、参考

android坐标

scroller基础知识点相关推荐

  1. Python培训教程之Python基础知识点梳理

    Python语言是入门IT行业比较快速且简单的一门编程语言,学习Python语言不仅有着非常大的发展空间,还可以有一个非常好的工作,下面小编就来给大家分享一篇Python培训教程之Python基础知识 ...

  2. 自然语言处理算法工程师历史最全资料汇总-基础知识点、面试经验

    2019年秋招已过,零星的招聘任然在继续.本资源适用于NLP算法工程师面试,也适用于算法相关的其他岗位.整理了算法面试需要数学基础知识.编程语言.深度学习.机器学习.计算机理论.统计学习.自然语言处理 ...

  3. java重要基础知识点_必看 | 新人必看的Java基础知识点大梳理

    原标题:必看 | 新人必看的Java基础知识点大梳理 各位正在认真苦学Java的准大神,在这烈日炎炎的夏季里,老九君准备给大家带来一个超级大的"冰镇西瓜,"给大家清凉一下,压压惊. ...

  4. mysql 存储引擎 面试_搞定PHP面试 - MySQL基础知识点整理 - 存储引擎

    MySQL基础知识点整理 - 存储引擎 0. 查看 MySQL 支持的存储引擎 可以在 mysql 客户端中,使用 show engines; 命令可以查看MySQL支持的引擎: mysql> ...

  5. 布尔值_Python基础知识点手册——布尔值及布尔运算

    布尔值及布尔运算 布尔值有 True 和 False,布尔类型是整数类型的子类型,所以整数的运算都适用布尔值运算. issubclass(bool,int) True True + 1 2 ~True ...

  6. python基础知识整理-整理了27个新手必学的Python基础知识点

    原标题:整理了27个新手必学的Python基础知识点 1.执行脚本的两种方式 Python a.py 直接调用Python解释器执行文件 chomd +x a.py ./a.py #修改a.py文件的 ...

  7. python基础知识整理-python爬虫基础知识点整理

    首先爬虫是什么? 网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动的抓取万维网信息的程序或者脚本. 根据我的经验,要学习Python爬虫 ...

  8. Python2.7基础知识点思维导图

    2019独角兽企业重金招聘Python工程师标准>>> 特别感谢廖雪峰官方网站! 这个思维导图是学习Python2.7时罗列的知识点,能够帮助快速回忆基础知识点,分享给各位. 思维导 ...

  9. 计算机知识必备,小结||计算机基础知识点十(必备)

    原标题:小结||计算机基础知识点十(必备) 451.数据库管理系统主要功能: (1)数据定义功能 (2)数据操纵功能 (3)数据库的运行管理 (4)数据库的建立和维护功能 452.数据库不仅要反映数据 ...

最新文章

  1. linux系统的编译原理,GCC编译原理_Linux编程_Linux公社-Linux系统门户网站
  2. [置顶]       Javascript js中页面的重新加载
  3. 算法之道:形而之上谓之道
  4. 登录框显示,错误:Cookies因预料之外的输出被阻止
  5. boost::math::statistics相关用法的测试程序
  6. es6 数组合并_九个前端开发必学超级实用的 ES6 特性
  7. 2015.12.23 OC中的字符串(NSStringNSMutableString) 数组(NSArrayNSMutableArray)
  8. 判断.java文件中getConnection与cleanUp数量是否匹配
  9. REST与Apache Camel
  10. 将 Palo Alto Networks 连接到 Azure Sentinel
  11. Dubbo的基本介绍和搭建一个Dubbo环境
  12. Spring Cloud Gateway (六) 自定义 Global Filter
  13. 我的 HTTP/1.1 好慢啊!
  14. 免费课程:Java高级教程-项目部分视频——私塾在线提供
  15. 超低插损的新材料射频开关(PCM RF switch)的新进展
  16. Git 彻底删除大文件
  17. 简要介绍一下Dos/Windows格式文件和Unix/Linux格式文件(剪不断理还乱的\r\n和\n)
  18. 【每日新闻】雷军:5G+AIoT是下一代的超级互联网
  19. 【T3】打印单据(非新打印)表头显示不全
  20. 论文阅读:Detecting Visual Relationships with Deep Relational Networks

热门文章

  1. oracle10g rac ocssd,求教:安装oracle10g rac 报crs-0223错误问题
  2. MPEG TS流简介
  3. K8S(二)安装配置篇
  4. PHP如何在照片下面写一行字_怎样在手机照片下方留白加文字?
  5. 实现用户注册功能的代码
  6. 黑马程序员就业班第二天的总结以及自己的看法
  7. 重庆师范大学第一届ACM选拔赛
  8. 基于麒麟座继续串口--DMA可以顺手开启-它是ADD不影响
  9. java高性能rpc,企业级rpc,zk调度,负载均衡,泛化调用一体的rpc服务框架
  10. Incapsula专业提供后门特洛伊保护