一、概述
1.1 简述
Android框架提供两大动画方案:属性动画与补间动画。这两者都非常有用,而且从谷歌文档来看,都会持续支持。但官方文档建议我们应优先考虑使用属性动画,因为属性动画更加灵活而且提供更多的可用特性。这两大动画方案之外Android还支持Drawable Animation(帧动画),通过逐帧播放来形成动画视觉效果。 属性动画引入自Android 3.0(API 11),其原理是给定对象的一个或多个属性,指定其开始与结束时的值,通过时间插值生成不同的属性值,并调用更新方法将属性值设置到对象内,引起对象重绘,从而形成动画效果。比如,通过不断的设置View的宽高来更新View的展示,形成缩放效果等。
1.2 属性动画相关概念
属性动画作用的对象,可以是没有渲染到界面的对象,当然这样,动画效果就看不到了,我们能得到的,是一段时间内不断改变属性的对象。同时,属性动画作用的对象属性,也可以是与界面展示无关的属性。而且,属性动画还支持自定义的属性,从而提高扩展性。简而言之:
属性动画可以作用任何对象的任何属性上。
一个属性动画执行需要的基本要素包括:承载动画执行的对象属性、动画持续时间和属性的变化区间。
属性动画支持定义动画的以下特性:
- Duration:指定动画的执行时间,默认300ms。
- Time interpolation:官方解释比较拗口,大概意思就是说这是一个输入为动画真实时间的函数,以此函数来计算动画属性;其实重点在于函数上,这个函数定义的是动画的变化速率,举个例子来看:假如有个函数y=x,x是动画的真实执行时间,y是映射后的执行时间,则这个动画是恒速执行的;假如映射函数是y=x*x,这个动画是就是不断加速运行的,因为y的变化速率在不断的增大。
- Repeat count and behavior:动画重复次数表示动画需要被执行的次数,动画重复时的行为有两种:从头开始或者反转执行;在动画执行结束时,如果没有执行完指定次数,则继续执行;假设执行次数为3,模式设为Restart从头执行,则执行顺序为“初始帧--插值-->结束帧-->初始帧--插值-->结束帧-->初始帧--插值-->结束帧”,模式设为Reverse,则执行顺序为“初始帧--插值-->结束帧--插值-->初始帧--插值-->结束帧”。
- Animator sets:将多个动画效果组成动画集合并设置彼此间的执行顺序,可设三种执行顺序:一起执行、顺序执行和在指定时间执行。
- Frame refresh delay:指定多长时间刷新一次动画的帧,默认值是10ms一次,但最终取决于当前系统的繁忙程度和系统多快能相应定时器,基本上我们可以不用考虑。
1.3 属性动画的过程描述
Android Developers上有完整的动画工作示例,这里简单的描述一下,并盗个图来做个生动的解释
假设有一个长方体(对象),基于其属性x(x是长方体在水平方向上的位置,单位px)做动画,动画持续时间是40ms,长方体在40ms内从位置x=0px移动到x=40px,帧刷新延迟是10ms,也就是每10ms移动一下长方体的位置:
如果长方体的时间插值是均匀的,也就是匀速运动,那么动画的效果是长方体在位置0停留10ms,位置10px停留10ms,位置20px停留10ms,位置30px停留10ms,最终到达位置40px,停下,动画结束,效果为:
如果我们想要先加速再减速的效果,那么我们调整时间插值器,可以形成加减速效果,长方体在位置0停留10ms,第10ms时计算出位移6px,在位置6px停留10ms,在第20ms时计算出位移20px,在位置20px停留10ms,在第30ms时计算出位移34px,停留在34px位置10ms,最终在第40ms时移动到位置40px,动画结束。这样视觉效果里,我们觉得长方体的移动速率在变化,先加速到中间位置,再减速到最终位置。
好了,这样说下来,应该对属性动画有点认识了吧,我们继续往下,看看属性动画和补间动画的区别。
1.4 属性动画与补间动画的区别
1) 补间动画的应用对象只能是View,不能用于非View对象,属性动画支持任意对象;
2) 补间动画的本质是对View做Transformation,并不能影响View的布局位置,只能影响View的可视位置。如果View从屏幕左边移动到右边,则其展示在右边了,但是这时候View相应点击事件还是在左边的位置,因为View并没有真的移动到右边,只是被绘制到右边了;
3)及时动画对象是View,补间动画能做的也比较有限,只能做平移/旋转/缩放/透明度四种变化效果,假如我们要不断改变View背景色,就搞不定了,属性动画表示毫无压力;
4)属性动画比补间动画更灵活,可以同时支持多个属性的动画,每个属性都可以独立定义插值器,各动画之间还可以做动画同步控制。
二、属性动画使用
2.1 结构概览
属性动画的每次插值执行过程可以分为两步:1)计算插值的属性值,2)将属性值设置到对象的属性上,也就是更新属性值。
Animator:所有属性动画的基类;注意和补间动画不同,补间动画所有类都继承自Animation;
ValueAnimator:属性动画最核心的插值逻辑管理类,包含最核心的属性值插值计算以及属性动画相关的控制策略(动画重复、属性值变化通知、处理自定义的属性值插值等)。前文说到的属性动画执行过程有两步,ValueAnimator只专注于完成第一步插值计算的操作,不负责第二步具体的属性刷新,它借助AnimatorUpdateListener.onAnimationUpdate()设置属性值,或进一步刷新界面;
ObjectAnimator:继承自ValueAnimator,专注于第二步操作,支持调用者指定对象和属性,其通过反射来调用属性的setter方法。通常情况下,我们都是用此类来做属性动画,因为其封装了属性设置操作,不用我们自己做更新;但有时候我们不得不使用ValueAnimator,比如说我要给某个View设置宽度,而View并没有setWidth接口,这时候,我们只能在onAnimationUpdate中更新View的LayoutParams。
AnimatorSet:将多个Animator组合在一起执行,支持一起执行、顺序执行和指定时间执行;
TimeAnimator:作用不大,主要作用是把动画已经执行的耗时和当前帧距离与上一帧的耗时回调到外面去。
2.2 属性动画的属性值计算
属性动画系统内使用Evaluators来告知系统如何计算给定属性的值。其输入是动画当前的执行时间(归一化的值,且已经被插值器处理过)、动画的开始值和结束值,输出是给定的属性开始值和结束值之间的插值。属性动画支持的Evalators包括:
IntEvaluator:计算int型的属性值插值;
FloatEvaluator:计算float型的属性值插值;
ArgbEvaluator:计算颜色的属性值插值;
TypeEvaluator:这是一个接口,我们可以实现此接口来定义自己的Evaluator。如果我们需要操作的属性不是int/float/color,就必须实现TypeEvaluator,告知如何计算属性插值。如果我们需要为int/float/color提供不同于默认实现的插值计算方案,也可以通过重写此接口实现。下面就是接口定义:
public interface TypeEvaluator< T> {
public T evaluate( float fraction, T startValue, T endValue);
}
2.3 属性动画的插值器
属性动画的插值器与补间动画的插值器完全相同,这里不再细述
如果觉得现有的插值器不满足你的需求,就实现TimeInterpolator接口自己写个插值器。基本上目前定义的插值器已经足够了。
2.4 使用ValueAnimator实现属性插值效果
前面我们说过,ValueAnimator不负责属性动画第二步,也就是对对象的属性进行操作,所以下面两个动作,都是集中在第一步,计算按时间变化的插值序列。
2.4.1 ValueAnimator支持int/float/color的插值动作,相应的接口是ofInt/ofFloat/ofArgb。一个简单的例子如下:
ValueAnimator animation = ValueAnimator.ofFloat(0f, 1f);
animation.setDuration(1000 );
animation.start();
这段代码表示ValueAnimator在start()执行后的1s内,不断的生成介于0~1之间的插值。这个插值可以作为某个对象的某个属性设置到对象里去。
2.4.2 ValueAnimator支持Object的插值动作,相应接口是ofObject。例子如下:
假设我们有个属性,类型为:
private static class TestData {
float size = 0 ;
TestData(float size) {
this.size = size;
}
}
我们需要为此对象定义一个TypeEvaluator,如下:
private static class MyTypeEvaluator implements TypeEvaluator<TestData> {
@Override
public TestData evaluate(float fraction, TestData startValue, TestData endValue) {
return new TestData(startValue.size + fraction * (endValue.size - startValue.size));
}
}
然后我们就可以将其放入ValueAnimator使用了:
ValueAnimator valueAnimator = ValueAnimator.ofObject(new MyTypeEvaluator(),
new TestData(0), new TestData( 1));
valueAnimator.setDuration(1000 );
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
TestData testData = (TestData) animation.getAnimatedValue();
Log.d( "TEST_PROPERTY_ANIM", "data is " + testData.size);
}
});
valueAnimator.start();
ValueAnimator通过MyTypeEvaluator类(自定义类型估值器)完成TestData(自定义属性值)的插值工作,并通知到AnimatorUpdateListener接口,这个接口里可以取到计算出的插值TestData,我们可以根据此值来刷新界面(当然,此Demo内我们只是把值打印出来了,没有刷新界面)。
2.5 使用ObjectAnimator实现属性动画效果
ObjectAnimator继承自ValueAnimator,ValueAnimator已经封装了计算随时间变化的插值属性值序列的任务,剩下来任务就是将计算好的插值属性值设置到对象的指定属性上。ObjectAnimator的目标就是完成剩下来的任务了。
使用ObjectAnimator与使用ValueAnimator非常相似,差别在于,使用ObjectAnimator时需要指定对象和对象属性的名字(也就是字符串):
比如说我们有一个View对象,对其float型的属性alpha做透明度0到1的渐显动画:
View view = new View(context);
ObjectAnimator.ofFloat(view, "alpha", 0f, 1f)
.setDuration(1000 )
.start();
ObjectAnimator需要依赖以下条件才能正常工作:
第一点,必须要确保指定的属性在对象内存在setter方法(且是驼峰法命名),上例中View类需要存在setAlpha方法设置透明度值,很幸运,View本来就有此方法。如果我们很不幸的发现并不存在这样的setter咋办呢?
下面有几种办法:
1)如果我们有权限,就直接去改了对象类,加个setter方法,上例中,如果View没有setAlpha,我们去View内加个setAlpha。好吧,我们是做不到的,但是google可以,setAlpha方法就是和属性动画在API 11一起添加的。
2)如果我们没办法改View,那就试着写个类把它包裹起来。类似下面这样的:
public class WrappedView extends View {
public WrappedView(Context context) {
super(context);
}
public void setAlpha(float alpha) {
AlphaAnimation animation = new AlphaAnimation(alpha, alpha);
animation.setDuration(0 );
animation.setFillAfter(true);
startAnimation(animation);
}
}
API 10以下我们取不到View的TransformationInfo,只能通过动画来间接实现。
3)如果连包装都难以做到,就只能用ValueAnimator了。在ValueAnimator回调的onAnimationUpdate做处理。
第二点,如果我们交给接口的values...参数只有一个值,也这样写属性动画:ObjectAnimator. ofFloat(view, "alpha", 1f),这唯一的一个值1f被认为是动画的结束值,那动画的初始值是多少呢?这时候就得靠getter方法返回了。所以View得定义getAlpha方法,没有getter方法,动画就不执行。
第三点,getter方法和setter方法必须保持和ObjectAnimator指定的属性值类型相同。假如有这样的调用:ObjectAnimator .ofFloat(targetObject, "propName", 1f ),那么targetObject对象必须存在两个方法:
void setPropName(float value);
float getPropName();
重点就是setter的参数是float,同时getter返回值也必须是float。
第四点,某些情况下我们需要强制调用invalidate/postInvalidate来强制刷新,保证动画效果显示在界面上。比如说我们对ImageView.getDrawable的drawable做属性动画,这些修改只能在View绘制时才会应用,所以我们要不断的调用invalidate来重新绘制View;但如果我们调用是View.setAlpha(基本上是所有View的setter方法),此方法会自己刷新界面,就不需要我们调用invalidate了。如果需要调用invalidate,那就应该放在onAnimationUpdate的回调中进行
2.6 使用动画集合
在实际应用场景中,可能需要指定动画与其他动画一起执行、在其他动画执行完成后执行或者在指定时间执行,属性动画提供了AnimatorSet来管理多个动画以及他们之间的执行顺序关系。可以指定多个动画一起执行、一个接一个执行或者在指定时间执行,因为AnimatorSet内持有的是抽象Animator类,所以这里也可以在动画集合内再套动画集合,形成复杂的动画效果。
下面这个例子是google官网的,我就没有加工了,假设有以下执行顺序:
1.执行bounceAnim。
2.随后同时执行squashAnim1/squashAnim2/stretchAnim1/stretchAnim2。
3.stretchAnim2执行完成后开始执行bounceBackAnim。
4.最后执行fadeAnim。
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();
//这里是为了展示AnimationSet嵌套
animatorSet.play(bouncer).before(fadeAnim);
animatorSet.start();
2.7 动画事件监听
2.7.1 监听正常的动画事件如开始/重复/结束/取消应使用Animator.AnimatorListener:
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(view, "alpha", 0f, 1f);
objectAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
//动画开始
}
@Override
public void onAnimationEnd(Animator animation) {
//动画结束
}
@Override
public void onAnimationCancel(Animator animation) {
//动画取消,不论动画如何结束,取消还是正常结束,都会回调onAnimationEnd
}
@Override
public void onAnimationRepeat(Animator animation) {
//动画重复执行
}
});
如果不想监听所有的事件,可以使用AnimatorListenerAdapter,这个类实现了AnimatorListener,并提供空实现。
2.7.2 监听属性动画每一次插值刷新界面的动作,使用ValueAnimator.AnimatorUpdateListener
objectAnimator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//属性动画开始了新的插值操作 ,动画每刷新一帧则调用一次
}
});
在此回调内,可以通过animation.getAnimatedValue()来取得插值的值。如果是使用ValueAnimator,一定需要实现此对象来实现界面刷新,形成动画效果。
2.8 自定义TypeEvaluator
前面已经写过一个简单的TypeEvaluator了,TypeEvaluator的作用是对未知属性类型的值的插值进行抽象,只有一个接口evaluate需要实现。其输入是当前的动画执行时间(经过归一化和速率变化处理的时间)fraction、属性开始值startValue和属性结束值endValue,绝大部分情况下,这里面的逻辑都是startValue+(endValue - startValue) * fraction;
需要注意一点,fraction已经由外面的插值器(TimeInterpolator)做过加速度变化处理,所以在TypeEvaluator内不需要再考虑速率变化。
举个系统实现的Float插值的插值器做例子:
public class FloatEvaluator implements TypeEvaluator<Number> {
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
2.9 使用关键帧来做属性动画
一个关键帧包含一个“时间/属性值”对,可以此来指定一个动画在特定时间(关键帧中的时间)处于的特殊状态(关键帧 中的属性值),每个关键帧可以包含自己的插值器,此插值器的影响范围是上一个关键帧的时间到当前关键帧的时间内动画的行为。
我们可以用KeyFrame的ofInt()、ofFloat()或者ofObject()来实例化一个关键帧对象(注意,没有ofRgba)。然后通过PropertyValuesHolder.ofKeyframe来根据多个(至少两个,一个就没动画效果了)关键帧生成一个PropertyValuesHolder,有了这个对象后,我们就可以通过ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation)生成一个属性动画了。大概代码如下:
Keyframe kf0 = Keyframe. ofFloat(0f , 0f );
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(view, pvhRotation);
rotationAnim.setDuration(5000 );
2.10 PropertyValuesHolder使用
前面已经有过一个PropertyValuesHolder的使用举例了,其实PropertyValuesHolder的作用就是持有一个属性和它的开始值结束值,一个ObjectAnimator可以同时对多个属性进行操作,如果一个个写代码就太难看了,这时候我们可以通过PropertyValuesHolder来先枚举所有的属性和它们的开始结束值,然后将所有的PropertyValuesHolder一次性传入ObjectAnimator构造中,生成一个属性动画。
PropertyValuesHolder支持的属性类型包括:float/int/keyframe/Object,对应的接口是ofFloat/ofInt/ofKeyframe/ofObject等。
举个例子看看其使用:
PropertyValuesHolder valuesHolderX = PropertyValuesHolder.ofFloat("translationX", view.getWidth() * 0.5f, view.getWidth() * 1.5f );
PropertyValuesHolder valuesHolderY = PropertyValuesHolder.ofFloat("translationY", view.getHeight() * 0.5f, view.getHeight() * 1.5f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, valuesHolderX, valuesHolderY);
2.11 ViewPropertyAnimator使用
ViewPropertyAnimator提供一种简单的并行操作View的几种属性形成属性动画的方案。它在操作View属性时比ObjectAnimator效率更高一点,代码可读性更高些。ViewPropertyAnimator虽然以Animator命名,但其不继承Animator,而是调用ValueAnimator完成相关操作。其支持的操作有:translationX、translationY、translationZ、scaleX、scaleY、rotation、rotationX、rotationY、x、y、z、alpha。
下面是官网的一个例子对比,看一下就明白差别了:
同时操作属性,改变View的位置:
1)多个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();
2)一个ObjectAnimator
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder( myView, pvhX, pvhY).start();
3)使用ViewPropertyAnimator
myView.animate().x(50f ).y(100f );
三、属性动画实现补间动画效果
之前我们已经讨论过属性动画与补间动画的区别,以及属性动画相对于补间动画的优势。我们再强调一遍,补间动画改变的是View的绘制方式与位置,而不改变View的真实位置(影响draw过程而非layout过程),原因是相关的操作(平移旋转缩放)都是由View的父元素完成的,View自身并无操作方法来完成这些动作。结果就是可能View已经发生了动画变化,但自身并无改变,比如说,View在屏幕左边绘制,经过动画到屏幕右边绘制,这是View布局位置并未变化,相应点击的区域还在左边区域。Android3.0(API11)增加了相应的新属性和setter/getter方法来解决这个问题。
属性动画可以通过改变View的属性来真正的改变View的位置,同时当View的属性发生变化时,View会自动调用invalidate来刷新界面。View在3.0增加了以下属性来支持属性动画:
translationX/translationY:这两个变量辅助控制View的位置,它们是真实位置与父元素设置布局位置的差值。假设View的左坐标是left,上坐标是top,这两个值是View的容器设置的,在运行过程中不会发生改变,真实的View位置在left + translationX,top+translationY的位置。
rotation/rotationX/rotationY:这些属性控制View绕中心点的2D和3D旋转效果,其中2D效果由rotation表示。
scaleX/scaleY:控制View绕中心点的2D缩放效果。
pivotX/pivotY:控制View的中心点位置,缩放和旋转动画基于此位置来执行动画,默认在View中心位置。
x/y:描述View最终在其容器内的位置,如上所述,x = left + translationX, y = top + translationY。
alpha:表示View的透明度,1不透明,0全透明(不可见)。
要想用属性动画实现补间动画的效果,其实只需要创建属性动画,并指定上面这些属性即可
以下这些效果,如果希望启动Activity就执行,应写在Activity的onWindowFocusChanged内。同时,以下效果都是在3.0以上才能运行,因为这些属性和属性动画本身都是3.0以上才有的。
3.1 平移效果
举例:x轴从自身位置50%向右平移相对于自己宽度100%的距离,y轴从自身位置50%向下平移相对于自己100%高度的距离,时间1s,先加速再减速:
PropertyValuesHolder valuesHolderX = PropertyValuesHolder.ofFloat("translationX", view.getWidth() * 0.5f, view.getWidth() * 1.5f );
PropertyValuesHolder valuesHolderY = PropertyValuesHolder.ofFloat("translationY", view.getHeight() * 0.5f, view.getHeight() * 1.5f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, valuesHolderX, valuesHolderY);
animator.setDuration(1000);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.start();
3.2 缩放效果
举例:x轴从自身50%缩放到自身200%,y轴从自身50%缩放到自身200%,中心点(10%,10%)时间1s,先加速再减速:
PropertyValuesHolder valuesHolderX = PropertyValuesHolder.ofFloat("scaleX", 0.5f, 1f);
PropertyValuesHolder valuesHolderY = PropertyValuesHolder.ofFloat("scaleY", 0.5f, 2f );
//两个中心点其实可以用set方法直接设置,mView.setPivotX(pivotX)
float pivotX = mView.getWidth() * 0.1f;
float pivotY = mView .getHeight() * 0.1f;
PropertyValuesHolder valuesHolderPvX = PropertyValuesHolder.ofFloat("pivotX", pivotX, pivotX);
PropertyValuesHolder valuesHolderPvY = PropertyValuesHolder.ofFloat("pivotY", pivotY, pivotY);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder( mView, valuesHolderX, valuesHolderY, valuesHolderPvX, valuesHolderPvY);
animator.setDuration(1000);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.start();
3.3 旋转效果
举例:View绕自身中心点(10%,10%)位置,从-270旋转到180,时间1s,先加速再减速 :
PropertyValuesHolder valuesHolderX = PropertyValuesHolder.ofFloat("rotation", - 270f, 180f);
//两个中心点其实可以用set方法直接设置,mView.setPivotX(pivotX)
float pivotX = mView .getWidth() * 0.1f;
float pivotY = mView .getHeight() * 0.1f;
PropertyValuesHolder valuesHolderPvX = PropertyValuesHolder.ofFloat("pivotX", pivotX, pivotX);
PropertyValuesHolder valuesHolderPvY = PropertyValuesHolder.ofFloat("pivotY", pivotY, pivotY);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder( mView, valuesHolderX, valuesHolderPvX, valuesHolderPvY);
animator.setDuration(1000);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.start();
3.4 Alpha效果
举例:View从透明到完全不透明,时间1s,先加速再减速:
ObjectAnimator animator = ObjectAnimator.ofFloat(mView, "alpha", 0f , 1f );
animator.setDuration(1000);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.start();
四、使用XML定义属性动画
在XML中定义属性动画更容易在多个Activity中复用,且更容易编辑动画顺序。考虑到属性动画使用了新的属性动画API,为了与补间动画的动画文件区分开,在Android 3.1后,属性动画的XML动画文件定义在 res/animator/文件夹中。
属性动画各接口与XML tag对应关系为:
ValueAnimator对应<animator>;
ObjectAnimator对应<objectAnimator>;
AnimatorSet对应<set>;
下面是官网上提供的一个例子:
<set xmlns:android="http://schemas.android.com/apk/res/android"
android :ordering="sequentially">
<set >
<objectAnimator
android:duration="500"
android:propertyName="x"
android:valueTo="400"
android:valueType="intType" />
<objectAnimator
android:duration="500"
android:propertyName="y"
android:valueTo="300"
android:valueType="intType" />
</set >
<objectAnimator
android:duration= "500"
android:propertyName= "alpha"
android:valueTo= "1f" />
</set>
这里有两个动画集合,外层的动画集合嵌套内层的动画集合,外层动画集合有两个子元素,它们按照android:ording的要求顺序播放,子集合(xy变化)执行完成后再执行下面的alpha动画。子集合内,x的属性动画和y的属性动画一起执行。
XML的动画文件定义好后,我们需要将其加载成Java对象使用,并设置各动画的target,具体方法如下:
AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(context, R.anim.property_animator);
set.setTarget(myObject);
set.start();
五、属性动画实现ViewGroup内Layout变化动画
属性动画对处理ViewGroup内子元素变化导致的动画行为提供了非常好的支持。使用LayoutTransition类来处理ViewGroup元素的变化动画。通过给ViewGroup设置LayoutTransition,View从ViewGroup内添加/移除/变的可见(setVisibility(VISIBLE))/变的不可见(setVisibility(GONE))等情况都可以执行一个显示或隐藏的动画。当添加/删除某个View时,ViewGroup内其他的View也可以展示挪到新位置的动画。
具体设置方法如下:
LayoutTransition transition = new LayoutTransition();
transition.setAnimator(LayoutTransition.APPEARING, objectAnimatorApearing);
transition.setAnimator(LayoutTransition.CHANGE_APPEARING, objectAnimatorChangeAppearing);
transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, objectAnimatorChangeDisappearing);
transition.setAnimator(LayoutTransition.CHANGING, objectAnimatorChanging);
transition.setAnimator(LayoutTransition.DISAPPEARING, objectAnimatorDisappearing);
ViewGroup group = new LinearLayout(context);
group.setLayoutTransition(transition);
对应的常量意义为:
APPEARING:当View在容器内展示出来时显示的动画;
CHANGE_APPEARING:当View在容器内展示出来时,其他被影响了的View的动画;
DISAPPEARING:当View从容器内消失时展示的动画;
CHANGE_DISAPPEARING:当View从容器内消失时,其他被影响了的View的动画;
CHANGING:当不是View添加/移除导致的容器重新布局(Layout Change)时,所有被影响了的View的动画;这个属性并不是自动默认开启的,需要通过transition.enableTransitionType(LayoutTransition. CHANGING)来开启;
如果不通过setAnimator设置而通过enableTransitionType开启动画效果的话,LayoutTransition对这些情况的动画都支持使用默认值。
在XML内将android:animateLayoutchanges置为true就可以开启ViewGroup的layout transitions。如下:
<LinearLayout
android :orientation="vertical"
android :layout_width="wrap_content"
android :layout_height="match_parent"
android :id="@+id/verticalContainer"
android :animateLayoutChanges="true" />
六、其他注意要点
1.Android属性动画只能支持Android3.0以上版本,想要支持3.0以前的版本,需要使用NineOldAndroids包。
七、总结
本文总结了属性动画的使用方法,Android属性动画相对于补间动画而言,的确是发生了质的变化,整个框架的抽象性设计非常合理,扩展性也非常强。在实际使用过程中,如果动画很简单,而且没有文中提到的补间动画的坑(View显示位置与布局位置不同),可以考虑使用补间动画,如果动画比较复杂,建议使用属性动画。