關于Android的幾種基本動畫

一直以來各種動畫效果也只是零碎地在需要用到的時候復制粘貼,或者直接自己Draw一遍,并且記下一些零碎的代碼段。然而最近比賽項目又碰到了需要各種動畫過場的情況,想想這問題也是積攢已久了,于是準備認真的學習一下。

? 首先,Android里的動畫分為三種:PropertyAnimation(屬性動畫)ViewAnimation(視圖動畫)DrawableAnimation(幀動畫,By the way,官網中翻是可繪制動畫,感覺就很不對勁 -_-)。其中屬性動畫在API 11之后加入,它可以對所有對象(包括未渲染到屏幕的對象)添加動畫效果,拓展性強且特點多樣,是官方推薦的動畫實現(xiàn)手段;視圖動畫則是比較老的動畫實現(xiàn)手段,其僅對View起效,并且只有視覺上的變化,而不實際改變View的位置或其他真實屬性;可繪制動畫則適用于借助Drawable資源實現(xiàn)的情景,通過切換幀實現(xiàn)動畫效果,比如常見的進度條旋轉與游戲中人物走動效果等。下面依次說一下各個動畫的用法。

視圖動畫

? 首先講講ViewAnimation,為什么不是PropertyAnimation?大概是因為它的文檔比較短.(PropertyAnimation:明明是我先,出現(xiàn)在正文也好,官方推薦也好...)。記得當年年輕的我某一日終于忍受不了各種View硬生生直挺挺地在面前排布時,怒而百度,第一個搜出來的教程就是關于它的,然后我就實現(xiàn)了Coding以來第一個動畫:一個很蠢的點擊按鈕旋轉滾動拉出一個EditText的動畫。ViewAnimation實際上是為了在View上實現(xiàn)TweenedAnimation,即補間動畫(這個名詞在各種教程技術文里出現(xiàn)的頻率明顯也更高)。它的實現(xiàn)效果就是簡單地設置View進行自定義起始點和結束點的平移、旋轉、大小和透明度變換等等,能夠用XML與Java代碼兩種方式實現(xiàn),官方推薦是用XML文件(位于res/anim下)實現(xiàn),這樣會使代碼結構看起來更清晰。下面是XML代碼的各種屬性和格式:

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@[package:]anim/interpolator_resource"http://插值器類型,可以設置在單獨的動畫效果中
    android:shareInterpolator=["true" | "false"] >
    <alpha//設置不透明度,值為0f-1.0f
        android:fromAlpha="float"
        android:toAlpha="float" />
    <scale//設置大小
        //縮放比例,1.0f為無變化
        android:fromXScale="float"
        android:toXScale="float"
        android:fromYScale="float"
        android:toYScale="float"
        //縮放中心
        android:pivotX="float" 
        android:pivotY="float" />
    <translate//設置平移
        //以下值的表示規(guī)則為:普通float無后綴數字,如“50”,表示絕對偏移值;-100 - 100以“%”為后綴的值,如“50%”,表示相對自身位置的偏移值;-100 - 100以“%p”為后綴值,如“50%p”,表示相對父布局的偏移值。
        android:fromXDelta="float"
        android:toXDelta="float"
        android:fromYDelta="float"
        android:toYDelta="float" />
    <rotate//設置旋轉
        //旋轉角度,單位為度,正方向為順時針
        android:fromDegrees="float"
        android:toDegrees="float"
        //旋轉中心,與上面偏移值的表示規(guī)則相同,分別以無后綴、“%”與“%p”分別表示其與View邊緣(左和上)的絕對值,與View邊緣的相對百分比(50%,50%則為正中心),與父布局邊緣的相對百分比。
        android:pivotX="float"
        android:pivotY="float" />
    <set>
        ...
    </set>
</set>

另外它們都有繼承自Animation的通用屬性:

屬性名 說明 參數類型
android:detachWallpaper 窗口動畫的特殊選項,當窗口處于壁紙之上時,選擇是否讓壁紙與其一起運動 boolean
android:duration 動畫的執(zhí)行時間,以毫秒為單位,默認為300ms integer
android:fillAfter 為true時,動畫變換效果將保持。(僅在set中設置有效) boolean
android:fillBefore 為true時,動畫運行結束后停留在第一幀。默認為true。 boolean
android:fillEnabled 設為true時 fiilBefore才有效,為false且動畫未設置給View時,讓fillAfter為true boolean
android:interpolator 設置相應插值器。 refrence
android:repeatCount 設置動畫重復次數 integer
android:repeatMode 動畫播完一遍再重復時的行為模式,有restart和reverse兩種模式。 string
android:startOffset 動畫開始前的延遲時間,以毫秒為單位。 integer
android:zAdjustment 允許調整在動畫持續(xù)時間內動畫的內容的Z排序。有bottom(ffffff)、normal(0)與top(1)三種模式,默認為normal(保持現(xiàn)有順序)。 integer

關于上面的interpolator,有如下系統(tǒng)默認調用:

名稱 資源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_interpolator 重復動畫指定的循環(huán)次數。 變化率遵循正弦曲線。(也就是有正有負,可以制造回彈效果)
DecelerateInterpolator @android:anim/decelerate_interpolator 減速變化
LinearInterpolator @android:anim/linear_interpolator 線性變化
OvershootInterpolator @android:anim/overshoot_interpolator 減速增長然后越界,回歸原點。

上面的插值變化實際表示各種速度變化曲線,可以參考blog中的圖參考圖例

在寫好動畫相關函數后可以用Java代碼調用:

Animation anim = AnimationUtils.loadAnimate(context,R.anim.demo);
//方式一
view.startAnimation(anim);//立即開始動畫,考慮上面的startOffset屬性
//方式二
anim.setStartTime(time)//設置動畫開始時間,參數是格林威治時間至今的毫秒數
view.setAnimation(anim);//設置動畫,當其動畫時間到達時則自然開始動畫。

另外,需要注意的地方有:一、set內的所有動畫默認都是同時運行的,所以當需要動畫有先后順序時需要用startOffset來進行延遲播放。二、位移或者放大類動畫并不會拓展View本身的空間,當其越界時,便不會顯示在界面上。三、ViewAnimation并不會改變View的真實屬性,當其由于動畫效果不處于本身的位置時,其事實上的點擊區(qū)域還是在原地。(所以在知道屬性動畫之前,我一直都是用兩個不同時出現(xiàn)的按鈕實現(xiàn)的一開始提到的那個滾動出EditText的效果,真的很蠢。。)

關于屬性動畫

? 由于視圖動畫的純“花架子”式動畫效果,在更多需要和動畫界面交互的場景中,屬性動畫后來的上位也就不難理解了。它的構成部分主要有這么幾項:ValueAnimator、 TimeInterpolator和TypeEvaluator,下面先進行一下概述。

? ValueAnimator是整個屬性動畫系統(tǒng)的核心基類,其中包含了動畫的起止時間,屬性變化規(guī)律和監(jiān)聽等功能,負責維護整個動畫的運行流程,其下子類ObjectAnimator是最常用到的屬性動畫實現(xiàn)類,可以為任意對象加載動畫效果,另外還有AnimatorSet,其類似XML文件內的<set>標簽,表示一組動畫。

? TimeInterpolatorTypeEvaluator事實上也不是什么新東西了,在ViewAnimation中也有相同的機制,它們的運行機制簡單來說是這樣的:當Animator設定了動畫的起止時間并執(zhí)行start()之后,之后動畫的每一幀都會根據開始時間與當前時間的差與總時間的比值通過TimeInterpolator得到相應的0-1.0間的值,然后TypeEvaluator得到這個值并由此估算出此時對象應具有的屬性,并更新其屬性值,如此循環(huán)直至動畫結束。所以這一對其實就是控制動畫播放速度(視覺上的,總時間是一定的)的工具。

接下來就是各種例子嘍,首先是ValueAnimator:

它的初始化方法有幾種,如下:

ValueAnimator animator = ValueAnimator.ofInt(startValue,endValue);
ValueAnimator animator = ValueAnimator.ofFloat(startValue,endValue);
ValueAnimator animator = ValueAnimator.ofArgb(startValue,endValue);//必須為8位ARGB值,且必須為兩個值
ValueAnimator animator = ValueAnimator.ofObject(evaluator,startValue,endValue);//這里的兩個value都是Object類型的。
Keyframe kf0 = Keyframe.ofFloat(time1, value1);//關鍵幀,記錄了在指定的time時其應有的value,可以定義自己的Interceptor
Keyframe kf1 = Keyframe.ofFloat(time2, value2);
Keyframe kf2 = Keyframe.ofFloat(time3, value3);
PropertyValuesHolder pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2);
//"rotation"在ValueAnimator中僅僅是一個屬性的名字,只在在getAnimationValue(name)時可能會使用到,但在ObjectAnimator中就代表目標對象的屬性
ValueAnimator rotationAnim = ValueAnimator.ofPropertyValuesHolder(pvhRotation)

//以上所有startValue 和 endValue都是可變參數,只填一個的話會默認從0開始,然后將填入的值為終點值,當然填多個的話也沒有任何問題,甚至可以填入一串值實現(xiàn)來回變化屬性的效果
//ofInt,ofFloat 和ofArgb只是決定了TimeInterpolator計算得到的參數類型,并不影響最終結果,影響結果的永遠是Evaluator

然而ValueAnimator并沒有預設的屬性動畫效果,官網上給的例子是通過addUpdateListener監(jiān)聽來自己更改View屬性的(當然所有對象都一樣,反正都是自定義),下面是一個比較完整的一個例子:

ValueAnimator animator = ValueAnimator.ofObject(new TypeEvaluator() {
            @Override
            public Object evaluate(float fraction, Object startValue, Object endValue) {
                return fraction*(int)endValue; //這里的算法可以隨便定義,甚至可以完全不管       TimeInterpolator傳來的fraction數值,返回值就是getAnimatedValue能拿到的值。
            }
        },0, 100);
        animator.setDuration(1000);
        animator.setInterpolator(new CycleInterpolator(5));//隨便設置了一個插值器,上面有說明
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //注意,動畫的每一幀耗費的時間是不確定的,所以這里得到的值在線性插值器條件下也只是近似線性
                view.setTranslationX((float)animation.getAnimatedValue());
            }
        });
        animator.start();//即時開始動畫,也可以用setStartDelay進行一下延時

為了簡化ValueAnimator的使用(畢竟老是得寫匿名類監(jiān)聽還是挺占地方的),ObjectAnimator便站了出來,它的使用如下:

ObjectAnimator animator = ObjectAnimator.ofFloat(view,"scaleX",0.1f);
animator.setDuration(1000);
animator.start();
//大部分方法繼承自ValueAnimator,就不寫了

需要注意的是,ObjectAnimator的工廠方法第一個參數與ValueAnimator 不同,它需要傳入一個Object(可以認為是一個Java Bean)以及其相關屬性,在之后的屬性更新操作中,會通過反射調用Object的setter和getter方法來更改屬性,要求相關Object類中必須有setter和getter方法(如屬性字段size,需要在Object類中定義setSize和getSize方法,方法參數與返回值必須與傳入數據類型相同),若無法控制Object的方法,則要做一個符合要求的包裝類。對于View,除了上面的scaleX,還有幾個常用的預定義屬性:translationX和translationY,rotation(繞中心在水平面內順時針旋轉) rotationX(繞X軸在立體面內旋轉)和rotationY(繞Y軸在立體面內旋轉),pivotX和pivotY(設置縮放與平移的中心,默認為View中心),alpha以及backgroundColor等View屬性,Android實際上對其也做了一層封裝,即ViewPropertyAnimator類:

view.animate().scaleX(0.5f).scaleY(0.5f).duration(1000).start();//默認以中心縮小一倍
view.animate().translationY(200).duration(1000).start();//向下移動200px
view.animate().alpha(0f).duration(1000).start();//從當前透明度變至完全透明
//注意,這里的所有動畫都是有絕對的默認中心的。而當運行過前面的動畫后,默認中心仍不會變。如translationY(200)后回到原位置的方式并不是translationY(-200),而是translationY(0)

關于AnimatorSet主要是幾個動畫調度方法:

AnimatorSet set = new AnimatorSet();
set.playSequentially(anim1,anim2,anim3);//在start后順序播放參數列表內的動畫
set.playTogether(anim1,anim2,anim3);//在start后同時播放參數列表內的動畫
//AnimatorSet.Builder調用
set.play(anim1).with(anim2);//start后同時播放動畫1和動畫2
set.play(anim2).before(anim3);//在播放動畫2之后播放動畫3
set.play(anim4).after(anim3);//在播放動畫3之后播放動畫4
set.play(anim4).after(1000);//在start后1秒播放動畫4
set.start();
//注意,上面的鏈式調用相鄰鏈之間是并列關系,如set.play(anim1).before(anim2).before(anim3)播放順序為動畫1先播放,動畫2和動畫3同時播放

其實如果僅是同時運行的話,用上面出現(xiàn)過的PropertyValuesHolder也能實現(xiàn)與AnimatorSet相似的效果:

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();//這里的工廠方法仍然是可變參數的方法,所以可以添加多個動畫同時運行

與ViewAnimation相同,PropertyAnimation也能通過XML文件定義,文件存放在res/animator目錄下以跟原來的animation區(qū)分,其對應上面三個類的標簽分別為<animator> <objectAnimator>和<set>,其相關定義如下:

<set
  android:ordering=["together" | "sequentially"]> //共同運行(默認)和順序執(zhí)行

    <objectAnimator
        android:propertyName="string" //屬性名,如alpha,background等屬性
        android:duration="int"  //運行時長
        android:valueFrom="float | int | color"  //起始值
        android:valueTo="float | int | color" //終點值
        android:startOffset="int" //起始時間延遲
        android:repeatCount="int"  //重復次數,-1為無限重復,0為不重復
        android:repeatMode=["repeat" | "reverse"] //重復模式,順序或倒序,僅在repeatCount為正數或-1時起效
        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>

在定義了XML后,在Java代碼中調用:

//調用ValueAnimator
ValueAnimator animator = (ValueAnimator)AnimatorInflater(context,R.animator.anim);
animator.addUpdateListener(mListener);//在監(jiān)聽里更新屬性
animator.start();
//調用ObjectAnimator
ObjectAnimator animator = (ObjectAnimator)AnimatorInflater(context,R.animaror.anim);
animator.setTarget(view);//設置作用對象,此時該對象必須含有propertyName屬性以及其setter getter方法
animator.start();
//調用AnimatorSet
AnimatorSet set = (AnimatorSet)AnimatorInflater(context,R.animtor.anim);
set.setTarget(view);//這里主要是set里的動畫為ObjectAnimator的情況,事實上將ValueAnimator放在set里是沒有意義的,因為set中沒有相關的更新回調方法,沒有辦法更新ValueAnimator指定的屬性
set.start();

至此屬性動畫的基本使用都說完了。不過上面寫到的動畫效果都是需要人為調用的,事實上Android還提供了一些在特定情景下自動調用的動畫效果,這就是 以LayoutTransition表現(xiàn)的布局動畫()。

添加布局動畫的方式很簡單,只要在XML文件中的某個ViewGroup標簽下加上一行:

android:animateLayoutChanges="true"

則當布局發(fā)生變化(一般是add、remove、setVisibility等情況下)時,就會有默認的控件fade或者上滑等效果。在源碼中可以看到,當這個值為true時,ViewGroup便設置了一個LayoutTransition:

case R.styleable.ViewGroup_animateLayoutChanges:
                    boolean animateLayoutChanges = a.getBoolean(attr, false);
                    if (animateLayoutChanges) {
                        setLayoutTransition(new LayoutTransition());
                    }
                    break;

同樣地,我們也可以在代碼中通過設置LayoutTransition的方式來設置布局動畫,并且LayoutTransition的動畫效果可以自定義。從官方文檔中可以看到,LayoutTransition的動畫分為四種情景:

  • APPEARING -View出現(xiàn)在布局中的動畫標志。

  • CHANGE_APPEARING -由于新View出現(xiàn)在布局中而造成其他View改變的動畫。

  • DISAPPEARING - View消失在布局中時的動畫標志。

  • CHANGE_DISAPPEARING - 由于View消失在布局中而造成其他View改變的動畫。

    ?

其設置方法如下:

transition.setAnimator(LayoutTransition.APPEARING,appearingAnim);
transition.setAnimator(LayoutTransition.DISAPPEARING,disappearingAnim);
transition.setAnimator(LayoutTransition.CHANGE_APPEARING,changingAppearingAnim);
transition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING,changingDisappearingAnim);
//上面的參數都是普通的ObjectAnimator或者ValueAnimator。

關于幀動畫

相對上面兩種動畫來說,幀動畫真的不能再簡單了。其動畫實現(xiàn)原理不同于上面的View局部更新重繪,而是簡單粗暴地一幀幀切換已有的圖片,它的XML文件直接放在res/drawable文件夾下,結構如:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot=["true" | "false"] > //只播放一次或者循環(huán)播放
    <item
        android:drawable="@[package:]drawable/drawable_resource_name" //準備的圖片
        android:duration="integer" /> //該幀播放時長
    ...
</animation-list>

在Java中使用時也就幾行代碼:

ImageView iv = (ImageView) findViewById(R.id.iv);
iv.setBackgroundResource(R.drawable.anim);

animation = (AnimationDrawable) iv.getBackground();
rocketAnimation.start();

就這么簡單,但是需要注意的是,不能在onCreate中調用start()方法,官方解釋是此時AnimationDrawable對象還未加載到窗口上,故推薦有這個需求是通過在onWindowFocusChanged()中開始動畫,此時表示Android已經將焦點聚集在窗口上,即動畫已經加載完畢。

最后

各種翻文檔后終于是搞定了這篇其實并沒有多高深的筆記,里面有些東西被省略了,比如動畫狀態(tài)的監(jiān)聽,不過日常大概也不太會被這種看字面就能理解的東西難住。還有挺重要的一點是layoutAnimation和Activity Fragment等整體組件切換的動畫,某些是5.0以后的效果,以后大概會補上??傊院髮懶┬赢嫷氩黄饋韺傩詴r終于能告別百度和忍受那些糟糕的界面了,開心。

如果上面的內容有任何錯誤或不足,歡迎各種交流指導,鞠躬。
個人Github主頁:Lazxy

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容