滚动视图的方法有两种:scrollTo和scrollBy,而Scroller就是它们的辅助工具类,所以Scroller是学好高级UI必不可少的一课。

(1)scrollTo、scrollBy、getScrollX、getScrollY

view的内容本身具备滚动的方法,其中滚动方法如下:

scrollTo:相对于初始位置移动

scrollBy:相对于上次移动的最后位置移动

这两个方法特别需要注意以下几点:

两者移动的都是view的内容,view本身是不移动的,所以getX和getY的值不会受到这两个方法的影响;

不要再在onDraw中调用这两个方法,避免onDraw方法被重复执行,因为一旦调用这两个方法view会被重绘,onDraw方法会再次执行。

view内容滚动的方法有了,那么该如何获取view内容被滚动的距离呢?看以下两个方法:

getScrollX:获取view的内容在X轴滚动的距离

getScrollY:获取view的内容在Y轴滚动的距离

以上只说到view内容的滚动,那么view本身的移动用什么方法呢?

答:setX和setY方法。

本文的重点内容是Scroller,这个辅助类的作用不是view本身的移动,而是view内容的滚动,下面开始简单说明一下Scroller辅助类。

(2)熟悉Scroller的构造方法

//默认插值器是ViscousFluidInterpolator

Scroller mScroller = new Scroller(mContext);

//指定一个插值器

Scroller mScroller = new Scroller(mContext, new AccelerateDecelerateInterpolator());

//指定一个插值器,第三个参数表示是否开启“飞轮”效果,也就是多次滚动时速度叠加

Scroller mScroller = new Scroller(mContext, new AccelerateDecelerateInterpolator(), false);

(3)熟悉插值器

图片.png

Scroller其实就是在scrollTo(x, y)和scrollBy(x, y)的基础上添加滚动效果,滚动效果是一个动画,当我们new一个Scroller对象时,就已经指定了一个插值器,下面来说明一下各种插值器:

ViscousFluidInterpolator:这是一个默认插值器,当构造Scroller时,如果不传递插值器或者插值器为null时,系统默认使用ViscousFluidInterpolator插值器。

AccelerateDecelerateInterpolator:在动画开始与结束的时候速率改变比较慢,在中间的时候速率较快。

AccelerateInterpolator:在动画开始的地方速率改变比较慢,然后开始加速。

AnticipateInterpolator:开始的时候向后然后向前甩。

AnticipateOvershootInterpolator:开始的时候向后然后向前甩一定值后返回最后的值。

BounceInterpolator:反弹插值器。

CycleInterpolator:动画循环播放特定的次数,速率改变沿着正弦曲线。

DecelerateInterpolator:在动画开始的地方快然后慢。

LinearInterpolator:以常量速率改变。

OvershootInterpolator:向前甩一定值后再回到原来位置。

PathInterpolator:路径插值器,我们可以按照自己想要的轨迹滚动。

PathInterpolator(Path path)

PathInterpolator(float controlX, float controlY)

PathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2)

FastOutLinearInInterpolator:MaterialDesign基于贝塞尔曲线的插补器效果:依次慢慢快。

FastOutSlowInInterpolator:基于贝塞尔曲线的插补器效果:依次慢快慢

LinearOutSlowInInterpolator:基于贝塞尔曲线的插补器效果:依次快慢慢

以上的插值器运用比较广泛,在Scroller中设置一个插值器可以优化滚动的效果。

(4)Scroller滑动辅助类的基本方法

Scroller本身不会去滚动view,它只是一个滚动计算辅助类,用于跟踪控件滑动的轨迹,只相当于一个滚动轨迹记录工具,最终还是通过View的scrollTo、scrollBy方法实现view的滚动。

getCurrX()

获取mScroller当前水平滚动的位置

getCurrY

获取mScroller当前竖直滚动的位置

getFinalX

获取mScroller最终停止的水平位置

getFinalY

获取mScroller最终停止的竖直位置

startScroll()

开始滚动动画:

startX:滚动的x方向起始点

startY:滚动的y方向起始点

dx:x方向的偏移量

dy:y方向的偏移量

duration:滚动所消耗的时间,默认为250毫秒

startScroll(int startX, int startY, int dx, int dy)

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

fling()

惯性滑动,参数如下:

startX:滚动起始点x

startY:滚动起始点y

velocityX:x轴方向的速度

velocityY:y轴方向的速度

minX:x轴最小滚动距离(注意直角坐标系)

maxX:x轴最大滚动距离(注意直角坐标系)

minY:y轴最小滚动距离(注意直角坐标系)

maxY:y轴最大滚动距离(注意直角坐标系)

computeScrollOffset()

判断滚动动画是否结束:

true:滚动尚未完成

false:滚动已经完成

(5)Scroller实现滚动效果

public class TestView extends View {

private float mDownX = 0;

private float mDonwY = 0;

private float move_x = 0;

private float move_y = 0;

private int finalX = 0;

private int finalY = 0;

private Paint mPaint;

private Scroller mScroller;

public TestView(Context context) {

super(context);

init(context);

}

public TestView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

init(context);

}

public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

init(context);

}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)

public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {

super(context, attrs, defStyleAttr, defStyleRes);

init(context);

}

private void init(Context mContext){

mPaint = new Paint();

mPaint.setTextSize(80);

mScroller = new Scroller(mContext);

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.drawText("我是中国人!", 0, 100, mPaint);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

float x = event.getX();

float y = event.getY();

switch (event.getAction()){

case MotionEvent.ACTION_DOWN:

//标志着第一个手指按下

mDownX = x;//获取按下时x坐标值

mDonwY = y;//获取按下时y坐标值

break;

case MotionEvent.ACTION_MOVE:

//按住一点手指开始移动

move_x = mDownX - x;//计算当前已经移动的x轴方向的距离

move_y = mDonwY - y;//计算当前已经移动的y轴方向的距离

//开始滚动动画

//第一个参数:x轴开始位置

//第二个参数:y轴开始位置

//第三个参数:x轴偏移量

//第四个参数:y轴偏移量

mScroller.startScroll(finalX, finalY, (int) move_x, (int) move_y, 0);

invalidate();//目的是重绘view,是的执行computeScroll方法

break;

case MotionEvent.ACTION_UP:

finalX = mScroller.getFinalX();

finalY = mScroller.getFinalY();

break;

}

return true;

}

@Override

public void computeScroll() {

super.computeScroll();

if(mScroller.computeScrollOffset()){//判断滚动是否完成,true说明滚动尚未完成,false说明滚动已经完成

scrollTo(mScroller.getCurrX(),mScroller.getCurrY());//将view直接移动到当前滚动的位置

invalidate();//触发view重绘

}

}

}

效果如下:

51.gif

当发生ACTION_MOVE事件时,执行startScroll方法开始滚动view,由于ACTION_MOVE事件发生的特别频繁,所以startScroll方法的最后一个参数设置为0ms。

当然,可以将startScroll方法放在ACTION_UP事件中执行,调整代码:

@Override

public boolean onTouchEvent(MotionEvent event) {

float x = event.getX();

float y = event.getY();

switch (event.getAction()){

case MotionEvent.ACTION_DOWN:

//标志着第一个手指按下

mDownX = x;//获取按下时x坐标值

mDonwY = y;//获取按下时y坐标值

break;

case MotionEvent.ACTION_MOVE:

//按住一点手指开始移动

move_x = mDownX - x;//计算当前已经移动的x轴方向的距离

move_y = mDonwY - y;//计算当前已经移动的y轴方向的距离

break;

case MotionEvent.ACTION_UP:

finalX = mScroller.getFinalX();

finalY = mScroller.getFinalY();

//开始滚动动画

//第一个参数:x轴开始位置

//第二个参数:y轴开始位置

//第三个参数:x轴偏移量

//第四个参数:y轴偏移量

mScroller.startScroll(finalX, finalY, (int) move_x, (int) move_y, 3000);

invalidate();//目的是重绘view,是的执行computeScroll方法

break;

}

return true;

}

代码的意思是:从一个坐标到另一个坐标的滑动需要3秒时间,当手指松开时开始执行滚动动画,动画时长为3秒。

效果如下:

52.gif

(6)Scroller实现惯性滚动效果

惯性滚动是指,手指松开view后,根据当前速度再滑动一段距离,就跟惯性类似。

实现惯性滚动效果的方法是fling(),执行fling()方法的时机是MotionEvent.ACTION_UP事件,代码实现如下:

public class TestView extends View {

//惯性滑动速度追踪类

private VelocityTracker velocityTracker;

private float mDownX = 0;

private float mDonwY = 0;

private float move_x = 0;

private float move_y = 0;

private int finalX = 0;

private int finalY = 0;

private int xVelocity = 0;

private int yVelocity = 0;

private Paint mPaint;

private Scroller mScroller;

private OverScroller mOverScroller;

public TestView(Context context) {

super(context);

init(context);

}

public TestView(Context context, @Nullable AttributeSet attrs) {

super(context, attrs);

init(context);

}

public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {

super(context, attrs, defStyleAttr);

init(context);

}

@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)

public TestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {

super(context, attrs, defStyleAttr, defStyleRes);

init(context);

}

private void init(Context mContext){

mPaint = new Paint();

mPaint.setTextSize(80);

mPaint.setColor(Color.RED);

mPaint.setStrokeWidth(50);

mScroller = new Scroller(mContext);

mOverScroller = new OverScroller(mContext);

}

@Override

protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

canvas.drawText("我是中国人!", 0, 100, mPaint);

}

@Override

public boolean onTouchEvent(MotionEvent event) {

float x = event.getX();

float y = event.getY();

switch (event.getAction()){

case MotionEvent.ACTION_DOWN:

//标志着第一个手指按下

mDownX = x;//获取按下时x坐标值

mDonwY = y;//获取按下时y坐标值

//创建惯性滑动速度追踪类对象

velocityTracker = VelocityTracker.obtain();

break;

case MotionEvent.ACTION_MOVE:

//按住一点手指开始移动

move_x = mDownX - x;//计算当前已经移动的x轴方向的距离

move_y = mDonwY - y;//计算当前已经移动的y轴方向的距离

//开始滚动动画

//第一个参数:x轴开始位置

//第二个参数:y轴开始位置

//第三个参数:x轴偏移量

//第四个参数:y轴偏移量

if(mScroller.isFinished()){

mScroller.startScroll(finalX, finalY, (int) move_x, (int) move_y, 0);

}

invalidate();//目的是重绘view,是的执行computeScroll方法

//将事件加入到VelocityTracker类实例中

velocityTracker.addMovement(event);

//计算1秒内滑动的像素个数

velocityTracker.computeCurrentVelocity(1000);

//X轴方向的速度

xVelocity = (int) velocityTracker.getXVelocity();

//Y轴方向的速度

yVelocity = (int) velocityTracker.getYVelocity();

break;

case MotionEvent.ACTION_UP:

//获取认为是fling的最小速率

int mMinimumFlingVelocity= ViewConfiguration.get(getContext()).getScaledMaximumFlingVelocity() / 10;

if (Math.abs(xVelocity) >= mMinimumFlingVelocity || Math.abs(yVelocity) > mMinimumFlingVelocity) {

Log.d("yunchong", "触发惯性滑动");

mScroller.fling(getScrollX(), getScrollY(), -xVelocity, -yVelocity, -getWidth()+100, 0, -getHeight()+100, 0);

} else {//缓慢滑动不处理

}

finalX = mScroller.getFinalX();

finalY = mScroller.getFinalY();

velocityTracker.recycle();

velocityTracker.clear();

velocityTracker = null;

break;

case MotionEvent.ACTION_CANCEL:

velocityTracker.recycle();

velocityTracker.clear();

velocityTracker = null;

break;

}

return true;

}

@Override

public void computeScroll() {

super.computeScroll();

if(mScroller.computeScrollOffset()){//判断滚动是否完成,true说明滚动尚未完成,false说明滚动已经完成

scrollTo(mScroller.getCurrX(),mScroller.getCurrY());//将view直接移动到当前滚动的位置

invalidate();//触发view重绘

}

}

}

效果展示:

52.gif

[本章完...]

android scroller,高级UI第四十四篇:Android Scroller详解相关推荐

  1. MySQL数据库,从入门到精通:第十二篇——MySQL数据类型详解

    MySQL数据库,从入门到精通:第十二篇--MySQL数据类型详解 第 12 章_MySQL数据类型精讲 1. MySQL中的数据类型 2. 整数类型 2. 1 类型介绍 2. 2 可选属性 2. 2 ...

  2. Android UI开发第二十九篇——Android中五种常用的menu(菜单)

    Android Menu在手机的应用中起着导航的作用,作者总结了5种常用的Menu. 1.左右推出的Menu 前段时间比较流行,我最早是在海豚浏览器中看到的,当时耳目一新.最早使用左右推出菜单的,听说 ...

  3. Linux内存管理(四十):Linux PSI 详解

    源码基于:Linux 5.4 0. 前言 之前在<Linux PSI 指标>一文中简单的描述了 PSI 指标的意义,以及PSI 出现的历史过程. 在PSI 之前,Linux 也有一些资源压 ...

  4. MySQL数据库,从入门到精通:第十四篇——MySQL视图详解

    MySQL数据库,从入门到精通:第十四篇--MySQL视图详解 第 14 篇_视图 1. 常见的数据库对象 2. 视图概述 2. 1 为什么使用视图? 2. 2 视图的理解 3. 创建视图 3. 1 ...

  5. 万字长文告诉新手如何学习Python图像处理(上篇完结 四十四) | 「Python」有奖征文

    该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门.OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化算子.图像增强技术.图像分割等,后期结合深度学习研究图像识别 ...

  6. Android UI开发第二十五篇——分享一篇自定义的 Action Bar

    Action Bar是android3.0以后才引入的,主要是替代3.0以前的menu和tittle bar.在3.0之前是不能使用Action Bar功能的.这里引入了自定义的Action Bar, ...

  7. Python编程基础:第四十四节 方法重写Method Overriding

    第四十四节 方法重写Method Overriding 前言 实践 前言 我们前面说了,子类继承于父类,可以调用父类的所有属性和方法.那么如果我们想在继承的过程中重新书写父类的某些方法,此时就用到了方 ...

  8. 四十四种Javascript技巧大全

    四十四种Javascript技巧大全 1.第一个给变量分配值时不要忘了var关键字. 分配值给未定义的变量将是该变量自动成为全局变量,应该避免全局变量. 2.使用 === 而不是 == 使用 == ( ...

  9. 四十四、深入Java 的序列化和反序列化

    @Author:Runsen @Date:2020/6/8 作者介绍:Runsen目前大三下学期,专业化学工程与工艺,大学沉迷日语,Python, Java和一系列数据分析软件.导致翘课严重,专业排名 ...

  10. [系统安全] 四十四.APT系列(9)Metasploit技术之基础用法万字详解及防御机理

    您可能之前看到过我写的类似文章,为什么还要重复撰写呢?只是想更好地帮助初学者了解病毒逆向分析和系统安全,更加成体系且不破坏之前的系列.因此,我重新开设了这个专栏,准备系统整理和深入学习系统安全.逆向分 ...

最新文章

  1. 第9章 数据字典(选项)管理
  2. jQuery Mobile 图标无法显示
  3. html学习_认识html
  4. Zigbee协议栈中OSAL的运行机理
  5. Zookeeper集群搭建方法
  6. mysql表级锁和行级锁_Mysql的表级锁和行级锁
  7. 【bzoj 十连测】[noip2016十连测第三场]Problem C: 序列(静态主席树)
  8. [Perforce系列—] 1. 使用Perforce 命令 与常用命令
  9. Redis基础学习记录(1)
  10. 用 O(1) 时间检测整数 n 是否是 2 的幂次。
  11. Nb iot php_nb-lot技术的特点是什么?
  12. 曼昆《经济学原理(微观经济学分册)》(第6版)课后习题答案
  13. Linux 系统日常巡检脚本
  14. Markdown箭头总汇
  15. html批量处理图片大小,如何批量修改图片大小?批量处理图片尺寸的方法
  16. 定时删除虚拟服务器快照,ESXi6.0 设置自动删除快照脚本及计划任务
  17. 金融行情尽在掌握 — Google(谷歌)推出谷歌金融 Onebox
  18. 字节跳动梁汝波:管理者过于依靠规则会使组织僵化 |王兴:反垄断无损美团竞争优势...
  19. html css 微信小程序,tailwindcss 支持微信小程序配置
  20. Sass系统技术选型笔记(2)JBPM

热门文章

  1. PLG日志平台搭建: Promtail + Loki + Grafana 全步骤
  2. 【SaaS播客】onboard4. 连线硅谷顶尖Product-Led Growth公司产品经理,聊聊如何打造一流PLG产品
  3. Android中集成讯飞语音,语音转文字以及文字转语音操作
  4. Linux搭建KMS服务器激活你的Windows
  5. vue 中 vue-print-nb 表格打印不全的问题
  6. 根据微信公众号关注/取消关注事件,获取用户信息
  7. 小程序跳转公众号关注页面的两种方法
  8. 第一篇Blog,随便写一点吧:)
  9. Synology-群晖(一)移动大内网使用 IPv6 + DDNS 实现公网访问
  10. python高逼格动态图_微信编辑哪里找高逼格 GIF 动图?