android scroller,高级UI第四十四篇:Android Scroller详解
滚动视图的方法有两种: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详解相关推荐
- MySQL数据库,从入门到精通:第十二篇——MySQL数据类型详解
MySQL数据库,从入门到精通:第十二篇--MySQL数据类型详解 第 12 章_MySQL数据类型精讲 1. MySQL中的数据类型 2. 整数类型 2. 1 类型介绍 2. 2 可选属性 2. 2 ...
- Android UI开发第二十九篇——Android中五种常用的menu(菜单)
Android Menu在手机的应用中起着导航的作用,作者总结了5种常用的Menu. 1.左右推出的Menu 前段时间比较流行,我最早是在海豚浏览器中看到的,当时耳目一新.最早使用左右推出菜单的,听说 ...
- Linux内存管理(四十):Linux PSI 详解
源码基于:Linux 5.4 0. 前言 之前在<Linux PSI 指标>一文中简单的描述了 PSI 指标的意义,以及PSI 出现的历史过程. 在PSI 之前,Linux 也有一些资源压 ...
- MySQL数据库,从入门到精通:第十四篇——MySQL视图详解
MySQL数据库,从入门到精通:第十四篇--MySQL视图详解 第 14 篇_视图 1. 常见的数据库对象 2. 视图概述 2. 1 为什么使用视图? 2. 2 视图的理解 3. 创建视图 3. 1 ...
- 万字长文告诉新手如何学习Python图像处理(上篇完结 四十四) | 「Python」有奖征文
该系列文章是讲解Python OpenCV图像处理知识,前期主要讲解图像入门.OpenCV基础用法,中期讲解图像处理的各种算法,包括图像锐化算子.图像增强技术.图像分割等,后期结合深度学习研究图像识别 ...
- Android UI开发第二十五篇——分享一篇自定义的 Action Bar
Action Bar是android3.0以后才引入的,主要是替代3.0以前的menu和tittle bar.在3.0之前是不能使用Action Bar功能的.这里引入了自定义的Action Bar, ...
- Python编程基础:第四十四节 方法重写Method Overriding
第四十四节 方法重写Method Overriding 前言 实践 前言 我们前面说了,子类继承于父类,可以调用父类的所有属性和方法.那么如果我们想在继承的过程中重新书写父类的某些方法,此时就用到了方 ...
- 四十四种Javascript技巧大全
四十四种Javascript技巧大全 1.第一个给变量分配值时不要忘了var关键字. 分配值给未定义的变量将是该变量自动成为全局变量,应该避免全局变量. 2.使用 === 而不是 == 使用 == ( ...
- 四十四、深入Java 的序列化和反序列化
@Author:Runsen @Date:2020/6/8 作者介绍:Runsen目前大三下学期,专业化学工程与工艺,大学沉迷日语,Python, Java和一系列数据分析软件.导致翘课严重,专业排名 ...
- [系统安全] 四十四.APT系列(9)Metasploit技术之基础用法万字详解及防御机理
您可能之前看到过我写的类似文章,为什么还要重复撰写呢?只是想更好地帮助初学者了解病毒逆向分析和系统安全,更加成体系且不破坏之前的系列.因此,我重新开设了这个专栏,准备系统整理和深入学习系统安全.逆向分 ...
最新文章
- 第9章 数据字典(选项)管理
- jQuery Mobile 图标无法显示
- html学习_认识html
- Zigbee协议栈中OSAL的运行机理
- Zookeeper集群搭建方法
- mysql表级锁和行级锁_Mysql的表级锁和行级锁
- 【bzoj 十连测】[noip2016十连测第三场]Problem C: 序列(静态主席树)
- [Perforce系列—] 1. 使用Perforce 命令 与常用命令
- Redis基础学习记录(1)
- 用 O(1) 时间检测整数 n 是否是 2 的幂次。
- Nb iot php_nb-lot技术的特点是什么?
- 曼昆《经济学原理(微观经济学分册)》(第6版)课后习题答案
- Linux 系统日常巡检脚本
- Markdown箭头总汇
- html批量处理图片大小,如何批量修改图片大小?批量处理图片尺寸的方法
- 定时删除虚拟服务器快照,ESXi6.0 设置自动删除快照脚本及计划任务
- 金融行情尽在掌握 — Google(谷歌)推出谷歌金融 Onebox
- 字节跳动梁汝波:管理者过于依靠规则会使组织僵化 |王兴:反垄断无损美团竞争优势...
- html css 微信小程序,tailwindcss 支持微信小程序配置
- Sass系统技术选型笔记(2)JBPM
热门文章
- PLG日志平台搭建: Promtail + Loki + Grafana 全步骤
- 【SaaS播客】onboard4. 连线硅谷顶尖Product-Led Growth公司产品经理,聊聊如何打造一流PLG产品
- Android中集成讯飞语音,语音转文字以及文字转语音操作
- Linux搭建KMS服务器激活你的Windows
- vue 中 vue-print-nb 表格打印不全的问题
- 根据微信公众号关注/取消关注事件,获取用户信息
- 小程序跳转公众号关注页面的两种方法
- 第一篇Blog,随便写一点吧:)
- Synology-群晖(一)移动大内网使用 IPv6 + DDNS 实现公网访问
- python高逼格动态图_微信编辑哪里找高逼格 GIF 动图?