Android动画

2,092 阅读7分钟

前言

在Android开发中,UI的变化非常常见,如果不使用动画来进行过度,那么用户体验就会不怎么好。我将动画分为大三类View动画、帧动画、属性动画三种,下面先从View动画开始

一、View动画

View动画包括以下几种

  • ScaleAnimation(缩放动画)
  • TranslateAnimation(平移动画)
  • AlphaAnimation(透明度动画)
  • RotateAnimation(旋转动画)

并且还提供了一个AnimationSet类来把多个View动画进行组合显示,我们可以直接在代码中创建这些Animation对象也可以在xml中进行指定

缩放动画

  • 代码中定义
ScaleAnimation sAnimation = new ScaleAnimation(0, 1, 0,
                1, Animation.RELATIVE_TO_SELF, 0.5f, 
                Animation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(2000);
imageView.startAnimation(sAnimation);
  • xml中定义
<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXScale="0" android:toXScale="1" android:fromYScale="0"
    android:toYScale="1" android:pivotX="50%" android:pivotY="50%" android:duration="2000">
</scale>

平移动画

  • 代码中定义
TranslateAnimation tAnimation = new TranslateAnimation(0, 100, 0, 100);
tAnimation.setDuration(2000);
imageView.startAnimation(tAnimation);
  • xml中定义
<translate xmlns:android="http://schemas.android.com/apk/res/android" android:duration="2000" android:fromXDelta="0" 
    android:toXDelta="200" android:fromYDelta="0" android:toYDelta="200">
</translate>
imageView.startAnimation(AnimationUtils.loadAnimation(this, R.anim.translate));

透明度动画

  • 代码中定义
AlphaAnimation aAnimation = new AlphaAnimation(0, 1);
aAnimation.setDuration(2000);
imageView.startAnimation(aAnimation);
  • xml中定义
<alpha xmlns:android="http://schemas.android.com/apk/res/android" android:duration="2000"
    android:fromAlpha="0" android:toAlpha="1">
</alpha>

旋转动画

  • 代码中定义
RotateAnimation rAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f,
                Animation.RELATIVE_TO_SELF, 0.5f);
rAnimation.setDuration(2000);
imageView.startAnimation(rAnimation);
  • xml中定义
<rotate xmlns:android="http://schemas.android.com/apk/res/android" android:duration="2000" 
    android:fromDegrees="0" android:toDegrees="360">
</rotate>

组合动画

  • 代码中定义
AnimationSet set = new AnimationSet(true);
set.addAnimation(sAnimation);
set.addAnimation(aAnimation);
set.addAnimation(tAnimation);
set.addAnimation(rAnimation);
// 所有子动画都会被设置成该超时时间
set.setDuration(5000);
imageView.startAnimation(set);
  • xml中定义
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha 
        android:duration="2000"
        android:fromAlpha="0" 
        android:toAlpha="1">
    </alpha>
    <rotate 
        android:duration="2000"
        android:fromDegrees="0" 
        android:toDegrees="360">
    </rotate>
    <scale
        android:fromXScale="0" 
        android:toXScale="1" 
        android:fromYScale="0"
        android:toYScale="1"
        android:pivotX="50%" 
        android:pivotY="50%"
        android:duration="2000">
    </scale>
    <translate 
        android:duration="2000" 
        android:fromXDelta="0"
        android:toXDelta="200" 
        android:fromYDelta="0" 
        android:toYDelta="200">
    </translate>
</set>

以上代码有几个需要注意

  • 在xml中定义的动画可以通过AnimationUtils.loadAnimation(context, resId))拿到对应的Animation对象
  • 如果想让一个动画延时执行可以设置startOffset
  • 如果想让一个动画循环执行可以设置repeatCount
  • 动画执行完后默认会返回到原始位置如果不需要返回那么需要将setFillAfter设置为true,但是虽然执行完后保留在了执行完后的位置但是该位置并不能响应点击事件,原位置能够响应点击事件
  • 上述动画都没有设置插值器,采用默认的插值器AccelerateDecelerateInterpolator,如果想要动画按照不停的速率运行,那么可以设置以下几种插值器
插值器类 对应xml资源id 变化方式
AccelerateDecelerateInterpolator @android:anim/accelerate_decelerate_interpolator 先加速后减速
AccelerateInterpolator @android:anim/accelerate_interpolator 一直加速
AnticipateInterpolator @android:anim/anticipate_interpolator 先往反方向运动一段距离再前进
AnticipateOvershootInterpolator @android:anim/anticipate_overshoot_interpolator 先往反方向运动再前进并且超过目标点,最后回到目标点
BounceInterpolator @android:anim/bounce_interpolator 类似球落地时的反弹效果
CycleInterpolator @android:anim/cycle_interpolatorr 循环,先往正向运动到终点,然后向反方向运动知道运动到-终点处再回来
DecelerateInterpolator @android:anim/decelerate_interpolator 一直减速
LinearInterpolator @android:anim/linear_interpolator 匀速运动
OvershootInterpolator @android:anim/overshoot_interpolator 移动超过目标点,然后再回来

LayoutAnimation

如果一个ViewGroup被设置了该属性那么在第一次显示该ViewGroup时会执行所设置的动画,用法如下。

首先定义一个AnimationSet

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:duration="2000"
        android:fromAlpha="0"
        android:toAlpha="1">
    </alpha>
    <translate
        android:duration="2000"
        android:fromYDelta="100%p"
        android:toYDelta="0">
    </translate>
</set>

然后再res/anim里面建立一个根节点为layoutAnimation文件,引用上述的文件,其中的delay表示对应ViewGroup中的每个View都要延迟动画duration * delay的时间,现在动画持续时间是2000ms,那么第一个item就会在600ms的时候执行动画,第二个会在1200ms的时候执行

<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android"
    android:animationOrder="normal"
    android:animation="@anim/set"
    android:delay="0.3">
</layoutAnimation>

最后给目标ViewGroup设置LayoutAnimation

<android.support.v7.widget.RecyclerView
    android:id="@+id/rv"
    android:background="@color/colorPrimary"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layoutAnimation="@anim/layout_animation"
    android:orientation="vertical">

</android.support.v7.widget.RecyclerView>

运行效果如下图所示,当然有点丑,需要的时候可以慢慢调整

二、帧动画

帧动画就是将一组图片按照特定的顺序进行播放, 使用方式如下所示

<animation-list xmlns:android="http://schemas.android.com/apk/res/android" 
    android:oneshot="true">
    <item android:drawable="@drawable/s1" android:duration="1000"/>
    <item android:drawable="@drawable/s2" android:duration="1000"/>
    <item android:drawable="@drawable/s3" android:duration="1000"/>
    <item android:drawable="@drawable/s4" android:duration="1000"/>
    <item android:drawable="@drawable/s5" android:duration="1000"/>
    <item android:drawable="@drawable/s6" android:duration="5000"/>
    <item android:drawable="@drawable/s7" android:duration="1000"/>
</animation-list>

imageView.setImageResource(R.drawable.list);
mDrawable = (AnimationDrawable)imageView.getDrawable();
imageView.post(new Runnable() {
    @Override
    public void run() {
        mDrawable.start();
    }
});

这里有几个注意点

  • oneshot表示是否只播放一次,该值默认是false表示循环播放
  • 帧动画在View三大流程执行完毕前,只会显示第一帧所以要在三大流程执行完成后再开启

三、属性动画

前面讲到的View动画其实并不会真正改变View的属性比如位置等等,而属性动画会真正的改变View的属性,其主要类包括以下几个,其中ObjectAnimator继承于ValueAnimator,先来看看ViewPropertyAnimator

  • ViewPropertyAnimator
  • ValueAnimator
  • ObjectAnimator
  • AnimatorSet

ViewPropertyAnimator

这个类内部其实是通过ValueAnimator实现的,我们可以通过view.animator()得到该类的对象

// translation从当前值慢慢的变为100
imageView.animate().translationX(100);
// translation从当前值慢慢的变为原来值+100
imageView.animate().translationXBy(100);
// 反转180度
imageView.animate().rotation(180);

注意我们不需要手动调用start方法其就会自动运行,默认持续时间是300ms

ValueAnimator

我们可以通过ofInt、ofFloat、ofArgb等获取到ValueAnimator实例,下面以通过属性动画动态改变图片的高度为例

在代码中定义

ValueAnimator vAnimator = ValueAnimator.ofInt(400, 0);
vAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        ViewGroup.LayoutParams params = imageView.getLayoutParams();
        params.height = (int) animation.getAnimatedValue();
        imageView.setLayoutParams(params);
    }
});
vAnimator.setDuration(3000);
vAnimator.start();

在xml中定义

<animator xmlns:android="http://schemas.android.com/apk/res/android" android:valueFrom="400dp" 
android:valueType="intType" android:valueTo="0dp" android:duration="3000">
</animator>
ValueAnimator vAnimator =
                (ValueAnimator) AnimatorInflater.loadAnimator(this, R.animator.animator);

ObjectAnimator

我们可以通过ofInt、ofFloat、ofArgb等获取到ObjectAnimator实例,一般会操作以下几个属性

  • translationX、translationY 平移
  • rotation、rotationX、rotationY 旋转
  • privotX、privotY 控制旋转缩放的支点位置
  • alpha 透明度
  • x 、y 、z位置

下面以通过属性动画动态改变图片的透明度为例

在代码中定义

ObjectAnimator animator = ObjectAnimator.ofFloat(imageView, "alpha", 0, 1);
animator.setDuration(3000);
animator.start();

在xml中定义

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:valueType="floatType" android:duration="3000" android:propertyName="alpha"
    android:valueFrom="0" android:valueTo="1">
</objectAnimator>
ObjectAnimator animator = (ObjectAnimator)
                AnimatorInflater.loadAnimator(this, R.animator.object_animator);
animator.setTarget(imageView);
animator.start();

几个注意点:

  • 当ofXXX属性名参数后面只有一个参数时那个参数表示的是目标值,系统会调用getter获取初始化值,然后不停的调用setter设置,当超过一个参数时第一个就变为初始值最后一个变成目标值,中间的都是转接点这个时候可以不提供setter方法
  • 当一个类的某个属性没有提供setter方法时我们需要对其运用属性动画,我们可以建一个包装类
  • 如果发现使用了属性动画改变自定义View的属性发现界面并没有发生变化,请检查下你的setter方法有没有触发重绘

AnimatorSet

AnimatorSet类似AnimationSet,作用是把多个属性动画组合起来执行

在代码中定义

ValueAnimator vAnimator =
                (ValueAnimator) AnimatorInflater.loadAnimator(this, R.animator.animator);
vAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        ViewGroup.LayoutParams params = imageView.getLayoutParams();
        params.height = (int) animation.getAnimatedValue();
        imageView.setLayoutParams(params);
    }
});
vAnimator.setDuration(3000);
vAnimator.start();
ObjectAnimator oAnimator = (ObjectAnimator)
        AnimatorInflater.loadAnimator(this, R.animator.object_animator);
oAnimator.setTarget(imageView);
oAnimator.start();
AnimatorSet set = new AnimatorSet();
// 同时执行
set.playTogether(vAnimator, oAnimator);
// 顺序执行
set.playSequentially(vAnimator, oAnimator);

在xml中定义 其中ordering表示同时执行还是顺序执行

<set xmlns:android="http://schemas.android.com/apk/res/android" android:ordering="together">
    <animator android:valueFrom="400px"
        android:valueTo="0px" android:duration="3000" android:valueType="intType">
    </animator>
    <objectAnimator
        android:valueType="floatType" android:duration="3000" android:propertyName="alpha"
        android:valueFrom="0" android:valueTo="1">
    </objectAnimator>
</set>

PropertyValuesHolder

使用PropertyValuesHolder也能实现多动画同时执行

PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("alpha", 0, 1);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofInt("rotation", 0, 360);
ValueAnimator animator = ObjectAnimator.ofPropertyValuesHolder(holder1, holder2);
animator.setDuration(3000);
animator.setTarget(imageView);
animator.start();

Tips: 将ViewGroup的animateLayoutChanges属性设置为true,在添加View或者移除View的时候会有动画