Android 动画详解:属性动画、View 动画和帧动画

在 Android 中,基本的动画共有三种类型:android

  1. View 动画:也叫视图动画或者补间动画,主要是指 android.view.animation 包下面的一些类,只能被用来设置给 View,缺点是好比当控件移动以后,接收点击的控件的位置不会跟随移动,而且可以实现的效果只有移动、缩放、旋转和淡入淡出操做四种及其组合。
  2. Drawable 动画:也叫 Frame 动画或者帧动画,其实能够划分到视图动画的类别,实现方式是将一些列的 Drawable 像幻灯片同样一个一个地显示。
  3. Property 动画: 属性动画主要是指 android.animation 包下面的一些类,只对 API 11 以上版本的Android 系统才有效,但咱们能够经过兼容库作低版本兼容。这种动画能够设置给任何 Object,包括那些尚未渲染到屏幕上的对象。这种动画是可扩展的,可让你自定义任何类型和属性的动画。

文末有免费福利哦git

一、Drawable 动画

在这里,咱们先对 Drawable 动画进行讲解,由于它相对于后面的两种动画比较简单。在示例程序咱们准备了一系列图片资源,并在 drawable 文件夹下面定义了动画资源 record_anim.xml:github

<?xml version="1.0" encoding="utf-8"?>
<animation-list android:oneshot="false"
    xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@drawable/record0" android:duration="500"/>
    <item android:drawable="@drawable/record1" android:duration="500"/>
    <item android:drawable="@drawable/record2" android:duration="450"/>
    <item android:drawable="@drawable/record3" android:duration="400"/>
    <item android:drawable="@drawable/record4" android:duration="350"/>
    <item android:drawable="@drawable/record5" android:duration="400"/>
    <item android:drawable="@drawable/record6" android:duration="400"/>
</animation-list>
复制代码

而后,咱们在代码中使用该资源,并将其赋值给 ImageView。而后,咱们从该控件中获取该 Drawable 并将其转换成 AnimationDrawable,随后咱们调用它的 start() 方法就开启了 Drawable 动画:面试

getBinding().ivRecord.setImageResource(R.drawable.record_anim);
animDraw = (AnimationDrawable) getBinding().ivRecord.getDrawable();
animDraw.start();
复制代码

此外,咱们能够调用该 Drawable 的 stop() 方法中止动画。小程序

帧动画的注意事项

使用帧动画的时候要注意设置的图片不宜过多、过大,以防止由于内存不够而出现 OOM。性能优化

二、View 动画

2.1 基本 View 动画

该动画的资源处在 android.view.animation 包下,主要有如下几个类,它们都继承自 Animation ,咱们可使用它们来实现复杂的动画。这些动画类分别有对应的 xml 标签,因此,咱们能够在 xml 中定义动画,也能够在代码中实现动画效果。这里的 AnimationSet 能够用来将多个动画效果进行组合,各预约义动画的对照能够参考下面这张图表:架构

 

Android View 动画框架

 

2.2 View 动画属性

固然,要实现一种动画效果会有许多属性须要指定,在 xml 中,咱们用标签的属性指定,在代码中咱们用对象的 setter 方法指定。因而,咱们能够获得下面这个对应关系:app

 

Android View 动画属性详解

 

上面的对应关系是全部的 View 动画共用的,对各个具体的动画类型还有其独有的属性。你能够在各个动画的构造方法中,经过它们从 AttributeSet 中获取了哪些字段来了解它们都定义了哪些属性,这里咱们不对其一一进行说明。各预约义的属性动画分别按照不一样的方式实现了 AnimationapplyTransformation 方法,具体的这些属性如何使用以及 View 动画的效果是如何实现的,均可经过阅读该方法的定义得知。框架

对于 AnimationSet,它内部维护了一个 Animation 列表,而且其自己也是一个 Animation,因此,AnimationSet 内部能够添加子 AnimationSetide

文末有免费福利哦

2.3 插值器

上文中咱们提到过,View 动画的具体实现是经过覆写 AnimationapplyTransformation 方法来完成的。这里咱们以 AlphaAnimation 为例来看它是如何做用的,同时你应该注意插值器的做用原理。该方法会在 Animation 中被循环调用,调用的时候会根据插值器计算出一个时间,并将其传递到 applyTransformation 方法中。

AnimationgetTransformation 方法片断:

if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
    if (!mStarted) {
        fireAnimationStart();
        mStarted = true;
        if (NoImagePreloadHolder.USE_CLOSEGUARD) {
            guard.open("cancel or detach or getTransformation");
        }
    }

    if (mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);

    if (mCycleFlip) {
        normalizedTime = 1.0f - normalizedTime;
    }

    final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
    applyTransformation(interpolatedTime, outTransformation);
}
复制代码

AlphaAnimationapplyTransformation 方法:

protected void applyTransformation(float interpolatedTime, Transformation t) {
    final float alpha = mFromAlpha;
    t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
}
复制代码

显然,这里的 interpolatedTime 的是一个比例。好比,假如一个透明动画须要持续 10s,透明度须要从 0.5f1.0f,而插值的规则是一个二次函数。那么第 t (0<t<10) 秒的时候控件的透明度应该是:

alpha = 0.5f + (1.0f - 0.5f) * t^2 / 100
复制代码

以上就是插值器的做用原理,你也能够按照本身的需求实现本身的插值器,从而实现期待的动画效果。

2.4 使用 View 动画

做为一个例子,这里咱们实现一个让控件抖动的动画。在 anim 文件夹下面,咱们定义一个平移的动画,并使用插值器使其重复:

anim/shake.xml 的定义:

<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="700"
    android:fromXDelta="0.0"
    android:interpolator="@anim/cycle_7"
    android:toXDelta="15.0" />
复制代码

插值器 anim/cicle_7.xml 的定义:

<?xml version="1.0" encoding="utf-8"?>
<cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
    android:cycles="4.0" />
复制代码

而后,咱们在代码中加载 Animation 并调用控件的 startAnimation() 方法开启动画:

getBinding().v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.shake));
复制代码

对于 View ,咱们有 startAnimation() 用来对 View 开始动画;有 clearAnimation() 用来取消 View 在执行的动画。

不使用 xml,仅使用代码咱们同样能够实现上述的效果,这里咱们再也不进行说明。

2.5 View 动画的特殊使用场景

2.5.1 LayoutAnimation

LayoutAnimation 做用于 ViewGroup,可使其子元素出场时都均有某种动画效果,一般用于 ListView。咱们能够像下面这样定义布局动画:

<?xml version="1.0" encoding="utf-8"?>
<layoutAnimation 
    android:delay="500"
    android:animation="@anim/shake"
    xmlns:android="http://schemas.android.com/apk/res/android" />
复制代码

显然,这里咱们须要引用一个其余的动画。而后,咱们能够在 ListViewlayoutAnimation 属性中指定布局动画。或者调用 ListViewsetLayoutAnimation() 方法应用上述动画。

2.5.2 Activity 切换

咱们能够经过在 Activity 中调用 overridePendingTransition(R.anim.shake, R.anim.shake); 方法来重写 Activity 的切换动画。注意这个方法应该在 startActivity(Intent) 或者 finish() 以后当即调用。

三、属性动画

3.1 基础梳理

咱们能够对比 View 动画来学习属性动画。

  1. 属性动画主要是指 android.animation 包下面的一些类。
  2. 属性动画基础的动画类是 Animator;属性动画也为咱们提供了几个预约义的类:AnimatorSet, ObjectAnimator, TimeAnimatorValueAnimator;这几个预约义类之间的继承关系是,AnimatorSetValueAnimator 直接继承自 Animator,而 ObjectAnimatorTimeAnimator 继承自 ValueAnimator
  3. 与 View 动画不一样的是,属性动画的使用范围更加宽泛,它不局限于 View,本质上它是经过修改控件的属性值实现的动画。当你尝试对某个对象的某个属性进行修改的时候会有一些限制,即属性动画要求该对象必须提供了该属性的 setter 方法。
  4. 属性动画也有 xml 和代码两种定义方式,它的 xml 一般定义在 animator 文件夹下面,而 View 动画定义在 anim 文件夹下面。
  5. 属性动画提供了相似于 AnimationUtils 的方法用来从布局文件夹中加载属性动画:AnimatorInflater 类的 loadAnimator() 方法。
  6. 属性动画也有本身的插值器:TimeInterpolator,而且也提供了几个预约义的插值器。
  7. 咱们也能够调用 View 的方法来使用属性动画,咱们能够经过 View 的 animate() 方法获取一个 ViewPropertyAnimator,而后调用 ViewPropertyAnimator 的其余方法进行链式调用以实现复杂的属性动画效果。

下面是属性动画的代码实现和 xml 实现两种方式的对比:

 

属性动画

 

上文中,咱们总结了属性动画的一些知识,并将其与 View 动画进行了对比。这里是一个简单的梳理,在下文中咱们会对属性动画进行更加详细的介绍。

3.2 使用属性动画

3.2.1 ValueAnimator

上面说过 ValueAnimatorObjectAnimatorTimeAnimator 的基类,咱们能够这样使用它:

ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
anim.setDuration(300);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        float currentValue = (float) animation.getAnimatedValue();
        Log.d("TAG", "cuurent value is " + currentValue);
    }
});
anim.start();
复制代码

这里咱们使用 log 输出了值渐变的过程,从日志中能够看出它的效果是值从 0 不断递增直到 1。若是咱们在这个监听方法中根据值修改控件的属性同样能够实现动画效果。除了 ofFloat() 还有 ofInt() 等方法,它们的效果类似。

3.2.2 ObjectAnimator

上面,若是咱们想要实现动画效果,须要在 ValueAnimator 的监听事件中修改对象的属性,这里的 ObjectAnimator ,咱们只须要传入对象实例和属性的字符串名称,修改对象属性的操做就能够自动完成。好比下面的程序的效果是控件 textview 的透明度会在 5s 以内从 1 变成 0 再变回 1.

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
animator.setDuration(5000);
animator.start();
复制代码

注意这里咱们传入的是 alpha,这个字段自己并不存在于控件中,而是有一个 setAlpha() 的方法。也就是说,ObjectAnimator 做用的原理是经过反射触发 setter 方法而不是修改属性来实现的。你能够在类 PropertyValuesHolder 中更详细地了解这方面的内容。

PropertyValuesHolder 包装了咱们要修改的属性的对象和方法等信息,而后会使用反射触发指定对象的方法来完成对对象属性的修改。其中

void setupSetter(Class targetClass) {
    Class<?> propertyType = mConverter == null ? mValueType : mConverter.getTargetType();
    mSetter = setupSetterOrGetter(targetClass, sSetterPropertyMap, "set", propertyType);
}
复制代码

会去寻找咱们要修改属性的 setter 方法,而后

void setAnimatedValue(Object target) {
    if (mProperty != null) {
        mProperty.set(target, getAnimatedValue());
    }
    if (mSetter != null) {
        try {
            mTmpValueArray[0] = getAnimatedValue();
            mSetter.invoke(target, mTmpValueArray);
        } catch (InvocationTargetException e) {
            Log.e("PropertyValuesHolder", e.toString());
        } catch (IllegalAccessException e) {
            Log.e("PropertyValuesHolder", e.toString());
        }
    }
}
复制代码

会去触发 setter 方法,以修改对象的属性。

文末有免费福利哦

3.2.3 AnimatorSet

AnimatorSet 内部提供了一个构建者 AnimatorSet.Builder 来帮助咱们构建组合动画,AnimatorSet.Builder 提供了下面四种方法:

  1. after(Animator anim):将现有动画插入到传入的动画以后执行
  2. after(long delay):将现有动画延迟指定毫秒后执行
  3. before(Animator anim):将现有动画插入到传入的动画以前执行
  4. with(Animator anim):将现有动画和传入的动画同时执行

当咱们调用 AnimatorSetplay() 方法的时候就能获取一个 AnimatorSet.Builder 实例,而后咱们就可使用构建者的方法进行链式调用了:

ObjectAnimator moveIn = ObjectAnimator.ofFloat(textview, "translationX", -500f, 0f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
animSet.play(rotate).with(fadeInOut).after(moveIn);
animSet.setDuration(5000);
animSet.start();
复制代码

3.2.4 TypeEvaluator

正如前文所述,属性动画也有本身的插值器,咱们能够经过插值函数指定在某个时间段内属性改变的速率。插值函数获得的是一个比例,是没有意义的。在 View 动画的 AlphaAnimation 中,若是咱们指定了起止的透明度,那么咱们能够经过透明度的计算规则获得某个时刻的透明度。可是对于属性动画,由于它能够应用于任何属性,这个属性又多是任何类型的,那么这个属性将采用什么样的计算规则呢?这就须要咱们使用 TypeEvaluator 来指定一个计算规则。也就是说,TypeEvaluator 是属性动画的属性的计算规则。

下面是 TypeEvaluator 的定义,这里的三个参数的含义分别是,fraction 是当前的比例,能够经过插值器计算获得;startValueendValue 分别是属性变化的起止值。它的返回结果就是在某个时刻某个属性的值。

public interface TypeEvaluator<T> {
    public T evaluate(float fraction, T startValue, T endValue);
}
复制代码

属性动画中已经为咱们提供了几个预约义的 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);
    }
}
复制代码

在属性动画的 PropertyValuesHolder 中会根据属性的类型选择预约义的 TypeEvaluator。可是若是咱们的属性的类型不在预约义的范围以内就须要本身实现一个 TypeEvaluator。下面咱们以日期类型为例来实现一个 TypeEvaluator

当咱们使用 ValueAnimatorofObject() 方法获取 ValueAnimator 实例的时候,要求咱们传入一个 TypeEvaluator,因而咱们能够像下面这样定义:

private static class DateEvaluator implements TypeEvaluator<Date> {

    @Override
    public Date evaluate(float fraction, Date startValue, Date endValue) {
        long startTime = startValue.getTime();
        return new Date((long) (startTime + fraction * (endValue.getTime() - startTime)));
    }
}
复制代码

而后,咱们能够这样使用它:

ValueAnimator animator = ValueAnimator.ofObject(new DateEvaluator(), new Date(0), new Date());
animator.setDuration(5000);
animator.addUpdateListener(animation -> {
    Date date = (Date) animation.getAnimatedValue();
    LogUtils.d(date);
});
animator.start();
复制代码

这样就能够获得在 5s 以内输出的从时间戳为0,到当前时刻的全部的日期变化。

3.2.5 TimeInterpolator

就像 View 动画同样,咱们能够为属性动画指定一个插值器。插值器的做用是用来设置指定时间段内数值的变化的速率。在属性动画中,插值器是 TimeInterpolator,一样也有几个默认的实现:

  1. AccelerateDecelerateInterolator:先加速后减速。
  2. AccelerateInterpolator:加速。
  3. DecelerateInterpolator:减速。
  4. AnticipateInterpolator:先向相反方向改变一段再加速播放。
  5. AnticipateOvershootInterpolator:先向相反方向改变,再加速播放,会超出目标值而后缓慢移动至目标值,相似于弹簧回弹。
  6. BounceInterpolator:快到目标值时值会跳跃。
  7. CycleIinterpolator:动画循环必定次数,值的改变为一正弦函数:Math.sin(2 * mCycles * Math.PI * input)。
  8. LinearInterpolator:线性均匀改变。
  9. OvershottInterpolator:最后超出目标值而后缓慢改变到目标值。

3.2.6 在 xml 中使用属性动画

咱们能够像下面这样定义一个属性动画,

<set android:ordering=["together" | "sequentially"]>
    <objectAnimator
        android:propertyName="string"
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>
    <animator
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>
    <set>
        ...
    </set>
</set>
复制代码

这里的android:ordering用于控制子动画启动方式是前后有序的仍是同时进行,两个可选参数: sequentially 表示动画按照前后顺序;together(默认)表示动画同时启动。

这里的 <objectAnimator> 标签的含义以下:

 

属性动画的XML属性详解

 

这样在 XML 中定义了属性动画以后,咱们能够在代码中经过工具类获取到动画实例并使用:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(myContext, R.animtor.property_animator);
set.setTarget(myObject);
set.start();
复制代码

四、使用动画的注意事项

  1. 内存耗尽:使用帧动画的时候防止由于图片过多致使 OOM。
  2. View 动画并无真正改变 View 的位置View 动画并无真正改变 View 的属性,即 View 动画执行以后并未改变 View 的真实布局属性值。譬如咱们在布局中有一个 Button 在屏幕上方,咱们设置了平移动画移动到屏幕下方而后保持动画最后执行状态呆在屏幕下方,这时若是点击屏幕下方动画执行以后的 Button 是没有任何反应的,而点击原来屏幕上方没有 Button 的地方却响应的是点击 Button 的事件。
  3. 内存泄漏:使用属性动画的时候,当使用无限循环动画,须要在 Activity 退出的时候中止动画,否则可能会由于没法释放资源而致使 Activity 内存泄漏。
  4. 动画兼容:当 APP 须要兼容到 API 11 如下的时候就须要注意动画的兼容问题。
  5. 使用 dp 而不是 px:由于 px 在不一样设备上面的兼容问题,使用动画的时候尽可能使用 dp 做为单位。
  6. 硬件加速:使用硬件加速能够提高动画的流畅性。

源代码

你能够在Github获取以上程序的源代码: Android-references

最后给你们分享一份很是系统和全面的Android进阶技术大纲及进阶资料,及面试题集

想学习更多Android知识,请加入Android技术开发企鹅交流 7520 16839

进群与大牛们一块儿讨论,还可获取Android高级架构资料、源码、笔记、视频

包括 高级UI、Gradle、RxJava、小程序、Hybrid、移动架构、React Native、性能优化等全面的Android高级实践技术讲解性能优化架构思惟导图,和BATJ面试题及答案!

群里免费分享给有须要的朋友,但愿可以帮助一些在这个行业发展迷茫的,或者想系统深刻提高以及困于瓶颈的朋友,在网上博客论坛等地方少花些时间找资料,把有限的时间,真正花在学习上,因此我在这免费分享一些架构资料及给你们。但愿在这些资料中都有你须要的内容。

相关文章
相关标签/搜索