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已經滿足你的需要了,那就用吧。
內容摘要
- 視覺動畫的使用。(幀動畫、補間動畫)
- 屬性動畫的使用及原理。(涉及插值器、估值器、關鍵幀等)
- 布局動畫。(不常用,只簡單提一下)
- 轉場動畫。(Activity和Fragment)
- 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和場景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>