android动画入门:属性动画

前言

属性动画是API11加入的新特性,旨在实现更加绚丽的动画效果,不再像 View动画那样只能支持四种简单的变换,其可以对任意对象的属性进行动画而不仅仅局限于View。

由于属性动画从API11才加入,我们可以采用nineoldandroids开源动画库来兼容以前版本,Nineoldandroids的功能和系统原生的android.animation.*中类的功能完全一致,使用方法也完全一样,只要我们用nineoldandroids 来编写动画,就可以在所有的Android系统上运行。

属性动画的使用

下面来集中学习属性动画中的几个常用的动画类:

  • ValueAnimator
  • ObjectAnimator 其继承自 ValueAnimator
  • AnimatorSet 动画集合
1. ValueAnimator

关于ValueAnimator,首先先要理解一个概念,ValueAnimator 的动画并不是直接作用于对象的,而是对一个值做动画,然后我们通过监听这个值得变化,来完成我们对所要变换的对象的属性值的修改,从而实现动画效果。下面通过一个例子,可能会更好理解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void startAnimation(final View view, final int start ,final int end){
ValueAnimator valueAnimator =ValueAnimator.ofInt(1,100);
valueAnimator.setDuration(3000).start();
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
private IntEvaluator evaluator = new IntEvaluator();
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//获取当前动画进度的比值
float fraction = animation.getAnimatedFraction();
//估值器通过比值计算变换后的宽度
view.getLayoutParams().width = evaluator.evaluate(fraction, start, end);
view.requestLayout();
}
});
}

从上面的例子可以看出,ValueAnimator 将整数值从1 变换至 100,间隔时间是 3000 毫秒,我们需要添加 AnimatorUpdateListener 来监听这个变换过程,在onAnimationUpdate 方法中,通过获取动画的变换进度来最终确定变换结果,并完成对 对象属性值的修改。

2. ObjectAnimator

ObjectAnimator 继承自 ValueAnimator , 并做了新的封装。ObjectAnimator 允许指定 作用的对象以及变换的属性,我们只需要调用其提供的方法,剩下复杂的变换过程,交给ObjectAnimator来完成即可。
在使用ObjectAnimator之前,我们先来看一下几个常用的View属性成员:

  • translationX,translationY:X/Y 轴方向偏移量的变化,相对于view 自身。
  • rotationX,rotationY:沿 X/Y轴方向旋转。
  • scaleX,scaleY:沿 X/Y 轴缩放。
  • x,y:改变view在整个屏幕上的绝对坐标。
  • alpha:透明度的变化。

下面举例来说明一下:

1
2
//沿Y轴向右平移100px ,正数为向右,负数为向左
ObjectAnimator.ofFloat(view, "translationY", 100).start();

ObjectAnimator类提供了ofInt、ofFloat、ofObject 等常用的方法,接下来我们来看一下这些方法调用时都需要什么参数:

1
2
3
4
5
6
7
8
/**
*
* @param target
* @param propertyName
* @param values
* @return
*/
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values);

从上面这个例子来看,target,propertyName 很好理解,分别是 作用的对象及 对象的属性名。 重点来说一下 , values。values 是可以设置多个的,如下所示:

1
2
ObjectAnimator.ofFloat(view, "translationY", 100).start();
ObjectAnimator.ofFloat(view, "translationY", 100,150,200).start();

当只设置一个时就把通过getTranslationY()反射获取的值作为初始值,设置的值作为终点;如果设置两个或多个,那么就会不断的改变属性值,而初始值则为 第一个属性值。

ObjectAnimator是通过不断地调用setXXX方法更新属性值来达到动画的效果的,这就要求Object必须声明有getXXX和setXXX方法。

在ObjectAnimator 的使用过程中,总会遇到 设置的属性名 在Object 中并不存在,或者没有提供getXXX和setXXX方法,再或者提供了方法并不能实现预期的效果等这类问题。那么下面提供一个解决方法: 我们可以自定义一个类来包装 目标对象,并提供getXXX和setXXX方法。
下面通过举例来看一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//首先定义包装类
private static class ViewWrapper{
private View target;
private ViewWrapper(View target) {
super();
this.target = target;
}
public int getWidth() {
return target.getLayoutParams().width;
}
public void setWidth(int width) {
this.target.getLayoutParams().width = width;
target.requestLayout();
}
}
//直接使用
ViewWrapper viewWrapper = new ViewWrapper(view);
ObjectAnimator.ofFloat(viewWrapper, "width", 100).start();

说到这里,有人想问:如果我想实现同时修改多个属性怎么办呢? 别急,这个时候我们就需要用到 PropertyValuesHolder 类。具体如下:

1
2
3
4
5
6
7
8
9
10
11
public static void performAnimate(View view) {
PropertyValuesHolder pvhSX = PropertyValuesHolder.ofFloat("scaleX", 1f,0.3f);
PropertyValuesHolder pvhSY = PropertyValuesHolder.ofFloat("scaleY", 1f,0.3f);
PropertyValuesHolder pvhTY = PropertyValuesHolder.ofFloat("translationY", 0, -250, 50);
PropertyValuesHolder pvhTX = PropertyValuesHolder.ofFloat("translationX", 0, 200);
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(
view, pvhSX, pvhSY, pvhTY, pvhTX).setDuration(1200);
objectAnimator.setInterpolator(new DecelerateInterpolator());
objectAnimator.start();
}

并且PropertyValuesHolder还提供了 ofFloat()、ofInt()以及ofKeyframe 等方法。ofFloat()、ofInt() 与 ObjectAnimator 中的方法是相同使用的,这里不做过多解释,这里重点说一下,ofKeyframe,先来看下面的例子:

1
2
3
4
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);
Keyframe kf1 = Keyframe.ofFloat(.5f, 2f);
Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("scaleX", kf0, kf1, kf2);

关于Keyframe

1
2
//参数 fraction 表示 动画进度 value 表示动画播放到当前进度的参数值
public static Keyframe ofFloat(float fraction, float value)

3. AnimationSet

AnimationSet 是动画集合,可以定义一组动画,使用起来也是极其简单的。

1
2
3
4
5
6
7
8
ObjectAnimator objectAnimatorA = ObjectAnimator.ofFloat(view, "translationY", 100);
ObjectAnimator objectAnimatorB = ObjectAnimator.ofFloat(view, "scaleY", 0.3f,1.5f);
AnimatorSet animSet = new AnimatorSet();
animSet.setDuration(5000);
// animSet.playTogether(objectAnimatorA, objectAnimatorB, ...); //多个动画同时执行
animSet.play(objectAnimatorA).after(objectAnimatorB); //多个动画先后执行
animSet.start();

4. 属性动画监听器

这里提一下,属性动画提供了两个接口,用于监听动画的播放过程。

  • AnimatorUpdateListener
    监听动画整个播放过程

    1
    2
    3
    4
    5
    6
    7
    8
    objectAnimatorA.addUpdateListener(new AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator arg0) {
    // TODO Auto-generated method stub
    }
    });
  • AnimatorListener
    用于监听动画的开始,结束,取消以及重复播放。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    objectAnimatorA.addListener(new AnimatorListener() {
    @Override
    public void onAnimationStart(Animator arg0) {
    // TODO Auto-generated method stub
    }
    @Override
    public void onAnimationRepeat(Animator arg0) {
    // TODO Auto-generated method stub
    }
    @Override
    public void onAnimationEnd(Animator arg0) {
    // TODO Auto-generated method stub
    }
    @Override
    public void onAnimationCancel(Animator arg0) {
    // TODO Auto-generated method stub
    }
    });

插值器和估值器

关于插值器和估值器 这里主要讲一下基本概念和使用方式。

1. 插值器(Interpolator)

插值器用于实现动画的非匀速变化,比较常见的插值器(Interpolator)有 LinearInterpolator (线性插值器:匀速动画),AcclerateDecelerateInterpolator(加减速插值器:两头慢中间快)和 DecelerateInterpolator(减速插值器:动画速度越来越慢)。

1
objectAnimator.setInterpolator(new AccelerateDecelerateInterpolator());

2. 估值器(TypeEvluator)

估值器用于 计算变换后的属性值。通过动画进行过程的百分比,确定当前的属性值。

1
2
3
4
5
6
7
public class IntEvaluator implements TypeEvaluator<Integer> {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
}

从源码可以看出,其实就是一个简单的估值算法。使用方式在 ValueAnimator 的示例中也有提到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void startAnimation(final View view, final int start ,final int end){
ValueAnimator valueAnimator =ValueAnimator.ofInt(1,100);
valueAnimator.setDuration(3000).start();
valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
private IntEvaluator evaluator = new IntEvaluator();
@Override
public void onAnimationUpdate(ValueAnimator animation) {
//获取当前动画进度的比值
float fraction = animation.getAnimatedFraction();
//估值器通过比值计算变换后的宽度
view.getLayoutParams().width = evaluator.evaluate(fraction, start, end);
view.requestLayout();
}
});
}

而 在ObjectAnimator中的使用可以 这样使用:

1
objectAnimator.setEvaluator(new IntEvaluator());

不过从源码来看,估值器设置与否,实现的计算方式实际上与IntEvaluator是一样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public int getIntValue(float fraction) {
if (mNumKeyframes == 2) {
if (firstTime) {
firstTime = false;
firstValue = ((IntKeyframe) mKeyframes.get(0)).getIntValue();
lastValue = ((IntKeyframe) mKeyframes.get(1)).getIntValue();
deltaValue = lastValue - firstValue;
}
if (mInterpolator != null) {
fraction = mInterpolator.getInterpolation(fraction);
}
if (mEvaluator == null) {
return firstValue + (int)(fraction * deltaValue);
} else {
return ((Number)mEvaluator.evaluate(fraction, firstValue, lastValue)).intValue();
}
}
//...省略了很多代码
}

属性动画的特殊使用

通过属性动画 为 ViewGroup 的子元素设置动画,相对于View 动画 ,有了更多的选择。
如果想开启默认的效果只需要在布局文件中添加:

1
android:animateLayoutChanges=”true”

如果想实现更炫的效果,就需要用到LayoutTransition,其有5种动画状态可以编辑:

  • LayoutTransition.APPEARING
    表示 View出现时 自身需要的动画效果
  • LayoutTransition.DISAPPEARING
    表示 View消失时 自身需要的动画效果
  • LayoutTransition.CHANGE_APPEARING
    表示 View出现时 整个容器所有View需要的动画效果
  • LayoutTransition.CHANGE_DISAPPEARING
    表示 View消失时 整个容器所有View消失的动画效果
  • LayoutTransition.CHANGE
    表示 除了以上这些情况,整个容器View发生改变时所需要的动画效果

ok,基础就说这个多,下面来看一下具体使用:
item动画效果的设置:

1
2
3
ObjectAnimator animator1 = ObjectAnimator.ofFloat(null, "alpha", 0F, 1F).
setDuration(mTransitioner.getDuration(LayoutTransition.APPEARING));
mTransitioner.setAnimator(LayoutTransition.APPEARING, animator1);

容器动画效果的设置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//pvhLeft, pvhTop, pvhRight, pvhBottom 必须添加,否则不显示动画效果
PropertyValuesHolder pvhLeft =
PropertyValuesHolder.ofInt("left", 0, 1);
PropertyValuesHolder pvhTop =
PropertyValuesHolder.ofInt("top", 0, 1);
PropertyValuesHolder pvhRight =
PropertyValuesHolder.ofInt("right", 0, 1);
PropertyValuesHolder pvhBottom =
PropertyValuesHolder.ofInt("bottom", 0, 1);
PropertyValuesHolder animator = PropertyValuesHolder.ofFloat("scaleX", 1F, 2F, 1F);
final ObjectAnimator changeIn = ObjectAnimator.ofPropertyValuesHolder(
this, pvhLeft, pvhTop, pvhRight, pvhBottom, animator).
setDuration(mTransitioner.getDuration(LayoutTransition.CHANGE_APPEARING));
changeIn.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
View view = (View) ((ObjectAnimator) animation).getTarget();
view.setScaleX(1.0f);
}
});
mTransitioner.setAnimator(LayoutTransition.CHANGE_APPEARING, changeIn);

关于容器动画的设置,pvhLeft, pvhTop, pvhRight, pvhBottom 必须添加,不然添加不了动画效果,至于具体原因,大家可以自行学习,实际上我也不知道 >_<。
最后给留一个demo ,LayoutTransition动画的实现:
LayoutTransition动画的具体实现效果

总结

写到这里,属性动画的基本使用就差不多了,我在这里并没有涉及属性动画通过 xml定义的方式,总感觉属性动画通过代码实现会更简单清晰一些,如果大家有兴趣可以去学习一下。

动画其他:View动画和帧动画