Android PropertyAnimation 属性动画(二)弹跳小球实例
前言
上篇博客简单介绍了属性动画的原理,这篇博客将会以一个简单的实例来运用上之前讲的内容,并对Animator
的几个回调方法进行讲解。
目标是自定义一个View
,在画布上绘制一个小球,点击屏幕后小球从顶部自由下落,落到底边后反弹,反弹损失一半的能量,也就是说小球只能上升到下落时一半的高度,再重复这个过程直到退出程序。如图:
上篇博客简单介绍了属性动画的原理,这篇博客将会以一个简单的实例来运用上之前讲的内容,并对Animator
的几个回调方法进行讲解。
目标是自定义一个View
,在画布上绘制一个小球,点击屏幕后小球从顶部自由下落,落到底边后反弹,反弹损失一半的能量,也就是说小球只能上升到下落时一半的高度,再重复这个过程直到退出程序。如图:
相对于静态的页面,动画往往能更直观地表达所需的信息,在UI开发过程中起着相当大的作用。
Android为我们提供了一系列实现动画效果的方法,PropertyAnimaiton
是最常见也是最实用的一种,如同它的名字一样,它的实现方式是通过改变对象的一系列属性值来改变对象的状态, 例如动态地改变绘制的位置就可以实现绘制物体的移动效果,动态地改变对象的显示状态可以实现闪烁效果。
Android提供的实现属性动画的工具是android.animation.Animator
这个类,它的使用需要配合animation
包下的其他工具类,这个类的功能是什么,我们要如何使用它来实现属性动画呢?
经过对View
的测量与布局过程后,下面就到了真正的View
绘制的过程了。这个过程从调用根View
的draw()
方法开始:(省略部分代码)
1 | public void draw(Canvas canvas) { |
这段源码来自于View
,过程非常清晰,执行了以下的步骤(如果需要):
onDraw()
方法进行自身的绘制dispatchDraw()
方法,进行子View
的绘制(调用子View
的draw()
方法),同时也表明了子View
的绘制在自身之后这一顺序经过上一篇介绍的measure
过程之后,各个View
的尺寸信息已经存储在了每个View
中,下面是layout
过程,layout
过程的目的是根据上一步中计算出的尺寸来正确设置各个View
及其后代的位置。这个过程首先被调用的是View
的layout()
方法,layout()
的方法签名是public void layout(int l, int t, int r, int b)
,四个参数分别为左边界距父View
左边界的距离,上边界距父View
上边界的距离,右边界距父View
左边界的距离,下边界距父View
上边界的距离。
1 | boolean changed = isLayoutModeOptical(mParent) ? |
changed
是用于传递给onLayout()
方法的参数,它指示了布局是否被改变。
后面的表达式查看了父View
的布局模式是否需要显示边框,如需要,调用的是setOpticalFrame()
方法:
1 | private boolean setOpticalFrame(int left, int top, int right, int bottom) { |
可以看到这个方法读取了设置的边框值, 把原值加上边框值后还是调用了setFrame()
方法。
setFrame()
方法通过传入的参数确定了该View
最终的位置以及尺寸。
上一篇博客简单地介绍了View
绘制的生命周期, 从这篇博客开始将会对这个周期中一些有用的过程进行一个详细一些的介绍。这篇的主角就是在构造方法之后调用的measure
过程。
为了演示,继承了TextView
来实现一个自定义的View
。注意这里继承的应该是android.support.v7.widget.AppCompatTextView
这个类。同时为了xml
文件的正常解析,我们需要实现View
的三个构造方法。
1 | public VView(Context context) { |
再通过完整包名的方法在xml
布局文件中创建我们的View
就可以直接显示了。
1 | <com.viseator.viewtest.VView |
这里给了TextView
一个背景颜色便于后面的观察。
为了直观表示整个过程,我制作了一张流程图。注意以下只是整个生命周期中比较常用的方法,并不代表所有的过程。
当一个Activity
收到焦点即将要处于激活状态时,将会被要求绘制它的布局,绘制布局之前的过程在这里不涉及,我们从绘制View
开始分析。
每个Activity
被要求提供一个ViewGroup
作为View树的根,也就是我们熟悉的setContentView
方法。
1 | @Override |
可以看到setContentView
拥有三种形式,可以直接传入View
、传入一个layout
资源文件,或传入一个View
文件和一个用于提供参数的LayoutParams
对象。
整个过程将从这个根View
开始,并遍历它的子View
来逐一绘制,每个ViewGroup
承担了要求它的子View
进行绘制的责任,每个View
承担了绘制自身的责任。并且父View
会在子View
完成绘制之前进行绘制,同级的View
将以它们出现在树中的顺序进行绘制。
Java 1.5中引入了泛型的概念以增加代码的安全性与清晰度,同时为了提供对旧代码的兼容性,让旧代码不经过改动也可以在新版本中运行,Java提供了原生态类型(或称原始类型)。但是实际中在新的代码中已经不应该使用原生态类型。
原生态类型的含义是不带任何实际参数的泛型名称,例如Java 1.5后改为泛型实现的List<E>
,List
就是它的原生态类型,与没有引入泛型之前的类型完全一致。
而在虚拟机层面上,是没有泛型这一概念的——所有对象都属于普通类。在编译时,所有的泛型类都会被视为原生态类型。
那么为什么不应该使用原生态类型呢?
如果使用原生态类型,就失掉了泛型在安全性和表述性方面的所有优势。——Effective Java
泛型的目的简单地说就是可以让一些运行时才能发现的错误可以在编译期间就可以被编译器所检测出,运行时出问题的代价与编译期出现问题的代价的差别可想而知。换句话说,泛型是编译器的一种及时发现错误的机制,同时也给用户带来了代码的清晰与简洁的附加好处(不必再写一些复杂而危险并且不直观的强制类型转换)。
下面就进入正题谈谈以List
为例时List
、List<Object>
、List<?>
的区别。
先下定义:
List
:原生态类型List<Object>
:参数化的类型,表明List
中可以容纳任意类型的对象List<?>
:无限定通配符类型,表示只能包含某一种未知对象类型