Android逐帧动画和补间动画

本篇博客来看一下Android中的逐帧动画和补间动画。


一、逐帧动画
逐帧动画也叫Drawable Animation。
在Android中实现逐帧动画,就是由设计师给出一系列状态不断变化的图片,
开发者可以指定动画中每一帧对应的图片和持续的时间,然后在合适的时候播发动画。

最常用定义逐帧动画的方式是:
在res/drawable目录下,放置动画对应的图片,并定义animation.xml文件。
animation.xml的定义类似于:

<?xml version="1.0" encoding="utf-8" ?>
<!--oneshot表示是否重复播放动画, true表示只播放一次, false表示重复播放 -->
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">

    <!--drawable指定图片, duration表示图像的持续时间-->
    <item
        android:drawable= "@drawable/p_1"
        android:duration="1200"/>

    <item
        android:drawable= "@drawable/p_2"
        android:duration="1200"/>

    <item
        android:drawable= "@drawable/p_3"
        android:duration="1200"/>
</animation-list>

负责播放动画的View,需要在xml中配置src属性为animation.xml,例如:

<ImageView
    android:id="@+id/test_view"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@drawable/animation" />

然后就可以在代码中,启动动画了:

ImageView imageView = findViewById(R.id.test_view);

//获取AnimationDrawable
AnimationDrawable drawable = (AnimationDrawable)imageView.getDrawable();

//调用start接口开始播放
//AnimationDrawable还有其它接口,例如停止、增加帧等
drawable.start();

如果不定义animation.xml文件,也可以仅通过代码实现逐帧动画:

        //在Activity中直接getDrawable,需要API >= 21
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            AnimationDrawable animationDrawable = new AnimationDrawable();

            int[] ids = new int[] {R.drawable.p_1, R.drawable.p_2, R.drawable.p_3};

            for (int id : ids) {
                Drawable tmp = getDrawable(id);
                if (tmp != null) {
                    //利用addFrame接口, 增加帧
                    animationDrawable.addFrame(tmp, 1200);
                }
            }

            animationDrawable.setOneShot(true);

            ImageView testView = findViewById(R.id.another_test_view);
            //设置animationDrawable
            testView.setBackground(animationDrawable);

            animationDrawable.start();
        }
    }

二、补间动画
补间动画是指开发者无需定义动画过程中的每一帧,
只需要定义动画的开发和结束这两个关键帧,并指定动画变化的时间和方式等,
然后交由Android系统进行计算,通过在两个关键帧之间插入渐变值来实现平滑过渡,
以实现对View的内容进行一系列的图形变换的动画效果。

补间动画主要包括四种基本效果:
透明度变化Alpha、大小变化Scale、位移变化Translate及旋转变化Rotate,
这四种效果可以动态组合,从而实现复杂灵活的动画。

Android中使用Animation来抽象动画类,对应上述四种补间动画的实现分别为:
AlphaAnimation:
改变透明度的动画,创建动画时需要指定动画开始和结束的透明度,
以及动画持续的时间,透明度取值范围是0到1。

ScaleAnimation:
缩放大小的动画,创建动画时需要指定动画开始和结束时在X轴和Y轴的缩放比,以及动画的持续时间;
同时由于缩放时以不同的点作为中心会产生不同的效果,因此也需要通过pivotX和pivotY指定缩放中心的坐标。

TranslateAnimation:
改变位置的动画,创建动画时需要指定动画开始和结束时的X、Y坐标,以及动画的持续时间。

RotateAnimation:
旋转动画,创建动画时需要指定动画开始和结束时的旋转角度,以及动画的持续时间;
同时由于旋转时以不同的点作为中心会产生不同的效果,因此也需要通过pivotX和pivotY指定旋转中心的坐标。

2.1、插值器Interpolator
在分析进一步补间动画前,我们有必要先了解一下插值器Interpolator。

Android系统会在补间动画的开始和结束帧之间插入渐变值,它的依据便是Interpolator。
Interpolator会根据类型,选择不同的算法,计算出在补间动画期间所需要动态插入帧的密度和位置。

简单来讲,Interpolator负责控制动画的变化速度,
使得动画效果能够以匀速、加速、减速、抛物线等多种速度进行变化。

Android中的Interpolator类是一个空接口,继承TimeInterpolator:

/**
 * 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);
}

通过注释容易了解到,TimeInterpolator时间插值器允许动画进行非线性变换,如加速、减速变化等。
TimeInterpolator只有一个函数getInterpolation,
其输入参数表示当前帧在整个动画过程中对应的比例,取值范围在0~1之间;
输出表示输入值对应的映射值。

Android已经定义了一些Interpolator的实现类,
例如:AccelerateInterpolator、AccelerateDecelerateInterpolator等。

我们以AccelerateInterpolator的getInterpolation函数为例,看看具体的实现:

    //AccelerateInterpolator的映射值为输入的平方或指数结果
    //那么显然输入的值越大,输出就越大
    //且随着输入的增大,输出增大速率越来越大
    //这就是会导致一种加速的效果
    public float getInterpolation(float input) {
        if (mFactor == 1.0f) {
            return input * input;
        } else {
            return (float)Math.pow(input, mDoubleFactor);
        }
    }

2.2、实现示例
了解完Interpolator后,我们来看看各种补间动画如何实现。

2.2.1、AlphaAnimation的实现
用XML定义AlphaAnimation时,需要在res/anim目录下建立对应的xml文件,其内容类似于:

<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1200"
    android:fillAfter="true"     //动画结束之后是否保持动画的最终状态;    true, 表示保持动画的最终状态 
    android:fillBefore="false"   //动画结束之后是否恢复到动画开始前的状态; true, 表示恢复到动画开始前的状态 
    android:fromAlpha="0.0"      //初始透明度
    android:interpolator="@android:anim/accelerate_interpolator"  //指定插值器
    android:toAlpha="1.0"        //最终的透明度
    android:repeatCount="-1"     //指定动画重复播放的次数; 如果需要无限循环播放, 填写一个小于0的数
    android:repeatMode="restart" //动画重复的Mode, 有reverse和restart两种
    android:startOffset="200"/>  //动画播放延迟时长

在代码中使用XML定义动画的方式类似于:

AlphaAnimation alphaAnimation = (AlphaAnimation) AnimationUtils.loadAnimation(
        this, R.anim.alpha_animation);
ImageView imageView = findViewById(R.id.test_view);
imageView.startAnimation(alphaAnimation);

仅用Java来实现AlphaAnimation时,对应代码类似于:

//直接创建AlphaAnimation对象
//xml中定义的属性都有对应的接口可以调用,此处仅举例一下
AlphaAnimation alphaAnimation = new AlphaAnimation(0.0f, 1.0f);
alphaAnimation.setDuration(1000);
alphaAnimation.setInterpolator(new AccelerateInterpolator(2));
alphaAnimation.setRepeatCount(2);

ImageView imageView = findViewById(R.id.test_view);
imageView.startAnimation(alphaAnimation);

2.2.2、ScaleAnimation的实现
用XML定义ScaleAnimation时,需要在res/anim目录下建立对应的xml文件,其内容类似于:

<?xml version="1.0" encoding="utf-8"?>

<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:fromXScale="0.2"
    android:fromYScale="0.2"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toXScale="1.5"
    android:toYScale="1.5"
    android:repeatCount="-1"/>

在代码中使用XML定义动画的方式类似于:

ImageView imageView = findViewById(R.id.test_view);
ScaleAnimation scaleAnimation = (ScaleAnimation)
        AnimationUtils.loadAnimation(this, R.anim.scale_animation);
imageView.startAnimation(scaleAnimation);

仅用Java来实现ScaleAnimation时,对应代码类似于:

ImageView imageView = findViewById(R.id.test_view);
ScaleAnimation scaleAnimation = new ScaleAnimation(1.0f, 4.0f, 1.0f, 4.0f);
scaleAnimation.setDuration(2000);
imageView.startAnimation(scaleAnimation);

ScaleAnimation构造函数的参数较多,具体含义可以参考对应的doc文档。

2.2.3、TranslateAnimation的实现
用XML定义ScaleAnimation时,需要在res/anim目录下建立对应的xml文件,其内容类似于:

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="2000"
    android:fromXDelta="0"
    android:fromYDelta="0"
    android:toXDelta="0"
    android:toYDelta="100" />

在代码中使用XML定义动画的方式类似于:

ImageView imageView = findViewById(R.id.test_view);
TranslateAnimation animation = (TranslateAnimation)
        AnimationUtils.loadAnimation(this, R.anim.translate_animation);
imageView.startAnimation(animation);

仅用Java来实现TranslateAnimation时,对应代码类似于:

ImageView imageView = findViewById(R.id.test_view);
TranslateAnimation animation = new TranslateAnimation(0f, 100f, 0f, 100f);
animation.setDuration(3000);
animation.setFillAfter(true);
imageView.startAnimation(animation);

TranslateAnimation构造函数的参数较多,具体含义可以参考对应的doc文档。

2.2.4、RotateAnimation的实现
用XML定义ScaleAnimation时,需要在res/anim目录下建立对应的xml文件,其内容类似于:

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:fromDegrees="0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:repeatCount="-1"
    android:toDegrees="360"/>

在代码中使用XML定义动画的方式类似于:

ImageView imageView = findViewById(R.id.test_view);
RotateAnimation animation = (RotateAnimation) AnimationUtils
        .loadAnimation(this, R.anim.rotate_animation);
imageView.startAnimation(animation);

仅用Java来实现TranslateAnimation时,对应代码类似于:

ImageView imageView = findViewById(R.id.test_view);
RotateAnimation animation = new RotateAnimation(0, -720, 0.5f, 0.5f);
animation.setDuration(1000);
imageView.startAnimation(animation);

2.3、自定义补间动画
在实际的项目中,上述默认的基本动画可能无法满足需求,
此时就可能需要自定义补间动画了。
具体的做法就是集成Animation类,然后重写其中的applyTransformation方法:

protected void applyTransformation(float interpolatedTime, Transformation t) {
}

其中:
interpolatedTime代表了动画的时间进行比。
不管动画实际的持续时间如何,当动画播放时,该参数总是自动从0变化到1。
Transformation代表了补间动画在不同时刻对图形或组件的变形程度,
该对象里封装了一个Matrix对象。
Transformation实际上就是通过对Matrix对象进行旋转、位置、倾斜等,
实现对图片或者视图进行相应的变换。

从applyTransformation的参数可以看出,实现自定义补间动画的重点就在于:
重写applyTransformation方法时,根据interpolatedTime,动态的计算动画对图形的变形程度。

我们来看一个具体的例子:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ImageView imageView = findViewById(R.id.test_view);
        DemoAnimation animation = new DemoAnimation(150, 150, 5000);
        imageView.startAnimation(animation);
    }

    //自定义的补间动画
    private class DemoAnimation extends Animation {
        private float mCenterX;
        private float mCenterY;
        private int mDuration;

        //这里使用的是:android.graphics.Camera类
        //用于空间变换的工具
        private Camera mCamera = new Camera();

        public DemoAnimation(float x, float y, int duration) {
            mCenterX = x;
            mCenterY = y;
            mDuration = duration;
        }

        @Override
        public void initialize(int width, int height, int parentWidth, int parentHeight) {
            super.initialize(width, height, parentWidth, parentHeight);

            setDuration(mDuration);
            setFillAfter(true);
            setInterpolator(new LinearInterpolator());
        }

        @Override
        protected void applyTransformation(float interpolatedTime, Transformation t) {
            super.applyTransformation(interpolatedTime, t);
            mCamera.save();

            //根据interpolatedTime来控制x、y和z轴上的偏移
            mCamera.translate(100.0f - 100.0f * interpolatedTime,
                    150.0f * interpolatedTime - 150, 80.0f - 80.0f * interpolatedTime);
            //设置x和y轴的旋转角度
            mCamera.rotateX(360 * interpolatedTime);
            mCamera.rotateY(360 * interpolatedTime);

            //获取Transformation中的matrix
            Matrix matrix = t.getMatrix();

            //将camera计算出的数据拷贝到matrix中,
            //即应用到Transformation中
            mCamera.getMatrix(matrix);

            //在旋转前、后移动图片
            //实际上就是更改图片的旋转中心
            matrix.preTranslate(-mCenterX, -mCenterY);
            matrix.postTranslate(mCenterX, mCenterY);
            mCamera.restore();
        }
    }
}

总结
至此,我们已经基本了解了逐帧动画和补间动画的使用方式。
其中,逐帧动画的使用最为简单;
补间动画既可以利用XML定义,也可以仅用Java代码实现。
对于基本的补间动画,主要是要选择合适的插值器以及配置参数;
对于自定义的补间动画,则可以利用Camera类来简化Matrix类的计算。

猜你喜欢

转载自blog.csdn.net/gaugamela/article/details/78983792