Android各类动画机制详解

1,697 阅读6分钟

1.逐帧动画

Frame Animation,也叫Drawable Animation。顾名思义,就是一帧一帧的播放图片,利用视觉暂留效应,设定好每一帧的图片(drawable)和显示的时间(duration),就能显示出宫崎骏动画片的播放效果。

逐帧动画在代码中有两种设置方式:

1.xml方式:

<animation-list xmlns:android="http://schemas.android.com/apk/res/android" 
    android:oneshot="false">
    <item
        android:drawable="@drawable/comp1_00000"
        android:duration="25"></item>
    <item
        android:drawable="@drawable/comp1_00001"
        android:duration="25"></item>
    <item
        android:drawable="@drawable/comp1_00002"
        android:duration="25"></item>
    <item
        android:drawable="@drawable/comp1_00003"
        android:duration="25"></item>
    <item
        android:drawable="@drawable/comp1_00004"
        android:duration="25"></item>
</animation-list>

android:oneshot="true",代表动画只播放一次,为false代表动画循环播放,oneshot是电影中一次镜头的意思。

2.代码方式:

        AnimationDrawable animationDrawable = new AnimationDrawable();
        for (int i=0; i< 5;i++) {
            int id = getResources().getIdentifier("comp1_0000" + i , "drawable", getPackageName());
            Drawable drawable = ResourcesCompat.getDrawable(this.getResources(), id, null);
            animationDrawable.addFrame(drawable, 50);
        }
            animationDrawable.setOneShot(true);
            imageView.setBackground(animationDrawable);
        

注意,上面两种方式都只是定义了动画,播放动画或停止动画的代码如下:

AnimationDrawable drawable = (AnimationDrawable) imageView.getBackground();
//获取xml文件的drawable方式
//AnimationDrawable drawable =
//(AnimationDrawable) ResourcesCompat.getDrawable(this.getResources()
//                               , R.drawable.frame_anim, null);
drawable.start();
drawable.stop();

image.png

2.补间动画

定义一个动画的开头和结尾的关键帧,也就是动画从(from)某处到(to)某处的两个关键帧,然后让Android自己计算这个过程的每一帧。所以我们还要指定这个过程持续多久(duration),持续过程的速度如何变化(Interpolator)。

我们知道,计算机图形的变换本质是矩阵运算。透明度变换(AlphaAnimation)是改变像素颜色ARGB中的A,缩放变换(ScaleAnimation)是改变图形坐标位置。透明度在Android中是用两位十六进制数表示的标量,但是位置是有方向和大小的矢量,所以缩放变换还要指定轴点的位置(pivot),如果不指定轴点的位置,Android就会默认以该View的中心点(用宽高计算)作为轴点。

平移变换(TranslateAnimation)只需要指定开始(fromXValue/fromXDelta,fromYValue/fromYDelta)和结束位置(toXValue/toXDelta,toYValue/toYDelta)。旋转变换(RotateAnimation)需要指定旋转开始的角度(fromDegrees),和旋转结束时的角度(toDegrees),以什么坐标位置为轴心旋转(pivotX,pivotY),默认会以坐标(0,0)也就是屏幕左上角为轴心。

使用平移变换(TranslateAnimation)或者旋转变换(RotateAnimation)时,还可以用比例形式设置平移或者旋转的位移/角度。可以根据自身View的Size或是parent View的Size,设置位移距离/角度为这个Size的多少倍。

        ImageView imageView = this.findViewById(R.id.imageView);
        //不设置XType和YType时默认使用绝对坐标
        //TranslateAnimation translateAnimation = new TranslateAnimation(0, 400 ,0 ,800);
        //这里设置X轴方向位移一个该View的Size,Y轴方向位移半个parent View的Size
        TranslateAnimation translateAnimation = new TranslateAnimation(
                Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 1
                , Animation.RELATIVE_TO_PARENT ,0 ,Animation.RELATIVE_TO_PARENT, 0.5f);
        //How long this animation should last
        translateAnimation.setDuration(2000);
        //true if the animation should apply its transformation after it ends
        translateAnimation.setFillAfter(true);
        imageView.setAnimation(translateAnimation);
        translateAnimation.start();

Interpolator

Interpolator 时间插值类,定义动画变换的速度。能够实现alpha/scale/translate/rotate动画的加速、减速和重复等。Interpolator类其实是一个空接口,继承自TimeInterpolator,TimeInterpolator时间插值器允许动画进行非线性运动变换,如加速和限速等,该接口中只有接口中有一个方法 float getInterpolation(float input)这个方法。传入的值是一个0.0~1.0的值,返回值可以小于0.0也可以大于1.0。

直接在上面的代码中加入:

     //直接使用SDK提供的加速插值器
     translateAnimation.setInterpolator(new AccelerateInterpolator(3f));

效果如下:

AccelerateInterpolator是SDK提供的插值器之一,开始慢后来快。SDK自带的还有:

  • DecelerateInterpolator 减速,开始时快然后减速
  • AccelerateDecelerateInterolator 先加速后减速,开始结束时慢,中间加速
  • AnticipateInterpolator 反向,先向相反方向改变一段再加速播放
  • AnticipateOvershootInterpolator 反向加超越,先向相反方向改变,再加速播放,会超出目的值然后缓慢移动至目的值
  • BounceInterpolator 跳跃,快到目的值时值会跳跃,如目的值100,后面的值可能依次为85,77,70,80,90,100
  • CycleIinterpolator 循环,动画循环一定次数,值的改变为一正弦函数:Math.sin(2* mCycles* Math.PI* input)
  • LinearInterpolator 线性,线性均匀改变
  • OvershootInterpolator 超越,最后超出目的值然后缓慢改变到目的值
  • PathInterpolator 可以定义路径坐标,然后可以按照路径坐标来跑动;注意其坐标并不是 XY,而是单方向,也就是我可以从0~1,然后弹回0.5 然后又弹到0.7 有到0.3,直到最后时间结束。 我们自定义一个Interpolator:
         * 自定义插值器,
         * @input 动画进行的时间阶段,0代表刚开始,1代表结束
         * @return 动画进行的空间阶段,低于0代表未达到目标处,高于1代表飞跃目标
         */
        Interpolator interpolator = new Interpolator() {
            @Override
            public float getInterpolation(float input) {
                return input * input + 0.2f;
            }
        };
        translateAnimation.setInterpolator(interpolator);

3.属性动画(Property Animation)

如何利用补间动画来将上面小球的颜色从红色变成绿色? 这时就要用到属性动画(Property Animation)了。

如果我们给上面的代码加一句

imageView.setOnClickListener(v -> Toast.makeText(this, "小球被点击了!", Toast.LENGTH_SHORT).show());

当小球动画结束后,再点击小球,会发现没有出现“小球被点击了!”的toast。但如果点击原来的位置,则会出现toast。

这是因为,补间动画只是改变了小球的显示位置,小球的实际还是处于原来的位置上,也就是小球的坐标这一属性没有改变。 属性动画可以对通过改变View对象或者其他影响到View对象显示效果的任何对象的属性值来产生动画,在一定时间间隔内,通过不断对值进行改变,并不断将该值赋给对象的属性,从而实现该对象在该属性上的动画效果。

Evaluator

**Evaluator**是用来控制属性动画如何计算属性值的,接口定义如下:
public interface TypeEvaluator<T> {
    public T evaluate(float fraction, T startValue, T endValue);
}

根据输入的初始值、结束值和一个进度比,计算出一个每一个进度对应的属性值。

//使用自定义的Evaluator
public static ValueAnimator ofArgb(int... values) {
    ValueAnimator anim = new ValueAnimator();
    anim.setIntValues(values);
    anim.setEvaluator(ArgbEvaluator.getInstance());
    return anim;
}
//自定义一个对颜色ARGB进行操作的Evaluator
public class ArgbEvaluator implements TypeEvaluator {
    private static final ArgbEvaluator sInstance = new ArgbEvaluator();
    public static ArgbEvaluator getInstance() {
        return sInstance;
    }
    // FloatEvaluator实现了TypeEvaluator接口
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        // 参数说明
        // fraction:表示动画完成度(根据它来计算当前动画的值)
        // startValue、endValue:动画的初始值和结束值
        int startInt = (Integer) startValue;
        int startA = (startInt >> 24) & 0xff;
        int startR = (startInt >> 16) & 0xff;
        int startG = (startInt >> 8) & 0xff;
        int startB = startInt & 0xff;
        int endInt = (Integer) endValue;
        int endA = (endInt >> 24) & 0xff;
        int endR = (endInt >> 16) & 0xff;
        int endG = (endInt >> 8) & 0xff;
        int endB = endInt & 0xff;
        // 初始值 过渡 到结束值 的算法是:
        // 1. 用结束值减去初始值,算出它们之间的差值
        // 2. 用上述差值乘以fraction系数
        // 3. 再加上初始值,就得到当前动画的值
        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))));
    }
}

AnimationSet

单一动画效果有限,更多的使用场景是同时使用多种动画效果,即把不同动画组合成一个集合(Set)。 使用方法————

xml文件中处理

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially" >
    <!--表示Set集合内的动画按顺序进行-->
    <!--ordering的属性值:sequentially & together-->
    <!--sequentially:表示set中的动画,按照先后顺序逐步进行(a 完成之后进行 b )-->
    <!--together:表示set中的动画,在同一时间同时进行,为默认值-->
    <set android:ordering="together" >
        <!--下面的动画同时进行-->
        <objectAnimator
            android:duration="2000"
            android:propertyName="translationX"
            android:valueFrom="0"
            android:valueTo="300"
            android:valueType="floatType" >
        </objectAnimator>
        <objectAnimator
            android:duration="3000"
            android:propertyName="rotation"
            android:valueFrom="0"
            android:valueTo="360"
            android:valueType="floatType" >
        </objectAnimator>
    </set>
    <set android:ordering="sequentially" >
        <!--下面的动画按序进行-->
        <objectAnimator
            android:duration="1500"
            android:propertyName="alpha"
            android:valueFrom="1"
            android:valueTo="0"
            android:valueType="floatType" >
        </objectAnimator>
        <objectAnimator
            android:duration="1500"
            android:propertyName="alpha"
            android:valueFrom="0"
            android:valueTo="1"
            android:valueType="floatType" >
        </objectAnimator>
    </set>
</set>
//关于代码中引用
AnimatorSet animator = (AnimatorSet) AnimatorInflater.loadAnimator(this, R.animator.set_animation);
// 创建组合动画对象  &  加载XML动画
animator.setTarget(mButton);
// 设置动画作用对象
animator.start();
// 启动动画

Java文件

// 步骤1:设置需要组合的动画效果
ObjectAnimator translation = ObjectAnimator.ofFloat(mButton, "translationX", curTranslationX, 300,curTranslationX); 
// 平移动画
ObjectAnimator rotate = ObjectAnimator.ofFloat(mButton, "rotation", 0f, 360f); 
// 旋转动画
ObjectAnimator alpha = ObjectAnimator.ofFloat(mButton, "alpha", 1f, 0f, 1f); 
// 透明度动画
// 步骤2:创建组合动画的对象
AnimatorSet animSet = new AnimatorSet(); 
// 步骤3:根据需求组合动画
animSet.play(translation).with(rotate).before(alpha); 
animSet.setDuration(5000); 
// 步骤4:启动动画
animSet.start();

ObjectAnimator

直接对对象的属性值进行改变操作,从而实现动画效果 继承自ValueAnimator类,即底层的动画实现机制是基于ValueAnimator类

使用方法————

Java代码

public static ObjectAnimator setObjectAnimator(View view , String type , int start , int end , long time){
    ObjectAnimator mAnimator = ObjectAnimator.ofFloat(view, type, start, end);
    // ofFloat()作用有两个
    // 1. 创建动画实例
    // 2. 参数设置:参数说明如下
    // Object object:需要操作的对象
    // String property:需要操作的对象的属性
    // float ....values:动画初始值 & 结束值(不固定长度)
    // 若是两个参数a,b,则动画效果则是从属性的a值到b值
    // 若是三个参数a,b,c,则则动画效果则是从属性的a值到b值再到c值
    // 以此类推
    // 至于如何从初始值 过渡到 结束值,同样是由估值器决定,此处ObjectAnimator.ofFloat()是有系统内置的浮点型估值器FloatEvaluator,同ValueAnimator讲解

    // 设置动画重复播放次数 = 重放次数+1
    // 动画播放次数 = infinite时,动画无限重复
    mAnimator.setRepeatCount(ValueAnimator.INFINITE);
    // 设置动画运行的时长
    mAnimator.setDuration(time);
    // 设置动画延迟播放时间
    mAnimator.setStartDelay(0);
    // 设置重复播放动画模式
    mAnimator.setRepeatMode(ValueAnimator.RESTART);
    // ValueAnimator.RESTART(默认):正序重放
    // ValueAnimator.REVERSE:倒序回放
    //设置差值器
    mAnimator.setInterpolator(new LinearInterpolator());
    return mAnimator;
}

xml方式(需要放在res的animator文件夹中)

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <ObjectAnimator
        android:valueFrom="1"
        android:valueTo="0"
        android:valueType="floatType"
        android:duration = "800"
        android:propertyName="alpha"/>
</set>
Animator mAnim = AnimatorInflater.loadAnimator(this, R.animator.animator_1_0);
mAnim.setTarget(fabHomeRandom);
mAnim.start();

每更新新的一帧回调AnimatorUpdateListener.onAnimationUpdate(ValueAnimator animation)方法

过渡动画(Transition Animation)

第一种方式:XML资源文件方式

<set xmlns:android="http://schemas.android.com/apk/res/android" >
    <translate
        android:duration="2000"
        android:fromXDelta="30"
        android:fromYDelta="30"
        android:toXDelta="-80"
        android:toYDelta="300" />
    <!--
    translate 位置转移动画效果
    整型值:
    fromXDelta 属性为动画起始时 X坐标上的位置
    toXDelta   属性为动画结束时 X坐标上的位置
    fromYDelta 属性为动画起始时 Y坐标上的位置
    toYDelta   属性为动画结束时 Y坐标上的位置
    注意:
     没有指定fromXType toXType fromYType toYType 时候,
     默认是以自己为相对参照物
    长整型值:
    duration  属性为动画持续时间
    说明:   时间以毫秒为单位
    -->
</set>

第二种方式:代码方式

rotate.setDuration(durationMillis);
rotate.setFillAfter(true);
ivImage.setAnimation(rotate);