标签:
Android提供了三种动画机制:属性动画(property animation)、补间动画(tween animation / view animation)、帧动画(drawable animation / frame animation)。其中属性动画被官方推荐使用,因为它拥有更多的灵活性和特色(because it is more flexible and offers more features)、并可以几乎完全替代后两种动画。
本文将介绍Android中的动画机制。如需访问官方原文,您可以点击这些链接:
撰写本文所参考的资料:
郭霖的博文:
工匠若水的博文:《Android应用开发之所有动画使用详解》;
何红辉的书《Android开发进阶 从小工到专家》,第2.4节。
属性动画在Android 3.0(API 11)版本中引入,该动画可以作用在任何对象的属性上( property animation system lets you animate properties of any object),甚至包括未在屏幕上渲染的对象(including ones that are not rendered to the screen)。属性动画具有很高的扩展性、您可以将动画作用在定制的属性上。
属性动画可以实现几乎任何动画效果。该动画的原理是在一定时间内改变某个对象的属性值来实现动画的渐变。
使用属性动画可以指定的参数如下:
持续时间(Duration):您可以指定动画的持续时间,默认为300毫秒。
时间插值器(Time interpolation):在规定的持续时间内,动画控制的属性值是以怎样的速率变化的(如线性、加速 等)。
重复的次数和行为(Repeat count and behavior):您可以指定是否让动画重复执行、以及重复的次数。您也可以指定是否让动画反向执行。
动画集合(Animator sets):您可以将若干单独的动画组成一个集合,以指定的顺序播放(或同时播放)。
帧的刷新频率(Frame refresh delay):您可以为动画指定刷新的频率。默认是10毫秒。
下图描述了当属性动画作用在某个对象的属性”x”上时的变化情况。属性”x”表示该对象在屏幕所在位置的横坐标。动画持续了40毫秒、将该属性变化了40个像素。默认每隔10毫秒就刷新一帧,对象就横向移动10个像素。这是使用了线性差值器,表示属性是随时间的推移匀速变化的。
您也可以使用非线性的差值器。下图演示了先加速后减速的情形。显然,在相同的时间内,“x”的变化长度在一开始时没有在中期变化的长度多。
下图演示了动画的运行方式。 ValueAnimator
是整个动画执行的核心类,它负责追踪动画的运行情况,比如动画的持续时间、某个时刻下动画所控制的属性值是多少等。
ValueAnimator
封装了一个TimeInterpolator
接口。该接口中唯一的方法abstract float getInterpolation(float input)
就是用于定义动画的变化速率的。方法唯一的float型参数取值范围是0.0-1.0,表示动画在规定时间内运行的百分比,比如为动画设定的持续时间为10秒,当系统回调该方法时,回传了0.3,就表示动画执行了3秒。方法的返回值表示根据动画执行的百分比设置属性的执行百分比,范围也是0.1-1.0。比如说,方法返回了参数值,这就表示属性值随时间线性变化,即如果还按照上面的例子,动画控制view的”x”属性从0变化到100,那么方法回传0.3时,返回值也是0.3,也就是”x”变化了30%,即变化到了30。而方法回传0.9时,返回值也是0.9,也就是”x”变化了90%,即变化到了90;如果方法返回的是参数值的平方,这就表示属性值是一个加速变化的过程,按照上面例子,方法回传0.3时,返回值就是0.3^2=0.09,也就是”x”变化了9%,即变化到了9,而方法回传0.9时,返回值为0.9^2=0.81,也就是”x”变化了81%。
ValueAnimator
中还封装了另一个接口 TypeEvaluator
,该接口中也只有一个方法abstract T evaluate(float fraction, T startValue, T endValue)
,参数1表示属性值执行的百分比,范围是0.0-1.0,也就是说,getInterpolation()
方法的返回值会传至该参数。而参数2和参数3分别表示属性的起始值和结束值,按照上例,这两个值分别为0和100。而evaluate()
的返回值表示为当前的属性设置的新的属性值,所以返回值通常为如下形式:
T returnValue = startValue + ( T ) ( fraction * ( endValue - startValue ) ;
比如按照上例,view的”x”从0-100,执行10秒,加速变化:当执行到第9秒时,返回值为0.81,即fraction为0.81,那么套用上面的公式,即为:
//returnValue = 81
int returnValue = 0 + ( int ) ( 0.81 * ( 100 - 0 ) ) ;
这说明x执行到了81的位置。
补间动画只能作用于View上(稍后将介绍补间动画),可实现的动画效果也比较有限,只包含四种动画效果(旋转、缩放、平移、透明度),若需要改变view的背景,补间动画就派不上用场了。
补间动画的另一缺点是,它只是将view视图改变了位置,实际的view并没有改变,比如,将补间动画的平移效果作用于某个view上,view的点击事件并没有平移,还是在原来的位置上。
属性动画弥补了补间动画的不足,属性动画完完全全地移动了对象,动画也可以作用在任何对象的任何属性上,比如颜色,位置,大小 等。
当然,补间动画也不是一无是处,它的执行效率高于属性动画。如果您想要实现的动画效果补间动画就能完成,那么没有必要使用属性动画。所以,根据不同场合来选择合适的动画,是最好的选择。
属性动画的API位于android.animation
包中。补间动画的API位于android.view.animation
包中,该包还包含了系统定义好的插值器interpolator,您可以在属性动画中直接调用这些插值器。下面的这些表介绍了属性动画的主要API。
抽象类Animator
是创建属性动画的基础类。Animator
的主要实现类如下表所示:
类(Class) | 描述(Description) |
---|---|
ValueAnimator |
这是整个属性动画的核心类,它封装了为动画设置起始值、结束值、持续时间以及如何重复等方法 ,当然还有监听变化值的回调方法。但是,ValueAnimator 并没有将实时监听的变化值设置到目标对象的指定属性上,所以您必须手动设置。 |
ObjectAnimator |
ValueAnimator 的子类,该类弥补了ValueAnimator 的不足,即它可以将回传的值直接设置给对象的指定属性( allows you to set a target object and object property to animate) |
AnimatorSet |
将若干动画以一定的顺序组合执行 |
估值器(Evaluators )负责将动画执行的进度(百分比)换算成实际的属性值,下表是各种类型的估值器:
类/接口(Class/Interface | 描述(Description) |
---|---|
IntEvaluator |
属性值以整数的形式变化 |
FloatEvaluator |
属性值以浮点数的形式变化 |
ArgbEvaluator |
属性值以十六进制数的形式变化,一般用于设置颜色属性 |
TypeEvaluator |
这是一个接口,用于定制 evaluator,如果需要改变的属性值并不是int、float、color类型,那么您必须实现该接口以指定如何计算属性值。 |
时间插值器( time interpolator)指定了随着时间的变化,属性值的变化是以何种速率进行的。您可以自己实现TimeInterpolator
接口来定制属性的变化速率。官方也定义一些interpolator ,如下表所示:
类/接口(Class/Interface | 描述(Description) | 对应的资源id |
---|---|---|
AccelerateDecelerateInterpolator |
先加速再减速 | @android:anim/accelerate_decelerate_interpolator |
AccelerateInterpolator |
加速运动 | @android:anim/accelerate_interpolator |
AnticipateInterpolator |
先退一小步然后加速前进 | @android:anim/anticipate_interpolator |
AnticipateOvershootInterpolator |
在上一个基础上超出终点一小步再回到终点 | @android:anim/anticipate_overshoot_interpolator |
BounceInterpolator |
最后阶段弹球效果 | @android:anim/bounce_interpolator |
CycleInterpolator |
周期运动 | @android:anim/cycle_interpolator |
DecelerateInterpolator |
减速运动 | @android:anim/decelerate_interpolator |
LinearInterpolator |
匀速运动 | @android:anim/linear_interpolator |
OvershootInterpolator |
快速到达终点并超出一小步,最后回到终点 | @android:anim/overshoot_interpolator |
TimeInterpolator |
一个接口,供您定制自己的Interpolator | / |
创建ValueAnimator实例需要调用其工场方法,如ofInt()
、ofFloat()
或 ofObject()
,示例如下:
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000);
animation.start();
示例中在1秒内将浮点数从0变为1,调用start()
方法执行动画。
您也可以使用下面的方式执行动画:
ValueAnimator animation = ValueAnimator.ofObject(new MyTypeEvaluator(), startPropertyValue, endPropertyValue);
animation.setDuration(1000);
animation.start();
在第一段代码片段中,动画并没有作用在某个对象的属性上,这需要为Animator
添加监听器,实时获取当前属性值。
ObjectAnimator是ValueAnimator的子类,该子类可以直接将变化的值作用在对象的属性上,示例如下:
ObjectAnimator anim = ObjectAnimator.ofFloat(foo, "alpha", 0f, 1f);
anim.setDuration(1000);
anim.start();
静态工场方法中的四个参数分别表示:作用的对象、对象中的指定属性、起始值、终止值。为了使动画生效,您必须注意以下几点:
作用的属性值必须有自己的setter()
方法(而且必须是驼峰命名法),比方说,若属性名为alpha
,那么必须包含setAlpha()
方法。若该setter()
方法不存在,那么有如下三种可选方式:
在拥有权限的前提下,为该属性添加setter()
方法;
在拥有权限的前提下,为对象添加一个包装类,在包装类中设置setter()
属性值;
使用ValueAnimator替换;
如果您在调用ofFloat()
创建动画时只传入了一个属性值(而不是既有启始值和终止值),那么系统将默认把这个值视为终止值,这就需要该属性包含getter()
方法,以获得其初始值,getter()
方法的命名规则与setter()
方法一致。
getter()方法获取值的类型与setter()方法设置值的类型必须相同,如代码为ObjectAnimator.ofFloat(targetObject, "propName", 1f)
的动画声明中, targetObject
必须包含targetObject.setPropName(float) 和float targetObject.getPropName()
方法。
实现动画的效果可能需要调用invalidate()
方法强制view重绘,这需要在onAnimationUpdate()
的回调方法中进行。
在很多情况下,您需要将某个动画在某个指定的时刻启动,如在上一个动画结束的时刻 等。android提供了AnimatorSet
类来组合若干动画的播放顺序,示例如下:
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
animatorSet.start();
代码片段的执行顺序如下:
执行 bounceAnim
;
同时执行 squashAnim1
, squashAnim2
, stretchAnim1
, 和 stretchAnim2
执行 bounceBackAnim
.
执行 fadeAnim
.
如需获取完整的demo示例,您可以点击这个链接:Bouncing Balls
属性动画包含如下监听器:
onAnimationStart()
:当动画开始时回调;
onAnimationEnd()
:当动画结束时回调;
onAnimationRepeat()
:当动画重复执行时回调;
onAnimationCancel()
:当动画取消时回调,这时也会回调onAnimationEnd()
。
ValueAnimator.AnimatorUpdateListener
:
onAnimationUpdate()
:动画每执行一帧就回调一次(called on every frame of the animation)。该方法主要用于监听属性值的变化,然后将该属性值设置到某个属性上;您也可以不实现Animator.AnimatorListener
接口,而是通过继承AnimatorListenerAdapter
类的方式定制自己需要的监听,如下所示:
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
fadeAnim.addListener(new AnimatorListenerAdapter() {
public void onAnimationEnd(Animator animation) {
//当动画结束时,让balls对象消失
balls.remove(((ObjectAnimator)animation).getTarget());
}
代码实现了一个AnimatorListenerAdapter
,并重写了onAnimationEnd()
,并实现自己的逻辑。
使用属性动画为ViewGroups设置布局动画同样方便,核心类是LayoutTransition
,它主要用于为ViewGroups中的View出现或消失时设置动画,您可以调用LayoutTransition
的void setAnimator (int transitionType,
方法,其中参数2为您需要设置的动画,而参数1为
Animator animator)LayoutTransition
常量值之一:
APPEARING
:某个view出现在viewgroup时的标志位;
CHANGE_APPEARING
:某个view替换其他view出现在viewgroup时的标志位;
DISAPPEARING
:某个view从viewgroup消失时的标志位;
CHANGE_DISAPPEARING
:某个view在viewgroup中被其他view替换时的标志位;
除此之外,还需要您在viewgroup的布局中添加动画声明,以告知系统其子view需要执行属性动画:
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:id="@+id/verticalContainer"
android:animateLayoutChanges="true" />
Keyframe
类用于在动画执行到某一帧时,为该帧设置一个属性值,这一帧也称为关键帧。在PropertyValuesHolder
类中添加若干关键帧,系统会自动在两个关键帧之间执行动画。该类的初始化同样使用静态工厂方法ofInt(), ofFloat(), 或 ofObject()
,传入的参数有两个,第一个的范围是0.0-1.0,表示动画在时间轴上执行的百分比,第二个参数表示要对这个属性赋值。示例如下:
//关键帧kf0 ,在动画执行到0%时,属性值为0f
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
//关键帧kf1 ,在动画执行到50%时,属性值为360f
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
//关键帧kf2 ,在动画执行到100%时,属性值为0f
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
//初始化PropertyValuesHolder ,依次传入作用的属性"rotation"、以及三个关键帧
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
//调用ObjectAnimator的静态方法ofPropertyValuesHolder初始化ObjectAnimator,传入的第1个参数target为属性"rotation"所属对象,第二个参数为PropertyValuesHolder
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation);
//设置动画的执行时间
rotationAnim.setDuration(5000ms);
上述代码在执行时,系统会根据kf0,kf1,kf2
这三个关键帧的值把动画连起来,默认为匀速执行,当然,您也可以添加插值器定制执行的速率。
您可以将属性动画作用在View的这些属性上:
translationX
、translationY
:表示view在横向或纵向上移动的距离;
rotation、 rotationX 、 rotationY
:前者表示view在2D平面上旋转的角度,后两者表示view在3D空间中旋转的角度;
scaleX 、 scaleY
:表示view的缩放;
pivotX 、 pivotY
:表示view在缩放或旋转的中心点,若不设置,默认为view的中心点;
x 、 y
:分别表示当前view的左边距其父容器左边的距离、当前view的上边距其父容器上边的距离;
alpha
:表示view的透明度,取值为0.0-1.0,0表示完全透明,1表示完全不透明;
示例如下:
ObjectAnimator.ofFloat(myView, "rotation", 0f, 360f);
Android提供了便捷的ViewPropertyAnimator
类,它可以将若干动画同时执行,而仅仅需要调用一句代码,下面的三段代码分别创建了2个ObjectAnimator
对象、1个ObjectAnimator
对象、0个个ObjectAnimator
对象,却实现了相同的效果,显然最后一种方式最简便,它就是运用了ViewPropertyAnimator
:
ObjectAnimator
对象:ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();
ObjectAnimator
对象:PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();
ObjectAnimator
对象:myView.animate().x(50f).y(100f);
为了让系统识别您定义的XML文件是属性动画,需要将该文件放在res/animator/
目录下。
XML标签与Java代码的对应类如下:
ValueAnimator
- <animator>
ObjectAnimator
- <objectAnimator>
AnimatorSet
- <set>
下面演示了一个属性动画的集合,在集合中嵌套了一个集合和一个单独的动画:
<set android:ordering="sequentially">
<set>
<objectAnimator
android:propertyName="x"
android:duration="500"
android:valueTo="400"
android:valueType="intType"/>
<objectAnimator
android:propertyName="y"
android:duration="500"
android:valueTo="300"
android:valueType="intType"/>
</set>
<objectAnimator
android:propertyName="alpha"
android:duration="500"
android:valueTo="1f"/>
</set>
在Java代码中执行该动画:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,
R.anim.property_animator);
set.setTarget(myObject);
set.start();
下面简要介绍一下补间动画。补间动画只能作用在View上,您同样可以使Java代码或XML的形式编写动画,官方推荐使用XML的形式,为了让系统识别您定义的XML文件为补间动画,需要将文件定义在res/anim/
目录下,示例如下:
<set android:shareInterpolator="false">
<scale
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:fromXScale="1.0"
android:toXScale="1.4"
android:fromYScale="1.0"
android:toYScale="0.6"
android:pivotX="50%"
android:pivotY="50%"
android:fillAfter="false"
android:duration="700" />
<set android:interpolator="@android:anim/decelerate_interpolator">
<scale
android:fromXScale="1.4"
android:toXScale="0.0"
android:fromYScale="0.6"
android:toYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="700"
android:duration="400"
android:fillBefore="false" />
<rotate
android:fromDegrees="0"
android:toDegrees="-45"
android:toYScale="0.0"
android:pivotX="50%"
android:pivotY="50%"
android:startOffset="700"
android:duration="400" />
</set>
</set>
其中属性android:fillAfter
表示是在动画结束时将其停留在最后一帧,android:startOffset
表示延迟执行的毫秒数。
在Java代码中执行XML动画:
ImageView spaceshipImage = (ImageView) findViewById(R.id.spaceshipImage);
Animation hyperspaceJumpAnimation = AnimationUtils.loadAnimation(this, R.anim.hyperspace_jump);
spaceshipImage.startAnimation(hyperspaceJumpAnimation);
帧动画是一系列图片按照一定的顺序展示的过程,和放电影的机制想死,它的原理实在一定时间内切换多张有细微差异的图片而达到的动画效果。帧动画同样可以被定义在XML文件中或由Java代码实现,若定义在XML中,需要将其放在res/drawable/
目录下,其中 <animation-list>
标签必须作为根元素,<item>
标签代表一帧动画,示例如下:
<!-- rocket_thrust.xml -->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
<!-- 表示仅执行一遍 -->
android:oneshot="true">
<item android:drawable="@drawable/rocket_thrust1" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust2" android:duration="200" />
<item android:drawable="@drawable/rocket_thrust3" android:duration="200" />
</animation-list>
在Java代码中执行XML动画:
AnimationDrawable rocketAnimation;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
rocketImage.setBackgroundResource(R.drawable.rocket_thrust);
rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
}
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
rocketAnimation.start();
return true;
}
return super.onTouchEvent(event);
}
需要注意的是,start()
方法不可以在Activity的onCreate()
回调中执行,因为这时AnimationDrawable
还未完全依附于window(the AnimationDrawable is not yet fully attached to the window);若您需要执行一个无交互并且可以立即执行的动画,那么可以在Activity的回调方法onWindowFocusChanged()
中执行该动画。
标签:
原文地址:http://blog.csdn.net/vanpersie_9987/article/details/51699595