美文网首页
Android中的动画

Android中的动画

作者: KwokKwok | 来源:发表于2016-08-05 18:41 被阅读41次

Android中的动画主要有帧动画、补间动画、属性动画(3.0之后出现),此处按照官方将其分为两大类记录。他们之间的不一样的地方,正如官方对属性动画所描述的“you can animate any porperty fo any object(Views and non-Views) and the object itself is actually modified”,View Animation所能做的动画有限,并且控件本身可能和它的视觉效果不一致以至于你需要更多的逻辑处理。但如果View Animation已经满足你的需要了,那就用吧。

内容摘要

  1. 视觉动画的使用。(帧动画、补间动画)
  2. 属性动画的使用及原理。(涉及插值器、估值器、关键帧等)
  3. 布局动画。(不常用,只简单提一下)
  4. 转场动画。(Activity和Fragment)
  5. API使用。(包括揭露效果和水波纹动画)

一、View Animation / 视觉效果的动态改变

官方建议写为XML以使提高可读性和复用性,并方便替换。
注意:被施加动画的View不会调整自身大小来适应动画变化,但是动画可以绘制到View边界之外。

Frame animation帧动画(官方文档有一处小错误)

XML文件放在 res/drawable/xxx.xml

定义在XML文件里,代码引用设为View背景,再使用getBackground()获取到之后强转为AnimationDrawable并调用start()方法即可。

详细属性值:

  • 根节点必须为<animation-list>,一个属性。
    • android:oneshot属性可设置是否只播放一次,默认为false,循环播放。
  • 子节点只有一个<item>,表示一帧。 两个属性。
    • android:drawable设置该帧的图片
    • android:duration设置该帧的播放时长,单位ms。

Tween animation补间动画

XML文件放在 res/anim/xxx.xml
文件必须只包含一个根节点,<alpha>, <scale>, <translate>, <rotate>, or <set>,子标签也可以继续使用 <set>

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    <!--插值器-->
    android:interpolator="@[package:]anim/interpolator_resource"
    <!--是否为子元素共享插值器-->
    android:shareInterpolator=["true" | "false"]
    <!--动画结束后保持在什么状态,after为结束时的状态,before没发现什么变化。另外,只能设置在根标签-->
    android:fillAfter=["true" | "false"]
    android:fillBefore= ["true" | "false"]
    <!--动画时长,所以标签都可以设置。但父标签有,子标签的就不会生效-->
    android:duration="ms"
    <!--重复模式,reverse为倒着播放一遍-->
    android:repeatMode=["restart" | "reverse"]
    <!--动画延迟开始时间-->
    android:startOffset="ms">

    <alpha
         <!--0.0透明,1.0不透明-->
        android:fromAlpha="float"
        android:toAlpha="float" />

    <scale
         <!--1.0无变化-->
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"

         <!--缩放时该坐标保持固定-->
        android:pivotX="float"
        android:pivotY="float" />

    <translate
         <!--具体数值指像素,加%指相对于自身,加%p指相对于父控件-->
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" />
    <rotate
         <!--度数-->
        android:fromDegrees="float"
        android:toDegrees="float"
         <!--中心点坐标。相对于控件左边,具体数值指像素,加%指相对于自身,加%p指相对于父控件 -->
        android:pivotX="float"
        android:pivotY="float" />
    <set>
        ...
    </set>
</set>

使用时

Animation mAnimation=AnimationUtils.loadAnimation(this,R.anim.xxx);
//mView.startAnimation(mAnimation);
mView.setAnimation(mAnimation);
mAnimation.start();

//添加监听器
mAnimation.setAnimationListener(new Animation.AnimationListener() {
    @Override
    public void onAnimationStart(Animation animation) {
           //动画开始
    }

    @Override
    public void onAnimationEnd(Animation animation) {
           //动画结束
    }

    @Override
    public void onAnimationRepeat(Animation animation) {
           //动画重复
    }
});

插值器
系统提供的,xml中使用@android:anim/xxxx

  • AccelerateDecelerateInterpolator ,加速减速插值器,中间速度最快
  • AccelerateInterpolator,加速插值器
  • AnticipateInterpolator,预备插值器,先向后,再向前加速。
  • AnticipateOvershootInterpolator,预备超出。先向后,到动画最后会超出一段再回到该处于的状态
  • BounceInterpolator,弹性插值器。最后会有一个反复的效果
  • CycleInterpolator,循环,对动画效果做一个正弦函数,0到1到0到-1到0
  • DecelerateInterpolator,减速
  • LinearInterpolator,线性,速率不变
  • OvershootInterpolator,结束时多向前一定的值再回到原来状态

自定义插值器,放置在res/anim/xxx.xml,属性值不做特别说明则都是float类型。

  • <accelerateInterpolator> android:factor,加速度值,默认为1
  • <anticipateInterpolator> android:tension,张力值,默认为2
  • <anticipateOvershootInterpolator> android:tension,张力值,默认2;android:extraTension,多少倍的张力,默认1.5
  • <cycleInterpolator> android:cycles,integer,多少周,默认1
  • <decelerateInterpolator> android:factor,减速度值,默认1
  • <overshootInterpolator> android:tension,张力值,默认2

如:res/anim/my_overshoot_interpolator.xml

<?xml version="1.0" encoding="utf-8"?>
<overshootInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
    android:tension="7.0"
    />

二、Property Animation 属性动画/属性的动态改变

每10ms(具体值根据运行环境有所不同)根据规则(插值器和估值器)设置一次属性

场景1,线性插值效果场景1,线性插值效果 场景2,加减速插值效果场景2,加减速插值效果 流程示意图流程示意图

结合场景1和场景2分析原理:

  • 需要指定起始值(x=0)、终值(x=40)、时长(40ms),并调用start()开始动画。
  • 在动画的执行过程中,ValueAnimator会基于动画已经运行时间和总时间来计算出一个0-1的值,称为elapsed fraction,如场景中t=10ms的时候该值为 10/40=0.25。
  • 调用当前设置的TimeInterpolator(该类为所有具体的插值器的最终父类)计算interpolated fraction的值,该值依据elapsed fraction和插值器计算,如场景二因为是加减速插值器所以在t=10ms时interpolated fraction的值为0.15,而场景一由于是线性插值器原因,该时刻的值为0.25,和elapsed fraction保持一致
  • 之后会调用合适的TypeEvaluator去计算属性当前时刻的值,该值根据interpolated fraction、起始值和结束值。如场景二中t=10ms时,属性x的值为0.15*(40-0)=6

XML位置:res/animator/xxx.xml
xml文件根标签必须是 <set>, <objectAnimator>, or <animator>,同样,<set>标签可以嵌套<set>

<set
  <!--同时执行|顺序执行-->

  android:ordering=["together" | "sequentially"]>

    <objectAnimator
        <!--属性名,必须。施加动画的object必须有这个属性-->
        android:propertyName="string"
        android:duration="int"
        <!--动画属性起始值,不指定的话会从从属性的getXX()方法获取-->
        android:valueFrom="float | int | color(6位16进制如#222222,后同)"
        <!--必须-->
        android:valueTo="float | int | color"
        android:startOffset="int"
        <!--重复次数,-1无限,1意思为运行2次,0表示不重复-->
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        <!--指定值的类型-->
        android:valueType=["intType" | "floatType"(default)]/>

    <!--对应ValueAnimator-->
    <animator
        android:duration="int"
        <!--From和To在该标签下都是必须,From不会自指定-->
        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>

res/animator/property_animator.xml

<set android:ordering="sequentially">
    <set>
        <objectAnimator
            android:propertyName="x"
            android:duration="500"
            android:valueTo="400"
            android:valueType="intType"/>
        <objectAnimator
            android:propertyName="y"
            android:duration="500"
            android:valueTo="300"
            android:valueType="intType"/>
    </set>
    <objectAnimator
        android:propertyName="alpha"
        android:duration="500"
        android:valueTo="1f"/>
</set>

代码中使用:

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(comtext,
    R.animator.property_animator);
set.setTarget(myObject);
set.start();

//另外AnimatorSet还有多种使用方法,灵活使用playTogether()、play()、with()、before()、after()来实现复杂的效果
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);
ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
animatorSet.start();

监听器

  • Animator.AnimatorListener(),可以使用AnimatorListenerAdapter()来有选择的实现某些方法。
    • onAnimationStart(),动画开始
    • onAnimationEnd(),动画结束
    • onAnimationRepeat(),动画重复
    • onAnimationCancel(),动画取消
  • ValueAnimator.AnimatorUpdateListener()
    • onAnimationUpdate(),通过animation.getAnimatedValue()来获取值的变化,值为上边第四步的值,即结合了插值器、起始值和终值计算出来的值。

容器动画LayoutTransition

LayoutTransition mTransitioner=new LayoutTransition();
cotainerView.setLayoutTransition( mTransitioner );
ObjectAnimator animator = ObjectAnimator.ofFloat(null,"xxx").setDuration(mTransitioner.getDuration(LayoutTransition.APPEARING));
mTransitioner.setAnimator(LayoutTransition.APPEARING,animator);
//LayoutTransition.APPEARING为出现,另外还有DISAPEARINHG消失,CHANGE_APPEARING,CHANGE_DISAPPEARING别的项目从容器中消失或者出现而发生改变的项目的动画。

需要使用系统默认的ViewGroup布局改变的动画,只需要在设置android.animateLayoutchanges为true即可。

使用Interpolators

基本使用不再多说,系统给了很多具体的实现,可以结合起来自由使用。在此只分析其代码的实现,打开自定义的思路。
在插值器必须实现的接口TimeInterpolator里只有一个抽象方法 :

float getInterpolation(float input);
  • 接收参数 input,即之前所说的 elapsed fraction ,代表整个动画的执行过程。举例,一个时长2000ms的动画,在开始时参数input的值为0,执行500ms时,input=500/2000=0.25,并最终等于1。该值只与动画的执行时间有关,并匀速由0到1,与插值器等其他元素无关。
  • 返回值,即之前所说的 interpolated fraction ,实际的进度值。如线性插值器直接返回的input参数的值;而其他插值器都是经过各种计算实现的效果。保证初值和终值为0和1即可,该值可以大于1(超过目标进度),也可以小于0(小于开始位置)。

所以,在自定义插值器的时候,可以简单的实现TimeInterpolator,并实现其getInterpolation()方法,写上自己的逻辑即可。

使用TypeEvaluator

有相应的setEvaluator方法,系统有IntEvaluator、FloatEvaluator、ArgbEvaluator三种实现,最后一种用于color。高Api有其他具体实现此处暂且不提。
我们知道,通过AnimateUpdateListener我们得到的是属性当前具体的值,而不是0-1的小数。这个具体的值就是通过Evaluator来得到的。
注意:监听中,animation.getAnimatedValue()方法的返回值是object类型,也就是说可以返回任何我们想要的数值类型。首先,系统已经实现的Evaluator有int、float、argb,三种。通过分析,我们也可以自定义自己的Evaluator。
在Evaluator必须实现的接口TypeEvaluator里也是只有一个抽象方法:

public interface TypeEvaluator<T> {
    public T evaluate(float fraction, T startValue, T endValue);
}
  • 泛型,IntEvaluator的泛型是Integer,FloatEvaluator是Number,而ArgbEvaluator由于最后返回的是ARGB色,为4个int值,泛型为Object类型没有做特殊规定。我们可以规定Char、String、甚至是自己的Bean类如果有必要的话。
  • evaluate()方法,第一个参数为插值器getInterpolation()的返回值,即 interpolated fraction ,第二个参数为我们设定的初值,第三个参数为我们设定的终值。如我们设置ofInt(100,400),则对应的初值和终值即为100、400。
  • 返回值,Int和float都是(初值+fraction*(终值-初值)),很容易理解。而Argb也是如此,但因为有4个值,所以经过了稍微复杂的计算。我们可以自己根据我们的逻辑来设置。该值即为在update监听中的animation.getAnimatedValue()返回值。

需要在ofInt、ofFloat、ofArgb之外有更复杂的使用,不仅可以监听进度变化来手动做设置,也可以使用ofObject()传入自定义的TypeEvaluator以及适当的初值和终值,来使代码更为简练。

指定关键帧Keyframe

做动画的时候不用一帧一帧的话,而是在有规律可循的情况下,指定两帧,之间的就由软件完成,这两帧就称之为关键帧。

//Keyframe.ofXx(fraction,value),一般使用float,第一个值是插值器的getInterpolation()的返回值,第二个参数为动画在该时间点的状态值
Keyframe kf0 = Keyframe.ofFloat(0f, 0f);

//可以添加插值器,插值器作用于该帧和上一帧之间的时间,所以给第一帧设置插值器是没有用的。
Keyframe kf1 = Keyframe.ofFloat(.5f, 360f);
kf1.setInterpolator(XXX);

Keyframe kf2 = Keyframe.ofFloat(1f, 0f);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
ObjectAnimator rotationAnim = ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation)
rotationAnim.setDuration(5000ms);

rotationAnim.start();

ObjectAnimator的一些补充

可以看到如果想要使用ValueAnimator来实现补间动画的视觉效果,需要监听Update,在里面对我们的控件做相应设置,是比较麻烦的。所以谷歌在ValueAnimator的基础上派生出ObjectAnimator,该类重写了几个方法,举例说明它的原理。

ObjectAnimator animator = ObjectAnimator.ofFloat(tv, "scaleY", 0, 3, 1);

在该例中,之前所说的插值器、Evaluator计算最终获取一个值反映在update里,这些都不变,只是会增加一步:

  • 根据属性值拼装控件的set函数,此处为setScaleY()
  • 通过反射找到控件对应的set函数并将获得的值作为set函数的参数传入,此处为setScaleY(float);

注意:当我们只设定一个值,就相当于只设定了一个终值,这时ObjectAnimator会根据控件该属性的getXxx()方法来获取到一个初值。

使用ViewPropertyAnimator进行动画处理
先看一个用ObjectAnimator实现的多动画实例

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

再看PropertyValuesHolder来实现,这个东西起始就是把ofInt、ofFloat那一套的参数给封装起来。在ObjectAnimator内部也是使用的PropertyValuesHolder将数据封装后进行各种操作。

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();

但我们的重点是ViewPropertyAnimator。如果只有一两个属性需要动态更改,ObjectAnimator可以准确恰当的完成任务。但是如果需要几个属性同时做动画,或者你想要使用更方便的语法,ViewPropertyAnimator是很合适的。只需要View.animate()获取到一个实例,就可以链式调用多种方法了。

myView.animate().x(50f).y(100f);

它的优点在于:

  • 相对于同时进行的动画独立发出自己的重绘要求(invalidate calls),它会对其进行优化,可以减少很多不必要的重绘。
  • 专门针对View类,简洁的链式调用,通过View.animate()获取到该类实例后,你只需要告知View的什么属性需要变化,并设置to/by即可。加By意味着变化量,不加By结尾意味变化到。
原理示意图原理示意图

布局动画LayoutAnimation

主要有LayoutAnimation和GridLayoutAnimation。 不做详细分析。

在XML文件中实现需要在res/anim/xxx.xml中的根节点为layoutAnimation或者gridLayoutAnimation,并将该文件应用到ViewGroup的layoutAnimation标签中

  • animation标签指定动画元素
  • delay标签设置动画在上一个动画结束后多久执行,可用xx%,可指定具体值,单位ms。
  • animationOrder,normal,顺序;reverse,倒序;random,随机;

在Java代码中实现需要先获取一个LayoutAnimationController对象,也可以做上述设置,之后调用setLayoutAnimation(layoutAnimationController)即可。

需要实现自定义的效果可以继承LayoutAnimationController并实现其getTransformedIndex()方法。


切换动画

Activity

overridePendingTransition

使用overridePendingTransition(下一个Activity的进入动画,当前Activity的消失动画,不需要可传0),动画资源放在res/anim,须在startActivity()/finish()之后调用。

定义Application的style(AppTheme)

<<!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
        <item name="android:windowAnimationStyle">@style/activityAnim</item>
    </style>


<!-- 使用style方式定义activity切换动画 -->
    <style name="activityAnim">
        <item name="android:activityOpenEnterAnimation">@anim/slide_in_top</item>
        <item name="android:activityOpenExitAnimation">@anim/slide_in_top</item>
        <item name="android:activityCloseEnterAnimation">@anim/slide_in_top</item>
        <item name="android:activityCloseExitAnimation">@anim/slide_in_top</item>

    </style>

此外还有共享元素和转场动画

Fragment

android.app.Fragment使用属性动画(res/animator),而我们常用的v4包使用的是View Animation(res/anim)。除了默认的FragmentTransaction.setTransition()来实现标准的默认动画,还有另外两种常用方式来实现我们自定义的动画。

  • 通过在Fragment类里重写onCreateAnimator()或v4包里的onCreateAnimation(),此处根据返回值的不同也证明了上边所说的。要想对动画的执行添加监听,可以在这里实现。
  • 通过FragmentTransaction.setCustomAnimations(),设置:enter(进入),exit(退出),popEnter(入栈),popExit(出栈)。注意因为包的不同和Api版本的不同,方法咯有差异。

对于Fragment本身来说 也有一些方法:

  • fragment.setEnterTransition(),以及Exit、Reenter、Return。以及共享元素setSharedElementEnterTransition(),setSharedElementExitTransition()。这些方法的参数必须是android.transition.Transition类型。
  • fragment.setAllowEnterTransitionOverlap()/setAllowReturnTransitionOverlap(),参数boolean,设置是否等之前动画结束再执行下一个动画,不然可能会出现重影。

API使用

最简单的

myView.animate().x(50f).y(100f);

属性值

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);

//AnimatorSet有多种使用方法,灵活使用playTogether()、play()、with()、before()、after()来实现复杂的效果
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

麻烦点的

AnimatorSet set = (AnimatorSet) AnimatorInflater.loadAnimator(comtext,R.animator.property_animator);
set.setTarget(myObject);
set.start();

//使用playTogether()、play()、with()、before()、after()实现复杂的效果
AnimatorSet bouncer = new AnimatorSet();
bouncer.play(bounceAnim).before(squashAnim1);
bouncer.play(squashAnim1).with(squashAnim2);
bouncer.play(squashAnim1).with(stretchAnim1);
bouncer.play(squashAnim1).with(stretchAnim2);
bouncer.play(bounceBackAnim).after(stretchAnim2);

ValueAnimator fadeAnim = ObjectAnimator.ofFloat(newBall, "alpha", 1f, 0f);
fadeAnim.setDuration(250);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(bouncer).before(fadeAnim);
animatorSet.start();

xml文件方式

视觉动画建议放xml里,属性动画看情况。

  • 帧动画 res/drawable/xxx.xml,根节点<animation-list>
  • 补间动画 res/anim/xxx.xml,根节点<alpha>, <scale>, <translate>, <rotate>, or <set>,<set>可嵌套
  • 属性动画 res/animator/xxx.xml,根节点必须是<set>, <objectAnimator>, or <animator>,<set>可嵌套

特殊动画

揭露效果动画

显示需先设置可见

Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0, finalRadius);
myView.setVisibility(View.VISIBLE);
anim.start();

隐藏需监听动画结束,设置不可见

Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0);
anim.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        myView.setVisibility(View.INVISIBLE);
    }
});
anim.start();
水波纹动画

基础

  • 有界android:background="?android:attr/selectableItemBackground">
  • 无界android:background="?attr/selectableItemBackgroundBorderless>

对背景有特殊要求

/drawable-v21/button.xml

ripple和指定色?android:colorControlHighlight包裹,colorControlHighlight默认为灰色,可在Theme中重新设定

<ripple xmlns:android="http://schemas.android.com/apk/res/android"
    android:color="?android:colorControlHighlight">
    <item android:drawable="@drawable/button_normal" />
</ripple>

相关文章

网友评论

      本文标题:Android中的动画

      本文链接:https://www.haomeiwen.com/subject/cmtksttx.html