Android动画总结

2018-05-04  本文已影响0人  留给时光吧

Android中的动画大致上分为视图动画(View Animation)和属性动画(Property Animation)两种,其中视图动画又分为补间动画(Tween Animation)和帧动画两种(Frame Animation)。其中每种动画有分很多情况,虽然用着都比较简单,但是经常容易忘,所以这里做一个总结。

1.帧动画

基本上属于最简单的一种动画,主要就是像幻灯片一样播放一组图片,形成动画效果。如下图一个进度条的动画就是许多张图片依次播放的效果:


帧动画有两种做法,一种利用xml文件,一种利用java代码。

1.1 xml文件写法

首先在res/drawable目录下创建一个animation-list类型的文件,虽然是动画文件(虽然是帧动画的这个文件再Android Studio中不能放在anim文件夹下)内容如下:

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@drawable/indeterminate01"
        android:duration="50" />
    <item
        android:drawable="@drawable/indeterminate02"
        android:duration="50" />
    <item
        android:drawable="@drawable/indeterminate03"
        android:duration="50" />
    <item
        android:drawable="@drawable/indeterminate04"
        android:duration="50" />
    <item
        android:drawable="@drawable/indeterminate05"
        android:duration="50" />
    <item
        android:drawable="@drawable/indeterminate06"
        android:duration="50" />
</animation-list>

内容很简单,就是按照顺序将一张一张图片列出来,duration属性表示这张图片的暂留时间,oneshot表示是否播放一次就结束,循环播放的话用false。

帧动画的载体是ImageView,当然也可以是别的控件,如TextView等可以设置图片显示的控件,因为我们前面写的那个xml文件是作为一个Drawable使用的,也就是为什么要放在drawable目录下的。不过为了最佳的显示效果还是用ImageView。如下,设置为ImageView的src

<ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/anim"
        android:src="@drawable/indeterminate"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

然后在代码中启动动画

ImageView iv = findViewById(R.id.anim);
AnimationDrawable drawable = (AnimationDrawable) iv.getDrawable();
drawable.start();

1.2 Java代码写法

使用xml文件的好处是将UI逻辑从代码中剥离,而且xml文件看起来条例比较清晰,当然如果我们需要动态控制的时候,还是需要用java代码:

ImageView iv = findViewById(R.id.anim);
AnimationDrawable drawable = new AnimationDrawable();
for (int i = 1; i < 7; i++)
        drawable.addFrame(getDrawable(getResources().getIdentifier("indeterminate0"+i,"drawable",getPackageName())),50);
drawable.setOneShot(false);
iv.setImageDrawable(drawable);
drawable.start();

addFrame:添加一帧图片及设置显示时间
setOneShot:设置是否仅播放一次
start() :开始播放
stop() :停止播放
isRunning(): 是否在播放

2.视图动画

一般而言视图动画分以下四种类型:平移,缩放,旋转,透明度变化。

分别对应Java中以下4个类:TranslateAnimation,ScaleAnimation,RotateAnimation,AlphaAnimation。

在xml文件中体现为下面4个标签:<translate/>,<scale/>,<rotate/>,<alpha/>

当然几种动画也可以组合起来使用,对于AnimationSet类和<set/>。

一般建议在xml文件定义动画,动画文件存放在res/anim文件夹下,各种类型动画举例及属性说明如下:

2.1 平移动画

<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="5000"
    android:fromXDelta="0"
    android:fromYDelta="0"
    android:toXDelta="500"
    android:toYDelta="0"
    />

android:fromXDelta / android:fromYDelta: 起始X,Y坐标
android:toXDelta / android:toYXDelta:结束X,Y坐标

2.2 缩放动画

<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXScale="0"
    android:fromYScale="0"
    android:toXScale="2"
    android:toYScale="2"
    android:pivotX="50%"
    android:pivotY="50%"
    android:duration="3000"
    />

android:fromXScale / android:fromYScale :起始XY轴缩放倍数
android:toXScale / android:toYScale:最终XY缩放倍数
android:pivotX / android:pivotY:缩放中心点位置,50%代表中间

2.3 旋转动画

<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="3000"
    android:fromDegrees="0"
    android:toDegrees="300"
    android:pivotX="50%"
    android:pivotY="50%"
    />

android:fromDegrees:旋转起始度数
android:toDegrees:旋转结束度数
android:pivotX / android:pivotY:旋转中心

2.4 透明度变化

<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="3000"
    android:fromAlpha="1"
    android:toAlpha="0"
    />

android:fromAlpha:起始透明度,1为不透明,0为全透明
android:toAlpha:动画结束时透明度

2.5组合动画

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="3000"
    android:shareInterpolator="true"
    >
    <rotate
        android:duration="3000"
        android:fromDegrees="0"
        android:toDegrees="359"
        android:pivotX="50%"
        android:pivotY="50%"
        />
    <scale
        android:fromXScale="0"
        android:fromYScale="0"
        android:toXScale="2"
        android:toYScale="2"
        android:pivotX="50%"
        android:pivotY="50%"
        />
</set>

android:shareInterpolator:表示组合动画中各个动画是否公用一个插值器

2.6一些共有属性

android:duration="3000" // 动画持续时间(ms)
android:startOffset ="1000" // 动画延迟开始时间(ms)
android:fillBefore = "true" // 动画播放完后,视图是否会停留在动画开始的状态,默认为true
android:fillAfter = "false" // 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
android:fillEnabled= "true"// 是否应用fillBefore值,对fillAfter值无影响,默认为true
android:repeatMode= "restart" // 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|
android:repeatCount = "0" // 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
android:interpolator = "anim/interpolator_resource" // 插值器

2.7 动画的应用

既然是view动画,就要实际应用到某个控件上,如下:

ImageView iv = findViewById(R.id.anim);
iv.startAnimation(AnimationUtils.loadAnimation(this,R.anim.set_anim));

2.8 Java代码形式

可以用xml书写的动画都可以用Java代码实现,便于动态修改,写起来也比较简单,只需实例化对应动画类型的对象,然后具体设置每种属性,简单例子如下:

ImageView iv = findViewById(R.id.anim);
ScaleAnimation scaleAnimation = new ScaleAnimation(0,0,2,2, Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
scaleAnimation.setDuration(3000);
RotateAnimation rotateAnimation = new RotateAnimation(0,300,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
rotateAnimation.setDuration(3000);
AnimationSet animationSet = new AnimationSet(true);
animationSet.addAnimation(scaleAnimation);
animationSet.addAnimation(rotateAnimation);
iv.startAnimation(animationSet);

2.9 动画监听

animationSet.setAnimationListener(new Animation.AnimationListener() {
            @Override
            public void onAnimationStart(Animation animation) {
                
            }

            @Override
            public void onAnimationEnd(Animation animation) {

            }

            @Override
            public void onAnimationRepeat(Animation animation) {

            }
        });

2.10 自定义动画

自定义动画仅需继承Animation类即可,然后重写applyTransformation和initialize两个方法。如下:

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

public void initialize(int width, int height, int parentWidth, int parentHeight) {
}

initialize主要用于一些数据的初始化,applyTransformation则是动画中对view进行操作的实际地方,interpolatedTime表示动画进行的时间,从0到1递增,当值为1时,表示动画结束,Transformation 可以看成view的实体进行操作,通过getMatrix()可以获得变换矩阵,进行操作。动画的实现就是不停调用applyTransformation。

一些复杂的自定义动画实现起来还是比较复杂的,我们可以看一下最简单的透明度动画的实现源码,关键代码如下:

    public AlphaAnimation(float fromAlpha, float toAlpha) {
        mFromAlpha = fromAlpha;
        mToAlpha = toAlpha;
    }
    
    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        final float alpha = mFromAlpha;
        t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
    }

可以看到还是很简单的,甚至没有重写initialize方法,仅在构造中保存了fromAlpha和toAlpha。然后在applyTransformation根据时间的变化设置相应的透明度。

3.属性动画

视图动画虽然简单易用,但是有很大的局限性,动画只能针对整个view,而且动画种类有限,可操作性较低,而且仅改变了视觉效果,别没有真正改变view的属性,所以如果需要更加复杂的动画效果,我们就需要使用属性动画。

属性动画主要涉及ObjectAnimator、ValueAnimator、AnimatorSet等几个类,我们依次来学习:

3.1 ValueAnimator

ObjectAnimator是继承于ValueAnimator,所以我们先学习ValueAnimator。

ValueAnimator实现动画的主要原理是不断变化数值,然后由我们赋给控件的各种属性从而实现动画效果,涉及的操作主要有ofInt,ofFloat,ofObject三个方法。

3.1.1 ValueAnimator ofInt (int... values)

使用这个方法主要是以整型进行数值变化,举例:

final ImageView iv = findViewById(R.id.anim);
ValueAnimator animator = ValueAnimator.ofInt(0,270).setDuration(3000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int currentValue = (Integer) animation.getAnimatedValue();
                iv.setRotation(currentValue);
            }
});
animator.start();

在例子中我们首先设置变化范围为0~270,动画时间为3000ms,然后添加一个监听,主要是为了在每次数值发生变化时进行相应的操作,这里我们对view进行旋转操作。

同样我们也可以在xml文件中定义动画:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:valueFrom="0"
    android:valueTo="270"
    android:duration="3000"
    android:valueType="intType"
    />

需要注意的时这里的xml文件要放在res/animator目录下,而且需要指定valueType,不指定的话默认为float。之后在java文件中进行应用:

final ImageView iv = findViewById(R.id.anim);
ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(this,R.animator.anim);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int currentValue = (Integer) animation.getAnimatedValue();
                iv.setRotation(currentValue);
            }
        });
animator.start();

效果和前面是一样的。

3.1.2 ValueAnimator ofFloat (float... values)

和ofInt基本一样,只不过过渡值变为float类型,这里不多叙述

3.1.3 ValueAnimator ofObject (TypeEvaluator evaluator, Object... values)

这个相当于是一个开放性的接口,前两个只能局限于固定的数据类型,这个方法我们可以指定任何类型的数据来实现各种动画。在这个方法中我们要指定一个自定义的TypeEvaluator和起始结束值,

TypeEvaluator主要是为了计算动画过程中值变化的详细过程,如已经实现好的FloatEvaluator如下:

public class FloatEvaluator implements TypeEvaluator<Number> {
   
    public Float evaluate(float fraction, Number startValue, Number endValue) {
        float startFloat = startValue.floatValue();
        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}

可见实现起来也是很简单的,根据起始和终止值,不断乘以系数fraction 最后计算出每次变化的值。

由此可见我们可以通过自定义所需TypeEvaluator,来实现各种对象的变化过程,最终来实现动画效果。

3.1.4 其余属性

当然和view动画类似,我们除了可以设置起始终止值和持续时间外,也可以设置如延迟,重复,插值器等属性,在XML文件中如下:

android:startOffset ="1000" // 动画延迟开始时间(ms)
android:fillBefore = "true"// 动画播放完后,视图是否会停留在动画开始的状态,默认为true
android:fillAfter = "false"// 动画播放完后,视图是否会停留在动画结束的状态,优先于fillBefore值,默认为false
android:fillEnabled= "true"// 是否应用fillBefore值,对fillAfter值无影响,默认为true
android:repeatMode="restart"// 选择重复播放动画模式,restart代表正序重放,reverse代表倒序回放,默认为restart|
android:repeatCount = "0"// 重放次数(所以动画的播放次数=重放次数+1),为infinite时无限重复
android:interpolator =" @[package:]anim/interpolator_resource" // 插值器

对应java代码中ValueAnimator类也有对应方法。

3.2 ObjectAnimator

ValueAnimator中主要是不断提供变化的值然后我们手动实现属性的变化,ObjectAnimator继承于ValueAnimator,对ValueAnimator进行了一定的简化。一些常用的操作,比如选择,渐变,我们每次都要加监听然后改变属性,比较繁琐。ObjectAnimator的出现可以让我们直接指定某些属性,然后划定起始终止值,然后一些交由系统完成。举例:

ObjectAnimator animator = ObjectAnimator.ofFloat(view,"rotation",0,270);
animator.setDuration(3000).start();

可见和ValueAnimator及其类似,只不过要指定一个属性而已,调用start后自动对该属性进行变化。

xml文件写法如下:

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:valueFrom="0"
    android:valueTo="270"
    android:duration="3000"
    android:propertyName="rotation"
    />

应用动画

ObjectAnimator animator = (ObjectAnimator) AnimatorInflater.loadAnimator(this,R.animator.object_anim);
animator.setTarget(view);
animator.setDuration(3000).start();

这个类使用的关键在于设置属性,一些基本的属性如下(图片来源于网络):



需要注意的是指定属性时,对应的值要正确,如我们进行旋转时只能用ofFloat。

当然一个控件的属性有很多,到底哪些属性可以通过ObjectAnimator 实现动画能,通过源码分析,那些有get,set方法的属性都是可以的,这也验证了ObjectAnimator 是继承于ValueAnimator,其实现原理都是一样的说法(都是不停地改变属性的值实现动画效果)。

另外ObjectAnimator 和ValueAnimator一样也有一些共有属性可以设置,这里也不详细叙述了。

最后通过文档我们可以知道ObjectAnimator 也有ofObject()方法,这样我们就可以通过包装或者直接添加get/set方法的形式,再配合自定义的估值器来实现各种动画效果。不过个人认为一些较复杂的效果还是ValueAnimator实现起来比较方便。

3.3 AnimatorSet

AnimatorSet 是用来实现组合动画的,本身并没有什么特殊效果,举例如下:

ObjectAnimator rotate = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f);
ObjectAnimator alpha = ObjectAnimator.ofFloat(view, "alpha", 1f, 0f, 1f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(3000);
animatorSet.play(rotate).with(alpha);
animatorSet.start();

当然也可以用xml文件书写:

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">
    <objectAnimator
        android:valueFrom="0"
        android:valueTo="1"
        android:propertyName="alpha"
        android:duration="3000"
        />
    <objectAnimator
        android:valueFrom="0"
        android:valueTo="360"
        android:propertyName="rotation"
        android:duration="3000"
        />
</set>

应用动画:

AnimatorSet animatorSet = (AnimatorSet) AnimatorInflater.loadAnimator(this,R.animator.set_anim);
animatorSet.setTarget(view);
animatorSet.start();

AnimatorSet 相比AnimationSet有一个顺序的概念。体现在java代码中就是涉及下面几个方法:

AnimatorSet.play(Animator anim)   :播放当前动画
AnimatorSet.after(long delay)   :将现有动画延迟x毫秒后执行
AnimatorSet.with(Animator anim)   :将现有动画和传入的动画同时执行
AnimatorSet.after(Animator anim)   :将现有动画插入到传入的动画之后执行
AnimatorSet.before(Animator anim) :  将现有动画插入到传入的动画之前执行

放在xml文件中,对应于set标签里的android:ordering属性,together表示标签内的动画同时执行,sequentially表示标签内的动画按次序执行。

3.4 ViewPropertyAnimator

这是一种更加简洁更加符合面向对象思想的动画。但其功能比较有限,基本和view动画的功能类似,但是这个属于属性动画。示例:

view.animate().alpha(0).setDuration(3000).start();

上面语句代表将透明度变为0的动画,实际应用中我们甚至不用调用start方法,系统会隐式的调用。

组合动画也很简单:

view.animate().alpha(0).rotation(270).setDuration(3000);

另外ViewPropertyAnimator还有一套以By结尾的方法,如alphaBy()。区别是不带by的表示变化到某个值,带by的表示变化量为某个值。

3.5 动画监听

一般动画监听的方法如下:

animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {
                
            }

            @Override
            public void onAnimationEnd(Animator animation) {

            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });

有时我们仅仅只是需要知道开始或结束的时刻,不必实现这么多方法,可以用下面方法:

animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
            }
        });

AnimatorListenerAdapter是个抽象类,实现了AnimatorListener接口,我们仅需实现所需的方法即可。

4.插值器

所有类型的动画都涉及到了interpolator这个属性,就是插值器。通过学习ValueAnimator我们了解到动画的本质就是不断改变数值,然后修改相应的属性。数值的改变也分很多种,如匀速改变,变速改变等,控制这种规律的就称为插值器。

首先系统为我们内置了许多现成的插值器,可以满足我们大部分需要(图片来源于网络):


当然我们也可以自定义插值器,满足各种特殊需求.一般而言我们首先要实现Interpolator接口,通过源码发现,Interpolator继承于TimeInterpolator,但没做任何处理,所以我们可以实现Interpolator接口或者TimeInterpolator接口。或者是和系统内置的插值器一样,继承BaseInterpolator类。

不管如何继承或实现,最后都是在处理一个方法:

float getInterpolation(float input);

这个方法的参数input介于0和1之间,动画开始为0,结束为1,我们在这里对这个值进行加工后,就能按照我们的意愿得到各种动画改变规律。接下来我们看一下,系统内置的一些差值器的实现:

public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
  
    ...
    public float getInterpolation(float input) {
        return input;
    }

    ...    
}

可见线性插值器没有做任何处理,怎么输入就怎么输出。

public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
    private final float mFactor;
    private final double mDoubleFactor;

    public AccelerateInterpolator() {
        mFactor = 1.0f;
        mDoubleFactor = 2.0;
    }

    public AccelerateInterpolator(float factor) {
        mFactor = factor;
        mDoubleFactor = 2 * mFactor;
    }

    ...

    public float getInterpolation(float input) {
        if (mFactor == 1.0f) {
            return input * input;
        } else {
            return (float)Math.pow(input, mDoubleFactor);
        }
    }

    ...
}

可见加速度插值器也很简单,默认mFactor为1.0,就对input去平方,随着值增大,结果呈二次递增。另外也可以自定义factor,自定义后就是调用pow方法,求一个幂,也能达到加速效果。

5.估值器

TypeEvaluator在我们在学习ValueAnimator的ofObject 方法时已经了解过。估值器和插值器是互相配合的。估值器中的系数fraction就是插值器传来的,估值器负责最后计算出实际的值。这里我们就不详细叙述了。

6.硬件加速

使用硬件加速会让动画绘制的更快,因为硬件会把图层缓存在GPU上。
开启方法:

view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(this,R.animator.anim);

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int currentValue = (Integer) animation.getAnimatedValue();
                view.setRotation(currentValue);
            }
        });
animator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                view.setLayerType(View.LAYER_TYPE_NONE, null);
            }
        });
animator.start();

基本就是动画开始前开启硬件加速,结束后关闭。

对于ViewPropertyAnimator,开启方法更加简单,调用withLayer()即可:

view.animate().alpha(0).withLayer();

7.总结

可见Android中的动画是非常丰富而且开放的,不仅提供了大量预置的效果,也开放了较底层的接口供我们实现各种丰富的动画效果。

上一篇下一篇

猜你喜欢

热点阅读