欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android逐帧动画和补间动画

程序员文章站 2022-03-25 14:55:37
...

本篇博客来看一下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类的计算。

相关标签: android 动画