从一开始Android 就提供了两种传统视图动画:逐帧动画(Frame Animation)和补间动画( Tweened animation )。但随着Android的发展,逐帧动画和补间动画先天不足慢慢地提现出来,在 Android 3.0(API 11)时属性动画(Property Animation)是作为一种全新的动画模式应运而生并且当仁不让地成为高级UI中重要的技术之一,有必要再次进行总结和巩固,查缺补漏。
逐帧动画(Frame Animation) 和 补间动画( Tweened animation) 的局限性主要体现在以下几个方面:
作用对象太局限,补间动画只能作用于View上,只能给View及其子类添加补间动画,但无法对非View的对象进行动画操作。对于一些特殊的需求,比如只需要针对View的某个属性或者非View添加动画效果,补间动画则无能为力。
没有改变View的属性,仅仅是了改变视觉效果。补间动画只是改变了View的视觉效果,而不会真正去改变View的属性。如,将屏幕左上角的按钮 通过补间动画 移动到屏幕的右下角点击当前按钮位置(屏幕右下角)是没有效果的,因为实际上按钮还是停留在屏幕左上角,补间动画只是将这个按钮绘制到屏幕右下角,改变了视觉效果而已。
动画效果单调,补间动画仅支持平移、旋转、缩放和透明度四种简单的动画需求。
而属性动画(Property Animation) 则:
属性动画的基类是Animator,通过动画的执行类(Animator系)来自动计算(插值器直接影响计算结果)并设置动画操作的对象的属性、持续时间,开始和结束的属性值,时间差值等,然后系统会根据设置的参数动态地改变对象的属性从而形成的一种动画效果,简而言之就是持续动态地改变View的真实属性从而形成动态效果,属性动画要求动画作用的对象提供该属性的set方法,属性动画根据传递的该属性的初始值和最终值,以动画的效果多次去调用set方法。每次传给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。
ValueAnimator作为属性动画的基本计时引擎,直接继承自Animator,可根据设置的插值器自动计算对象的属性值,具备计算动画的所有核心功能。属性动画实现由两部分组成:计算动画所需的属性值和把这些动画值动态设置到目标对象上,其中由插值器负责计算,通过监听ValueAnimator.AnimatorUpdateListener接口把值手动设置到目标对象上(本质上是由估值器去设置属性值)。因为ValueAnimator是基于数值变化(从初始值逐渐变化到结束值)产生的动画效果,无法直接作用于对象,只能通过动画监听并获取动画过程中的过渡值,然后不断改变对象的属性即可。简而言之,在开发中直接使用ValueAnimator实现属性动画的话,需要自己去监听接口并动态改变对应的属性,从而形成效果。
构造ValueAnimator主要有两种方式:通过代码动态创建和通过AnimatorInflater映射属性动画XML资源文件映射
根据不同类型的属性值选择对应的方法创建ValueAnimator对象,若值传递一个实参的时,默认为当前对象该属性的值为开始值,动画框架会通过getPropName方法反射获取(因此对应的属性字段必须是实际存在的),再设置的值为终点。若传递两个,则一个为开始、一个为结束,若传递三个则表示在动画持续时间内由值1–>值2–>值3以此类推,至于中间的变化规则由插值器来决定。
方法 | 说明 |
---|---|
static ValueAnimator ValueAnimator.ofInt(int … values) | 处理整形参数变化的动画,内部是通过整型估值器 IntEvaluator ,比如改变View的尺寸属性或透明度等 |
static ValueAnimator ValueAnimator.ofFloat(float … values) | 处理浮点型参数变化的动画,内部是通过浮点型估值器 FloatEvaluator ,比如改变View的尺寸属性或透明度等 |
static ValueAnimator ValueAnimator. ofArgb(int… values) | 处理颜色参数变化的动画,比如实现颜色渐变的 |
static ValueAnimator ValueAnimator.ofObject(TypeEvaluator evaluator, Object… values) | 处理Object对象,需要传入估值器,本质上还是操作 值,只是是采用将多个值 封装到一个对象里的方式 同时对多个值一起操作而已。 |
static ValueAnimator ValueAnimator.ofPropertyValuesHolder(PropertyValuesHolder… values) | 处理PropertyValuesHolder |
添加动画状态监听 | |
添加动画属性变化监听 |
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:valueFrom="1"
android:valueTo="0.1"
android:valueType="floatType"
android:repeatCount="1"
android:repeatMode="reverse" />
通过addUpdateListener方法设置ValueAnimator.AnimatorUpdateListener监听,并在监听中改变目标对象的属性值,动画效果需要自己去实现,在动画监听接口内部获取动画值,并手动修改对应的属性值
//每一次改变值的时候都会回调这个接口
public void addUpdateListener(AnimatorUpdateListener listener) {
@link{ArrayList mUpdateListeners}
if (mUpdateListeners == null) {
mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
}
mUpdateListeners.add(listener);
}
public void startObjectAnimatorAnim(final View v) {
/**
* 1) 创建ValueAnimator对象
* 2) 设置动画效果的取值变化范围,即根据传入的多个Int参数进行平滑过渡,假如传入0和1,表示将值从0平滑过渡到1,二如果传入了3个Int参数 1,0.1,1 ,则是先从1平滑过渡到0.1,再从0.1平滑过渡到1,以此类推。
* 3)设置了估值器,只不过这里是默认提供了,就不需要再显示去调用setEvaluator方法设置估值器了,这也意味着其他类型的需要自己设置估值器
*/
ValueAnimator valueAnimator =ValueAnimator.ofFloat(1,0.1f,1f);
///ValueAnimator valueAnimator = (ValueAnimator) AnimatorInflater.loadAnimator(getBaseContext(), R.animator.animtor_iv_scale);
valueAnimator.setDuration(3000);
valueAnimator.setStartDelay(300);
/**
* 设置动画重复播放次数,动画播放次数 = 重放次数+1,ValueAnimator.INFINITE表示无限循环播放
*/
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
/**
* 设置动画重复播放模式,ValueAnimator.RESTART 表示正序播放(默认),ValueAnimator.REVERSE逆序
*/
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
valueAnimator.setTarget(iv);//不设置也不影响
//将动画过程中的值手动设置给ImageView的matrix ,从而自己实现动画
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
//获取动画改变后的值
float data = (float) animator.getAnimatedValue();
Matrix matrix = new Matrix();
matrix.setScale(data,data);
//TODO mageView要支持matrix,需要设置ImageView的ScaleType为matrix
iv.setImageMatrix(matrix);
}
});
valueAnimator.start();
}
通过属性动画实现在3s内先缩小再还原的动画效果。
ObjectAnimator 直接继承自ValueAnimator支持将在目标对象和对象的属性上执行动画效果,绝大多数情况下可以替代ValueAnimator实现相同的动画效果,而且比ValueAnimator 更简便,但是使用ObjectAnimator执行属性动画时需要同时满足一些前提条件。
所作用的对象必须提供该对应属性的getter和setter方法,因为属性动画框架在动画执行过程中不断通过反射去调用setter更新对应的属性值,每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。所以使用ObjectAnimator更新某个属性时,其对象必须提供setter方法,如果动画的时候没有传递初始值,那么还要提供getter方法因为系统要去获取其初始值。
对应属性的setter所做的改变必须能够通过某种方法反映出来,比如会带来ui的改变啥的。
但如果要想给任意属性的执行属性动画,可以参考Android进阶——属性动画Property Animation详解与应用(二)
支持的属性 | 用法 |
---|---|
translationX,translationY | View相对于原始位置的偏移量 |
rotation,rotationX,rotationY | 旋转,rotation用于2D旋转角度,3D中用到后两个 |
scaleX,scaleY | 缩放比 |
x,y | View的最终坐标,x=View的left+translationX,y=top+translationY |
alpha | 透明度 |
public void startObjectAnimatorAnim(View v) {
ObjectAnimator.ofFloat(v, "rotationY", 0.0F, -360.0F)//rotationY为v的实际属性字段,如果任意传递字段值程序有可能无效果甚至crash,本例中是ImageView的实际属性
.setDuration(5000).start();
}
但是随便传入一个字符串,作为属性值,启动动画时候依然会正常执行,监听也会触发。
ObjectAnimator animator = ObjectAnimator.ofFloat(btn, "crz", 1.0f,0f,1f);
animator.setDuration(2000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
///animation.getAnimatedFraction();百分比
///Gif中的动画效果 不是由 ObjectAnimator提供的,而是在接口中通过代码实现的,如果注释掉则没有任何动画
float value = (float) animation.getAnimatedValue();//百分比的所对应的值
btn.setScaleX(0.5f + value / 200);
btn.setScaleY(0.5f + value / 200);
}
});
animator.start();
API 19之后当需要对一个View的多个属性进行动画可以用ViewPropertyAnimator类,该类对多属性动画进行了优化,会合并一些invalidate()来减少刷新视图,ViewPropertyAnimator 只能通过View.animate()方法获取ViewPropertyAnimator 对象,此时获取的动画对象就专用于操作当前view。
如果ViewPropertyAnimator 可以实现的功能尽量用ViewPropertyAnimator ,因为简单快捷且性能更优。
//android.view.View#animate
public ViewPropertyAnimator animate() {
if (mAnimator == null) {
mAnimator = new ViewPropertyAnimator(this);
}
return mAnimator;
}
ViewPropertyAnimator支持的动画 | 说明 |
---|---|
alpha(float value) | 设置View的透明度,value最终值 |
alphaBy(float value) | 设置View的透明度,value是在view当前值的基础上的偏移量,rotation(float value):旋转View,正值顺时针,负值逆时针,value最终值 |
rotationBy(float value) | 旋转,在当前值得基础上偏移量 |
rotationX(float value) | 绕x轴旋转 |
rotationXBy(float value) | 当View旋转的基础上以value为偏移量绕X轴旋转 |
rotationY(float value) | 绕Y轴旋转 |
rotationYBy(float value) | 在当前旋转的基础上绕Y轴旋转,translationYBy 可以多次移动View,而translationY多次执行没有效果 |
scaleX(float value) | 缩放view的X轴方向上的大小 |
scaleXBy(float value) | 当前View缩放的基础上,在X轴方向上对view进行缩放 |
scaleY(float value) | 缩放view的Y轴方向上的大小 |
scaleYBy(float value) | 当前View缩放的基础上,对view的Y轴方向进行缩放 |
translationX(float value) | 沿X轴方向平移,value大于0,X轴正方向 |
translationXBy(float value) | 带有偏移量的平移 |
translationY(float value) | 沿Y轴方向平移,value大于0,沿Y轴正方向平移 |
translationYBy(float value) | 在当前值的基础上,在Y轴方向上平移 |
x(float value) | 在当前值的基础上,修改view 的X坐标 |
xBy(float value) | 在当前值的基础上,修改view 的X坐标 |
y(float value) | 在当前值的基础上,修改View的Y的坐标 |
yBy(float value) | 在当前值的基础上,修改View的Y的坐标 |
z(float value) | 在当前值的基础上,修改View的Z的坐标 |
zBy(float value) | 在当前值的基础上,修改View的Z的坐标 |
ViewPropertyAnimator设置完毕之后无需手动调用start也会自动执行动画,也可以手动调用start函数强制动画立刻执行,而且在start方法之后还可以增加动画效果
ViewPropertyAnimator viewPropertyAnimator = view.animate();
viewPropertyAnimator.setDuration(2000)
.translationY(300)
.translationX(300)
//指定一个操作在下一个动画结束的时候发生。
.withEndAction(new Runnable() {
@Override
public void run() {
}
})
//指定一个操作在下一个动画开始的时候发生。
.withStartAction(new Runnable() {
@Override
public void run() {
}
}).start()
.alpha(0.3f);
//ViewPropertyAnimator viewPropertyAnimator = view.animate().setDuration(2000).translationYBy(500).start();
public void startPropertyValueHolderAnim(View v) {
PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("alpha", 1f, 0.5f);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleX", 1f, 0.5f);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("scaleY", 1f, 0.5f);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(v, holder1, holder2, holder3);
animator.setDuration(500);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();
}
keyFrame是一个 时间/值 对,通过它可以定义一个在特定时间的特定状态,即关键帧,而且在两个keyFrame之间可以定义不同的Interpolator,就好像多个动画的拼接,第一个动画的结束点是第二个动画的开始点。KeyFrame是抽象类,要通过ofInt(),ofFloat(),ofObject()获得适当的KeyFrame,然后通过PropertyValuesHolder.ofKeyframe获得PropertyValuesHolder对象。比如对btn对象的width属性值执行动画:
/*第一个参数为时间百分比,第二个参数是在第一个参数的时间时的属性值。*/
Keyframe kf0 = Keyframe.ofInt(0, 400);//开始时 Width=400
Keyframe kf1 = Keyframe.ofInt(0.25f, 200);//动画开始1/4时 Width=200
Keyframe kf2 = Keyframe.ofInt(0.5f, 400);//动画开始1/2时 Width=400
Keyframe kf4 = Keyframe.ofInt(0.75f, 100);//动画开始3/4时 Width=100
Keyframe kf3 = Keyframe.ofInt(1f, 500);//动画结束时 Width=500
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("width", kf0, kf1, kf2, kf4, kf3);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(btn2, pvhRotation);
rotationAnim.setDuration(2000);
定义了一些Keyframe后,通过PropertyValuesHolder类的方法ofKeyframe一个PropertyValuesHolder对象,然后通过ObjectAnimator.ofPropertyValuesHolder获得一个Animator对象,两种方式等价。
ObjectAnimator oa=ObjectAnimator.ofInt(btn2, "width", 400,200,400,100,500);
oa.setDuration(2000);
oa.start();
TimeInterpolator为所有插值器的接口,API提供的一组算法接口,用来操作动画执行是的变换规则,不同的插值器代表不同的计算方式(即实现不同的动画效果),大致分为九种:
public void startInterpolatorApply(final View v) {
ValueAnimator animator = new ValueAnimator();
animator.setDuration(3000);
animator.setObjectValues(new PointF(0, 0));
final PointF point = new PointF();
//设置估值器
animator.setEvaluator(new TypeEvaluator() {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
point.x = 100f * (fraction * 5);
// y=vt=1/2*g*t*t(重力计算)
point.y = 0.5f * 98f * (fraction * 5) * (fraction * 5);
return point;
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
v.setX(point.x);
v.setY(point.y);
}
});
// 加速插值器,公式: y=t^(2f) (加速度参数. f越大,起始速度越慢,但是速度越来越快)
animator.setInterpolator(new AccelerateInterpolator(10));
// 减速插值器公式: y=1-(1-t)^(2f) (描述: 加速度参数. f越大,起始速度越快,但是速度越来越慢)
// animator.setInterpolator(new DecelerateInterpolator());
// 先加速后减速插值器 y=cos((t+1)π)/2+0.5
// animator.setInterpolator(new AccelerateDecelerateInterpolator());
// 张力值, 默认为2,T越大,初始的偏移越大,而且速度越快 公式:y=(T+1)×t3–T×t2
// animator.setInterpolator(new AnticipateInterpolator());
// 张力值tension,默认为2,张力越大,起始和结束时的偏移越大,
// 而且速度越快;额外张力值extraTension,默认为1.5。公式中T的值为tension*extraTension
// animator.setInterpolator(new AnticipateOvershootInterpolator());
// 弹跳插值器
// animator.setInterpolator(new BounceInterpolator());
// 周期插值器 y=sin(2π×C×t) 周期值,默认为1;2表示动画会执行两次
// animator.setInterpolator(new CycleInterpolator(2));
// 线性插值器,匀速公式:Y=T
// animator.setInterpolator(new LinearInterpolator());
// 公式: y=(T+1)x(t1)3+T×(t1)2 +1
// 描述: 张力值,默认为2,T越大,结束时的偏移越大,而且速度越快
// animator.setInterpolator(new OvershootInterpolator());
animator.start();
}
以FloatEvaluator 的实现为例,简单介绍下估值器计算初始值过渡到结束值的逻辑:
public class FloatEvaluator implements TypeEvaluator<Number>{
/**
* @param fraction 表示动画完成度或百分比(根据它来计算当前动画的值)
* @param startValue 动画初始值
* @param endValue 结束值
*/
@Override
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
}
总之,Interpolator插值器只是控制动画变化的规则(匀速、线性等等),而在动画过程中估值器TypeEvaluator 根据属性的开始、结束值与TimeInterpolation计算出的系数,自动计算出对应时间的属性值。
public void runAnimSets(View view) {
ObjectAnimator animScaleX = ObjectAnimator.ofFloat(mImage, "scaleX",
2.0f, 0.5f);
ObjectAnimator animScaleY = ObjectAnimator.ofFloat(mImage, "scaleY",
2.0f, 0.5f);
ObjectAnimator animAlpha=ObjectAnimator.ofFloat(mImage,"alpha",1.0f,0.0f);
AnimatorSet animSet = new AnimatorSet();
animSet.setDuration(4000);
animSet.setInterpolator(new LinearInterpolator());
//animSet.play(animScaleX ).with(animScaleY ).after(animAlpha);//链式调用顺序
//animSet.play(animScaleY).with(animAlpha).before(animScaleX);
//顺序执行:传入的动画不能重复
//animatorSet.playSequentially(animScaleX, animScaleY, animAlpha);
//三个动画同时执行
animSet.playTogether(animScaleX, animScaleY,animAlpha);
animSet.start();
}