Android动画详解

概述

动画主要分为三种:View动画、帧动画、属性动画,分别介绍:

View动画

View动画的作用对象是View,主要包含平移动画缩放动画旋转动画透明度动画。分别对应TranslateAnimationScaleAnimationRotateAnimationAlphaAnimation。可以通过XML来实现也可以通过代码,需要注意一点的是View动画最大的缺点是不能交互,View自身的属性并不会发生变化。例如对一个放大的View进行点击,点击区域其实还是原本View的点击区域。

Animation自身的属性

上面提到的属性都是Animation子类特有的属性,Animation自身带有:

  • duration 设置动画持续时间,单位为毫秒
  • fillAfter 如果为true则在动画结束后保持动画结束时的状态
  • fillBefore 如果设置为true则在动画结束后还原到动画前的状态
  • fillEnabled 和fillBefore效果相同
  • repeatCount 动画重复次数
  • repeatMode 重复类型,包括reverse和restart两个值,分别是倒序播放和重新播放,必须与repeatCount一起才会有效果,因为这里的意义是回放时的动作
  • interpolator 设置插值器,该属性影响动画的速度,可以不指定,默认为加速减速差值器(AccelerateDecelerateInterpolator)
TranslateAnimation

在Xml中定义,fromXDelta、toYDelta等都是相对当前View的位置。

1
2
3
4
5
6
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="500"
android:toYDelta="500"
/>

1
2
3
4
Animation translateAnimation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.translate);
// TranslateAnimation translateAnimation = new TranslateAnimation(0,500,0,500);
translateAnimation.setDuration(5000);
view.startAnimation(translateAnimation);
ScaleAnimation

主要有6个属性如下:分别是:起始X轴上的相对自身比例的缩放,比如1是不变2是放大一倍;结束的X方向上相对自身的缩放比例;之后Y轴的类似。pivotX是缩放轴坐标的X坐标,可以是数值、百分数、百分数p 三种样式,比如 50、50%、50%p,当为数值时,表示在当前View的左上角,即原点处加上50px,做为起始缩放点;如果是50%,表示在当前控件的左上角加上自己宽度的50%做为起始点;如果是50%p,那么就是表示在当前的左上角加上父控件宽度的50%做为起始点x轴坐标;pivotY类似

1
2
3
4
5
6
7
8
9
<scale android:fromXScale="0"
android:toXScale="1.4"
android:fromYScale="0"
android:toYScale="1.4"
android:pivotX="0"
android:pivotY="0"
android:repeatCount="2"
android:duration="5000"
xmlns:android="http://schemas.android.com/apk/res/android" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
Animation scaleAnimation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.scale);
Animation scaleAnimation2 = new ScaleAnimation(0f,1.4f,0f,1.4f,0f,0f);
view.startAnimation(scaleAnimation);
```
##### RotateAnimation
主要有四个属性,如下,分别是:起始的角度,结束的角度,和旋转中心点的坐标,也是对于View的相对坐标
``` xml
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:toDegrees="360"
android:pivotX="0"
android:pivotY="0"
/>
1
2
3
4
Animation rotateAnimation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.rotate);
// Animation rotateAnimation = new RotateAnimation(0,360,100,100);
rotateAnimation.setDuration(5000);
view.startAnimation(rotateAnimation);
AlphaAnimation

透明动画相对简单一点,主要有fromAlpha和toAlpha,分别为是起始透明度和结束透明度,值是从0.0到1.0,表示全透明到不透明。

1
2
3
4
5
6
7
8
9
10
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
android:fromAlpha="0"
android:toAlpha="1"
android:duration="5000"
/>
Animation alphaAnimation = new AlphaAnimation(0f,1f);
alphaAnimation.setDuration(5000);
Animation alphaAnimation2 = AnimationUtils.loadAnimation(MainActivity.this, R.anim.alpha);
view.startAnimation(alphaAnimation);

set 动画合集

set中包含shareInterpolator,表示set中国的动画是否共享一个差值器。同时它还有继承于Animation的属性,如果在set标签下设置了某个属性则对标签内的所有Animation起作用。

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
27
28
29
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="5000"
android:fillAfter="true">
<alpha
android:fromAlpha="0"
android:toAlpha="1" />
<rotate
android:fromDegrees="0"
android:pivotX="0"
android:pivotY="0"
android:toDegrees="360" />
<scale
android:fromXScale="0"
android:fromYScale="0"
android:pivotX="0"
android:pivotY="0"
android:toXScale="1.4"
android:toYScale="1.4" />
<translate
android:fromXDelta="0"
android:fromYDelta="0"
android:toXDelta="500"
android:toYDelta="500" />
</set>
```
``` java
Animation scaleAnimation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.set);
view.startAnimation(scaleAnimation);

帧动画

帧动画就是按照顺序播放一些列的图片,实际上是AnimationDrawable。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
android:oneshot="false">
<item android:drawable="@drawable/takeout_img_loading_pic1" android:duration="84" />
<item android:drawable="@drawable/takeout_img_loading_pic2" android:duration="167" />
<item android:drawable="@drawable/takeout_img_loading_pic3" android:duration="125" />
<item android:drawable="@drawable/takeout_img_loading_pic4" android:duration="42" />
</animation-list>
``` xml
<View
android:id="@+id/image_view"
android:layout_width="50dp"
android:background="@drawable/frame"
android:layout_height="50dp" />

可以将其设置为一个View的background,然后((AnimationDrawable)findViewById(R.id.image_view).getBackground()).start();

View动画的其他场景

View动画除了可以对单个View设置动画效果外也可以设置ViewGoup中子元素的加入效果,也可以设置Activity和Fragment的进入和退出效果

LayoutAnimation

LayoutAnimation的属性包括delay:设置动画开始的延迟,比如动画的duration为50ms,delay0.5意味着每个元素都要延迟25ms后进入,第一个元素25ms后进入,第二个元素50ms,以此类推。AnimationOrder是动画的顺序,包括normal顺序进入,random随机,reverse反序.Animation是指定的动画。

需要注意的是LayoutAnimation只在第一次创建的时候才有效,之后向adapter里添加时时无效的。

1
2
3
4
5
6
7
8
9
10
11
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
android:animation="@anim/set"
android:animationOrder="normal"
android:delay="0.5">
</layoutAnimation>
<ListView
android:id="@+id/listview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layoutAnimation="@anim/layout"></ListView>

LayoutAnimation的java代码实现需要借助LayoutAnimationController

1
2
3
4
5
Animation animation = AnimationUtils.loadAnimation(MainActivity.this, R.anim.set);
LayoutAnimationController controller = new LayoutAnimationController(animation);
controller.setDelay(0.5f);
controller.setOrder(LayoutAnimationController.ORDER_NORMAL);
listView.setLayoutAnimation(controller);

同样的GridView也有系统提供的Animation,具体实现为GridLayoutAnimation,不详细介绍。

Activity的Fragemnt的过渡动画

可以通过overridePendingTransition(int enterAnim, int exitAnim)()来为Activity添加进入和退出的动画,第一个参数是第二个Activity的进入动画,第二个参数是第一个Activity的退出动画,需要注意的是:overridePendingTransition(int enterAnim, int exitAnim)()这个方法必须在startActivity之后调用或者在finish()之后调用

1
2
3
4
5
6
Intent intent = new Intent(MainActivity.this, Main2Activity.class);
startActivity(intent);
overridePendingTransition(R.anim.activity_in,R.anim.activty_out);
finish();
overridePendingTransition(android.R.anim.fade_in,android.R.anim.fade_out);

Interpolator 插值器

插值器(Interpolator)是Animation的一个属性,用来设置动画变化过程的,Android自身提供了几种插值器:

  • AccelerateDecelerateInterpolator 在动画开始与介绍的地方速率改变比较慢,在中间的时候加速
  • AccelerateInterpolator 在动画开始的地方速率改变比较慢,然后开始加速
  • AnticipateInterpolator 开始的时候向后然后向前甩
  • AnticipateOvershootInterpolator 开始的时候向后然后向前甩一定值后返回最后的值
  • BounceInterpolator 动画结束的时候弹起
  • CycleInterpolator 动画循环播放特定的次数,速率改变沿着正弦曲线
  • DecelerateInterpolator 在动画开始的地方快然后慢
  • LinearInterpolator 以常量速率改变
  • OvershootInterpolator 向前甩一定值后再回到原来位置

在XML中如下设置:

1
2
3
4
5
6
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:toDegrees="360"
android:interpolator="@android:anim/accelerate_decelerate_interpolator"
android:pivotX="0"
android:pivotY="0"
android:fromDegrees="0" />

Property Animator 属性动画

属性动画是api11之后引入的,可以实现View动画不实现的功能,比如实现颜色的变化。View动画的作用对象是View,而属性动画可以针对的是对象的属性,属性动画可以通过改变对象的属性来实现动画效果,而View动画是通过其parent view实现的,在View被draw通过改变它的绘制参数,这样虽然View的大小或者旋转改变了,但是View的实际属性并没有变。

属性动画包括:ValueAnimator和ObjectAnimator,其中ObjectAnimator是ValueAnimator的子类。

ValueAnimator

ValueAnimator主要是对值进行变化的,不依赖于任何控件,通过对值进行变化然后添加相应的listener。
主要包含getAnimatedValue()setRepeatCount()setRepeatMode()cancle()
其中setRepeatCount和setRepeatMode和View动画一样,只有在设置了repeatCount之后repeatMode才生效。cancle()则可以中断动画。AnimatorUpdateListener只在ValueAnimator类中有。

1
2
3
4
5
6
7
8
9
10
11
animator = ValueAnimator.ofInt(0, 500);
animator.setDuration(3000);
animator.setRepeatMode(ValueAnimator.REVERSE);
animator.setRepeatCount(1);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int)animation.getAnimatedValue();
view.layout(curValue, curValue, curValue + view.getWidth(), curValue + view.getHeight());
}
});

同时Animator还可以设置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
27
28
public static interface AnimatorListener {
/**
* <p>Notifies the start of the animation.</p>
*
* @param animation The started animation.
*/
void onAnimationStart(Animator animation);
/**
* <p>Notifies the end of the animation. This callback is not invoked
* for animations with repeat count set to INFINITE.</p>
*
* @param animation The animation which reached its end.
*/
void onAnimationEnd(Animator animation);
/**
* <p>Notifies the cancellation of the animation. This callback is not invoked
* for animations with repeat count set to INFINITE.</p>
*
* @param animation The animation which was canceled.
*/
void onAnimationCancel(Animator animation);
/**
* <p>Notifies the repetition of the animation.</p>
*
* @param animation The animation which was repeated.
*/
void onAnimationRepeat(Animator animation);
}

同时对AnimatorUpdateListener提供了animator.removeAllUpdateListeners();animator.removeUpdateListener(AnimatorUpdateListener listener)对AnimatorListener也提供了相应的方法RemoveAllListener()和RemoveListener(AnimatorListener listener),当调用RemoveAllListener时也会对PauseListener进行remove操作。

Animator的其他方法:pause()、resume()和isPause()、isRunning()、setStartDelay()、clone()

自定义插值器(Interpolator)

在Aniamator中也存在插值器,同样是用于设置动画效果的,例如设置运动速率,设置轨迹等。在前面介绍了几种系统内置的插值器,下面自定义自己的插值器:

看下系统的插值器LinearInterpolator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class LinearInterpolator implements Interpolator {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
}
public interface Interpolator extends TimeInterpolator {
}

它实现了Interpolator接口,而Interpolator没添加其他接口,只是继承了TimeInterpolator,看下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* A time interpolator defines the rate of change of an animation. This allows animations
* to have non-linear motion, such as acceleration and deceleration.
*/
public interface TimeInterpolator {
/**
* Maps a value representing the elapsed fraction of an animation to a value that represents
* the interpolated fraction. This interpolated value is then multiplied by the change in
* value of an animation to derive the animated value at the current elapsed animation time.
*
* @param input A value between 0 and 1.0 indicating our current point
* in the animation where 0 represents the start and 1.0 represents
* the end
* @return The interpolation value. This value can be more than 1.0 for
* interpolators which overshoot their targets, or less than 0 for
* interpolators that undershoot their targets.
*/
float getInterpolation(float input);
}

里面存在一个getInterpolation(float input),这个方法的参数是动画的进度,从0到1,是匀速的不受其他条件变化,只随时间进行进度的变化,输出则是动画的进度值。
例如下面的:

1
2
3
4
5
6
7
8
9
10
ValueAnimator anim = ValueAnimator.ofInt(100, 400);
anim.setDuration(1000);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float currentValue = (float) animation.getAnimatedValue();
Log.d("TAG", "cuurent value is " + currentValue);
}
});
anim.start();

getAnimatedValue的值就是 100 + (400 - 100)进度值,进度值 = 动画已持续时间 / 动画总时间自定义的数值。

1
2
3
4
5
6
7
public class CustomInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
double x = input * Math.PI / 2;
return (float) Math.sin(x);
}
}

上面实现了在动画时间内完成一个0-90度正弦效果的运动速率。

Evaluator


我们可以通过进度值来改变动画效果,也可以通过设置Evaluator,在ofInt()和OfFloat()时,系统会返回默认的IntEvaluator和FloatEvaluator

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

可以看到这个就是上面提到的公式,最终我们getAnimatedValue得到的值与Evaluator和Interpolator有关

自定义Evaluator

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

上面的操作就在结果上多了200。我们可以调用Animator.setEvaluator()来设置

ArgbEvaluator

通过ArgbEvaluator可以设置颜色的渐变效果

1
2
3
4
5
6
7
8
9
10
11
ValueAnimator animator = ValueAnimator.ofInt(0xffffff00,0xff0000ff);
animator.setEvaluator(new ArgbEvaluator());
animator.setDuration(3000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int)animation.getAnimatedValue();
view.setBackgroundColor(curValue);
}
});
animator.start();

ArgbEvaluator的源码的原理和上面的原理相似,也是在startValue上加上当前进度 * (endValue - startValue)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class ArgbEvaluator implements TypeEvaluator {
public Object evaluate(float fraction, Object startValue, Object endValue) {
int startInt = (Integer) startValue;
int startA = (startInt >> 24);
int startR = (startInt >> 16) & 0xff;
int startG = (startInt >> 8) & 0xff;
int startB = startInt & 0xff;
int endInt = (Integer) endValue;
int endA = (endInt >> 24);
int endR = (endInt >> 16) & 0xff;
int endG = (endInt >> 8) & 0xff;
int endB = endInt & 0xff;
return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
(int)((startR + (int)(fraction * (endR - startR))) << 16) |
(int)((startG + (int)(fraction * (endG - startG))) << 8) |
(int)((startB + (int)(fraction * (endB - startB))));
}
}

ObjectAnimator

我们可以看到ValueAnimator是通过改变数值,然后通过添加Listener,然后在回调中设置View的属性变化。为了简化整个流程,google推出了ObjectAnimator,其实ValueAnimator的子类,所以Valuemator有的方法,它都有。但它主要是通过反射获取到相应的属性的set方法,然后设置值。因此它的操作更加简单,如果参数或者属性错误则不会产生动画。

1
2
3
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "rotation", 0, 180 ,0);
animator.setDuration(2000);
animator.start();

View方法主要有以下几类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//1、透明度:alpha
public void setAlpha(float alpha)
//2、旋转度数:rotation、rotationX、rotationY
public void setRotation(float rotation)
public void setRotationX(float rotationX)
public void setRotationY(float rotationY)
//3、平移:translationX、translationY
public void setTranslationX(float translationX)
public void setTranslationY(float translationY)
//缩放:scaleX、scaleY
public void setScaleX(float scaleX)
public void setScaleY(float scaleY)

对比

因为原理是通过将设置的属性的第一个字母大写,然后添加set,找到该方法,然后设置值来产生动画。所以可以自定义一个属性来根据这个属性生成动画。

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
27
28
29
30
31
32
33
34
35
36
public class CircleView extends View {
public CircleView(Context context) {
super(context);
}
public CircleView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
int r = 100;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(300, 300, r, paint);
}
public void setRadius(int radius) {
r = radius;
invalidate();
}
}
ObjectAnimator animator = ObjectAnimator.ofInt(view2, "radius", 0, 200 ,100);
animator.setDuration(2000);
animator.start();

效果如下