浅谈Android View滑动和弹性滑动
引言
View的滑动这一块在实际开发中是非常重要的,无论是优秀的用户体验还是自定义控件都是需要对这一块了解的,我们今天来谈一下View的滑动。
View的滑动
View滑动功能主要可以使用3种方式来实现;第一种是通过View的scrollTo/scrollBy方法来实现滑动。第二种是通过动画给View添加平移效果来实现滑动。第三种就是通过修改View的LayoutParams来实现View的滑动。下面我们来依次介绍。
scrollTo/scrollBy方法
为了实现View的滑动,View类实现了scrollTo和scrollBy方法,我们先来看一下这两个方法的实现。代码如下:
/**
* Set the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the x position to scroll to
* @param y the y position to scroll to
*/
public void scrollTo(int x, int y) {
if (mScrollX != x || mScrollY != y) {
int oldX = mScrollX;
int oldY = mScrollY;
mScrollX = x;
mScrollY = y;
invalidateParentCaches();
onScrollChanged(mScrollX, mScrollY, oldX, oldY);
if (!awakenScrollBars()) {
postInvalidateOnAnimation();
}
}
} /**
* Move the scrolled position of your view. This will cause a call to
* {@link #onScrollChanged(int, int, int, int)} and the view will be
* invalidated.
* @param x the amount of pixels to scroll by horizontally
* @param y the amount of pixels to scroll by vertically
*/
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
我们看到scrollBy内部也是调用了scrollTo方法。从代码注释中我们也可以知道两者之间的区别。详细区别如下:
scrollTo的两个参数x,y表示的移动的具体位置(目标位置x,y),scrollTo实现了基于传递参数的绝对滑动。
scrollBy的两个参数x,y表示移动的偏移量,scrollBy实现了基于传递参数的相对滑动。
在学习View的滑动过程中,我们需要了解View内部mScrollX和mScrollY两个属性的变化规则,这两个属性可以通过getScrollX和getScrollY两个方法分别获得。
mScrollX的值总是等于View左边缘和View内容左边缘在水平方向上的距离。mSxrollY的值总是等于View上边缘和View内容上边缘在竖直方向上的距离。View的边缘是指View的位置,由四个顶点组成,而View的内容边缘是指View中内容的边缘,scrollTo和scrollBy方法只能改变View内容的位置,不能改变View在布局中的位置。
下面我们通过图来形象的看一下mScrollX和mScrollY在滑动过程中的变化过程。如图所示:

从图中我们可以看到,当View的左边缘在View内容左边缘的右边时(图中第一排第二个),mScrollX是正值,反之为负值。当View上边缘在View内容上边缘的下面时,mScrollY是正值,反之为负值。我们看到这和我们正常理解的向右侧滑动为正值的概念是相反的。这一点需要注意!
重点:
在追问下,上面说scrollTo改变的是View内容的位置,而不是View在布局中的位置,这个到底怎么整的?其实Android系统是通过移动可视区域从而达到改变View内容位置的。看看下面这张图:

例如我们移动图中黄色的View向右,我们自觉肯定是偏移量为正(X轴正方向为右边),但是我们在使用scrollBy的时候却要使用负值,这和我们自觉相反?如果Android实现滑动移动的是坐标系统呢?View要向右侧滑动,不是只要将坐标系统往左侧移动吗?不就是负值了。。。。。重点部分啊!
使用动画
上面我们介绍了通过View自有的scrollTo和scrollBy方法来实现滑动,现在我们来介绍使用动画来实现View的滑动。通过动画来实现View的滑动,主要就是操作View的translationX和translationY属性,通过修改这两个属性我们可以实现View的动画,这两个属性不明白?推荐看上一篇博客《View的定位》。
使用动画来实现View的滑动,可以使用传统的View动画也可以使用Android 3.0以后推出的属性动画(为了兼容Android 3.0以下的设备可以使用nineoldandroids动画兼容库)。
使用View动画需要注意一点,View动画是对View的影像做操作,它并不能真正的改变View的位置参数,包括宽度和高度,并且希望动画结束后的状态得以保持必须设置fillAfter为true,否则动画完成后其动画效果会消失。这一缺陷也会导致有事件处理的View使用View动画会出现更加严重的问题。试想如果一个View有事件处理代码(OnClick等),将其移动200px后,点击新位置的View是无法触发事件的,但是点击原来的位置居然可以触发事件,这就有些奇葩了。。。。。后面讲View动画的也会重点讲这一块。
结合上面的内容,我们推荐使用属性动画来实现绝大部分的动画效果,View动画建议只使用在没有事件处理的效果中。
改变布局参数
第三种实现View滑动的方法就是修改布局参数(即LayoutParams)。例如我们需要把一个View向右平移100px,我们只需要将这个View的LayoutParams里面的marginLeft参数的值添加100px即可。这种方式我在后面的博客中会详细介绍,这个一般和ValueAnimator配合使用的比较多,在动画执行过程中,不断修改LayoutParams的值,实现滑动效果。
三种滑动实现方式的异同点
scrollTo/scrollBy这种实现方式:操作简单,适合对View内容的滑动,也适合一些ViewGroup的滑动效果效果的实现。
动画实现方式:操作简单,View动画适用于没有交互的View。属性动画可以实现较为复杂的动画效果。
改变布局参数:操作稍微复杂,主要适用于有交互的View。
弹性滑动
下面我们开始介绍这一部分的重点内容,View的弹性滑动。实现View的弹性滑动也有好几种方法,下面就来一一介绍。
使用Scroller
我们在使用Scroller实现弹性滑动的时候有一套固定的方式来使用,固定方式代码如下:
/**
* 缓慢滚动到指定位置
* @param dx
*/
private void smoothScrollByDx(int dx) {
//在1000毫秒内滑动dx距离,效果就是慢慢滑动
mScroller.startScroll(getScrollX(), 0, dx, 0, 1000);
invalidate();
} @Override
public void computeScroll() {
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
postInvalidate();
}
}
上面的代码片段就是使用Scroller实现弹性滑动的典型方式,我们下面来简单的描述下其工作原理。我们看一下startScroll方法内部的逻辑,代码如下:
/**
* Start scrolling by providing a starting point, the distance to travel,
* and the duration of the scroll.
*
* @param startX Starting horizontal scroll offset in pixels. Positive
* numbers will scroll the content to the left.
* @param startY Starting vertical scroll offset in pixels. Positive numbers
* will scroll the content up.
* @param dx Horizontal distance to travel. Positive numbers will scroll the
* content to the left.
* @param dy Vertical distance to travel. Positive numbers will scroll the
* content up.
* @param duration Duration of the scroll in milliseconds.
*/
public void startScroll(int startX, int startY, int dx, int dy, int duration) {
mMode = SCROLL_MODE;
mFinished = false;
mDuration = duration;
mStartTime = AnimationUtils.currentAnimationTimeMillis();
mStartX = startX;
mStartY = startY;
mFinalX = startX + dx;
mFinalY = startY + dy;
mDeltaX = dx;
mDeltaY = dy;
mDurationReciprocal = 1.0f / (float) mDuration;
}
我们看到startScroll方法内部其实什么都没做,只是保存了我们传递的几个参数。从传递的参数中我们可以看到,startX和startY表示的是滑动的起点,dx和dy表示的是滑动的距离,而duration表示的是滑动时间(整个滑动过程完成的时间),这里的滑动是指View内容的滑动而非View本身位置的改变。
注意点:
从上面的代码中我们看到startScroll是无法实现View的滑动的,它内部只是做了简单的赋值操作。是invalidate方法导致了View的重绘,在View的draw方法中又回去调用computeScroll方法(computeScroll方法在View中是一个空实现,需要我们自己去实现,我们上面的代码片段已经实现了这个方法)
具体过程如下:
当View重绘后会在draw方法中调用computeScroll,在computeScroll方法中会去向Scroller获取当前的scrollX和scrollY,然后通过scrollTo方法实现滑动。接着又调用postInvalidate方法来进行第二次重绘,这一次的重绘过程和第一次重绘一模一样,如此反复,直到整个滑动过程结束。
我们再看一下computeScollOffset方法的逻辑,代码如下:
public boolean computeScrollOffset() {
// 如果已经结束,直接返回false.
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);
mCurrX = mStartX + Math.round(x * mDeltaX);
mCurrY = mStartY + Math.round(x * mDeltaY);
break;
// 处理fling模式......
}
} else {
// 当时间结束时,直接将x和y坐标置为终止状态的x和y坐标,同时将终止标志位置为true.
mCurrX = mFinalX;
mCurrY = mFinalY;
mFinished = true;
}
return true;
上面代码就是根据时间流逝来计算当前scrollX和scrollY的值,大意是根据时间的流逝的百分比来计算scrollX和scrollY,这个类似动画中的插值器。这个方法返回true表示滑动还未结束,false表示滑动已经结束。
通过动画实现弹性滑动
动画本身就是一种渐进的过程,因此通过动画实现的滑动天生就具有弹性效果。我们通常情况下可以使用属性动画来实现。不过我们也可以使用动画来模拟实现Scroller的弹性滑动的效果,如图:

在这个代码中我们看到,我们动画本质上没有作用于任何对象上,我们利用动画的每一帧的到来时获取动画完成的比例,然后计算需要滑动的距离,这和Scroller的思想比较类似。
使用延时策略
这种方式的核心思想是通过发生一系列的延时消息从而实现一种渐进式的效果,具体来说是使用Handler和View的postDelayed方法。具体实现可以看下一篇文章《Android弹性滑动的实现方式》。
浅谈Android View滑动和弹性滑动的更多相关文章
- 浅谈Android View滑动冲突
引言 上一篇文章我们从源码的角度介绍了View事件分发机制,这一篇文章我们就通过介绍滑动冲突的规则和一个实例来更加深入的学习View的事件分发机制. 1.外部滑动方向和内部滑动方向不一致 考虑这样一种 ...
- 浅谈Android View事件分发机制
引言 前面的文章介绍了View的基础知识和View的滑动,今天我们来介绍View的另一个核心知识,View的事件分发机制. 点击事件的传递规则 所谓的点击事件的分发机制,其实就是对MotionEven ...
- 浅谈Android View的定位
引言 今天我们来介绍Android坐标系统和View的定位,当然也会介绍View的滑动相关话题.下面让我们开始介绍吧. View的基础知识 View是Android中所有控件的基类,无论是TextVi ...
- 安卓开发_浅谈Android动画(四)
Property动画 概念:属性动画,即通过改变对象属性的动画. 特点:属性动画真正改变了一个UI控件,包括其事件触发焦点的位置 一.重要的动画类及属性值: 1. ValueAnimator 基本属 ...
- 浅谈Android应用性能之内存
本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处! 文/ jaunty [博主导读]在Android开发中,不免会遇到许多OOM现象,一方面可能是由于开 ...
- 浅谈Android保护技术__代码混淆
浅谈Android保护技术__代码混淆 代码混淆 代码混淆(Obfuscated code)亦称花指令,是将计算机程序的代码,转换成一种功能上等价,但是难于阅读和理解的形式的行为.将代码中的各种元 ...
- 浅谈Android应用保护(一):Android应用逆向的基本方法
对于未进行保护的Android应用,有很多方法和思路对其进行逆向分析和攻击.使用一些基本的方法,就可以打破对应用安全非常重要的机密性和完整性,实现获取其内部代码.数据,修改其代码逻辑和机制等操作.这篇 ...
- 浅谈Android五大布局
Android的界面是有布局和组件协同完成的,布局好比是建筑里的框架,而组件则相当于建筑里的砖瓦.组件按照布局的要求依次排列,就组成了用户所看见的界面.Android的五大布局分别是LinearLay ...
- [转]浅谈Android五大布局(二)——RelativeLayout和TableLayout
在浅谈Android五大布局(一)中已经描述了LinearLayout(线性布局).FrameLayout(单帧布局)和AbsoulteLayout(绝对布局)三种布局结构,剩下的两种布局Relati ...
随机推荐
- YAML语法介绍
官网YAML: http://yaml.org/ http://yaml.org/xml 参考链接: http://www.ruanyifeng.com/blog/2016/07/yaml.html? ...
- LeetCode 31 Next Permutation(下一个全排列)
题目链接: https://leetcode.com/problems/next-permutation/?tab=Description Problem :寻找给定int数组的下一个全排列(要求 ...
- jquery中event对象属性与方法小结
JQuery事件中的Event属性是经常性的被忽略的.大多数时间你的确不怎么用它,但有些时候它还是它还是有作用的.如获知触发时用户的环境(是否按了shift etc).每个浏览器对event都有不同的 ...
- dhroid - ioc基础(@Inject*)
1 ioc即控制反转.控制反转(Inversion of Control,英文缩写为IoC)是一个重要的面向对象编程的法则来削减计算机程序的耦合问题,也是轻量级的Spring框架的核心.控制反转还有一 ...
- Java虚拟机四 常用Java虚拟机参数
主要涉及的知识点: 1.跟踪Java虚拟机的垃圾回收和类加载等信息: 2.配置Java虚拟机的堆空间: 3.配置永久区和Java栈. 4.学习虚拟机的服务器和客户端模式. 1.1 跟踪垃圾回收 Jav ...
- vue--环境搭建(创建运行项目)
如何搭建vue环境: 1.安装之前必须要安装 node.js 2.搭建Vue环境,安装vue的脚手架工具 npm install --global vue-cli / cnpm install --g ...
- ElasticSearch在linux上安装部署(转)
一.安装准备工作安装参考文档: ELK官网:https://www.elastic.co/ ELK官网文档:https://www.elastic.co/guide/index.html ELK中文手 ...
- python----并发编程之IO模型
一:IO模型介绍 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个 ...
- HDU 1686 - Oulipo - [KMP模板题]
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1686 Time Limit: 3000/1000 MS (Java/Others) Memory Li ...
- pycurl实例详解
Pycurl是Python的libcurl接口.liburl是客户端的URL传输库,它支持FTP,FTPS,HTTP,HTTPS,TELNET,LDAP等诸多协议,同时支持HTTP认证,代理,FTP上 ...