Android幀、補間、屬性動畫

1. 分類

動畫必不可少,因為它可以讓交互更加流暢、自然陕贮,增強用戶滿意度。在Android中動畫主要有以下4類:

  • 幀動畫
  • 補間動畫
  • 屬性動畫(Android3.0)
  • VectorDrawable(Android5.0)(后期會單獨更新博客講解)

2. 原理

動畫實際上就是在指定的時間段內(nèi)持續(xù)地修改某個屬性的值潘飘,使得該值在在指定的范圍之內(nèi)平滑的過渡肮之。

動畫40ms.png

看圖可知:動畫就是在某個時間點根據(jù)一定的計算方式計算出屬性的取值,并且設(shè)置給目標(biāo)對象卜录。在動畫的執(zhí)行周期內(nèi)持續(xù)執(zhí)行這個過程戈擒,形成動畫的效果。

3. 幀動畫(Frame動畫)

  • 幀動畫是一系列圖片按照一定的順序展示的過程艰毒。它的原理是在一定的時間段內(nèi)切換多張有細微差異的圖片從而達到動畫的效果筐高。

  • 幀動畫可以在xml中定義,也可以編碼實現(xiàn)丑瞧。如果再xml文件中定義的話柑土,可以放置在res下的anim或drawable目錄中,文件名可以作為資源id在代碼中引用绊汹,如果完全編碼實現(xiàn)稽屏,需要使用到AnimationDarawable對象。

  • 在xml中定義幀動畫時灸促,<animation-list>元素必須為根元素诫欠,它可以包含一或多個<item>元素。

  • android:onshot如果定義為true時浴栽,此動畫只會執(zhí)行一次,如果為false則一直循環(huán)轿偎。

  • <item>元素代表一幀動畫典鸡,android:drawable指定此幀動畫所對應(yīng)的圖片資源,android:duration代表此幀持續(xù)的時間坏晦,單位為毫秒萝玷。

3.1 栗子:首先在XML中定義名為test_drawable.xml的文件,這里就不再講解在代碼中定義幀動畫了昆婿,推薦XML中定義球碉,更容易維護,減少代碼耦合度:

    <?xml version="1.0" encoding="utf-8"?>
    <animation-list
    
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:oneshot="true">
        <item
            android:drawable="@drawable/cancel"
            android:duration="1000"></item>
        <item
            android:drawable="@drawable/hiss"
            android:duration="1000"></item>
        <item
            android:drawable="@drawable/night"
            android:duration="1000"></item>
        <item
            android:drawable="@drawable/ok"
            android:duration="1000"></item>
    
    </animation-list>
  • 然后把動畫設(shè)置給某個View:例如:將該動畫設(shè)置為某個ImageView的背景:
    <ImageView
        android:layout_centerInParent="true"
        android:id="@+id/iv_anim"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="@drawable/test_drawable"
        />
  • 然后還需要通過Java代碼啟動該動畫:
    ((AnimationDrawable) mImageView.getBackground()).start();

4. 補間動畫(tween動畫或者View動畫)

  • 補間動畫就是操作某個控件讓其展示出旋轉(zhuǎn)仓蛆、漸變睁冬、移動、縮放的一種轉(zhuǎn)換過程看疙,這成為補間動畫豆拨。

  • 可以在XML中定義直奋,也可以編碼實現(xiàn),后者就不講解了施禾,推薦XML定義脚线。

  • 在XML中定義并放置于/res/anim目錄下,文件名可以作為資源的id被引用弥搞;如果編碼實現(xiàn)邮绿,需要使用到Animation對象或者AnimationSet。

  • Animation對象有四個:AlphaAnimation攀例、RotateAnimation斯碌、TranslateAnimation、ScaleAnimation四種動畫方式肛度,這里只講解AnimationSet動畫集合傻唾。

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
     android:interpolator="@android:anim/cycle_interpolator"
     android:shareInterpolator="true">

    <alpha
        android:duration="5000"
        android:fromAlpha="1.0"
        android:toAlpha="0.0"></alpha>
    <scale
        android:duration="5000"
        android:fromXScale="0.2"
        android:fromYScale="0.2"
        android:pivotX="50%"
        android:pivotY="50%"
        android:toXScale="1.5"
        android:toYScale="1.5"></scale>
    <translate
        android:duration="5000"
        android:fromXDelta="0"
        android:fromYDelta="0"
        android:toXDelta="0"
        android:toYDelta="500"></translate>
    <rotate
        android:duration="5000"
        android:fromDegrees="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:repeatCount="-1"
        android:repeatMode="restart"
        android:startOffset="0"
        android:toDegrees="360">
    </rotate>

</set>
    AnimationSet animationSet = (AnimationSet) AnimationUtils.loadAnimation(this, R.anim.test_tween);
    mImageView.startAnimation(animationSet);

說明:

  1. <set>是一個動畫容器,管理多個動畫的數(shù)組承耿,與之相對應(yīng)的Java對象是AnimationSet冠骄。

  2. 它有兩個屬性:android:interpolator代表的是一個插值器的資源,可以引用系統(tǒng)自帶的插值器資源加袋,也可以自定義插值器資源凛辣,默認值死活勻速插值器。android:shareInterpolator代表<set>里面的多個動畫是否要共享插值器职烧,默認值是true扁誓,即共享插值器,如果是false蚀之,那么<set>插值器就不再起作用蝗敢,我們要在每個動畫中加入插值器。

插值器.png
  1. alpha:透明度 淡入淡出效果 fromAlpha:開始透明度 toAlpha結(jié)尾透明度 浮點值足删。

  2. scale:縮放 調(diào)整空間尺寸 fromXScale:起始的X方向上相對自身的縮放比例:比如1.0代表無變化 0.5代表起始時縮小一倍 2.0代表放大一倍寿谴。toXScale fromYScale toYScale 不再講解 pivotX代表縮放的中軸點X坐標(biāo),pivotY代表縮放的中軸點Y坐標(biāo)失受。 如果想以中軸點為圖像的中心讶泰,可以把兩個屬性值定義為0.5或者50%。 浮點值 拂到。

  3. translate:移動 水平痪署、垂直的位移 fromXDelta代表起始X方向的位置 fromYDelta代表起始Y方向的位置 toXDelta代表結(jié)尾X方向的位置 toYDelta代表結(jié)尾Y方向的位置。 3種表示方式:浮點數(shù)(相對自身原始位置的像素值)兄旬、num%(代表相對于自己的百分比)狼犯、num%p(代表相對于父類控件的百分比) 以num%為例:toXDelta定義為100% 就表示在X方向上移動自己的1倍距離。

  4. rotate: 旋轉(zhuǎn)動畫 fromDegrees起始角度 toDegrees結(jié)尾的角度 pivotX 旋轉(zhuǎn)中心的X坐標(biāo) pivotY旋轉(zhuǎn)中心的Y坐標(biāo) 坐標(biāo)有3種表示方式:數(shù)字方式代表相對于自身左邊緣的像素值,num%方式代表相對于自身左邊緣或頂邊緣的百分比辜王,num%p代表相對于父容器的左邊緣或頂邊緣的百分比劈狐。

注意:

  1. 補間動畫只能運用在View上,功能較為局限:比如旋轉(zhuǎn)只能在x呐馆、y軸肥缔,不能在z軸。
  2. 另外補間動畫的View的位置汹来,系統(tǒng)認定始終在起始位置续膳。

5. 屬性動畫

  • 補間動畫的最大缺陷就是動畫改變的只是顯示,但View位置并沒有發(fā)生變化收班,View移動后不能響應(yīng)點擊事件坟岔。

  • 屬性動畫解決了補間動畫的缺陷,其經(jīng)常使用的兩個類是ObjectAnimator和AnimatorSet摔桦。

  • 屬性動畫通過調(diào)用屬性get社付、set方法真實地控制一個View的屬性值。

5.1 ObjectAnimator

  • ObjectAnimator是屬性動畫最重要的類:創(chuàng)建一個ObjectAnimator只需通過靜態(tài)工廠類直接返還一個ObjectAnimator對象邻耕。

  • 參數(shù)包括一個對象和對象的屬性名字鸥咖,但這個屬性必須有g(shù)et和set方法,其內(nèi)部會通過Java發(fā)射機制調(diào)用set方法修改對象的屬性值兄世。

    ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mImageView,"translationX",200);
    objectAnimator.setDuration(3000);
    objectAnimator.start();

說明:通過ObjectAnimator的靜態(tài)方法啼辣,創(chuàng)建一個ObjectAnimator對象,查看ObjectAnimator.java的靜態(tài)方法ofFloat():

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
        ObjectAnimator anim = new ObjectAnimator(target, propertyName);
        anim.setFloatValues(values);
        return anim;
    }

說明:第一個參數(shù)是Object的目標(biāo)View御滩;第二個參數(shù)是要操作的屬性鸥拧;最后一個參數(shù)是一個可變的float類型的數(shù)組,需要傳進去該屬性變化的取值過程削解,這里設(shè)置了一個參數(shù)富弦,變化到200.

  • 屬性動畫也可以設(shè)置市場、插值器等屬性钠绍。下面是一些常用的屬性動畫的屬性值:
translationX 和 translationY : 用來沿著X軸或者Y軸進行平移舆声。
rotation、rotationX柳爽、rotationY:用來圍繞View的支點進行旋轉(zhuǎn)。
PrivotX 和 PrivotY :控制VIew對象的支點位置碱屁,圍繞這個支點進行旋轉(zhuǎn)和縮放變換處理磷脯。默認該支點位置是View對象的中心點。
alpha :透明度娩脾,默認是1(不透明)赵誓,0代表完全透明
x和y: 描述View對象在其容器中的最終位置。
  • 需要注意ObjectAnimator使用時,要操作的屬性必須有set和get方法俩功,不然失效幻枉。當(dāng)然可以自定義一個屬性類或者包裝類來間接地給這個屬性增加get和set方法:
    private static class MyView {
    
        private View mTarget;

        private MyView(View mTarget) {
            this.mTarget = mTarget;
        }

        public int getWidth() {
            return mTarget.getLayoutParams().width;
        }

        public void setWidth(int width) {
            mTarget.getLayoutParams().width = width;
            mTarget.requestLayout();
        }
    }
    MyView myView = new MyView(mImageView);
    ObjectAnimator.ofInt(myView, "width", 5000).setDuration(5000).start();
  • ObjectAnimator 動畫的監(jiān)聽
objectAnimator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {

            }

            @Override
            public void onAnimationEnd(Animator animator) {

            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });

說明:大部分的時候我們只關(guān)心onAnimationEnd事件,Android也提供了AnimatorListenerAdapter來讓我們選擇必要的事件進行監(jiān)聽诡蜓。

 objectAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
            }
        });

5.2 ValueAnimator

  • ValueAnimator是在一定的時間段內(nèi)不斷地修改對象的某個屬性值熬甫。

  • 我們只需要將屬性的取值范圍、運行時長提供給ValueAnimator蔓罚,那么它就會自動幫我們計算屬性值在各個動畫運行時段的取值椿肩,這些值會按照一定的計算方式來實現(xiàn)平滑過渡。

  • 此外:ValueAnimator還負責(zé)管理動畫的播放次數(shù)豺谈、播放模式郑象、以及對動畫設(shè)置監(jiān)聽器等。

  • ValueAnimator 不僅功能強大茬末,API設(shè)計也非常簡單:通過ofFloat厂榛、ofInt等靜態(tài)工廠函數(shù)構(gòu)建ValueAnimator:例:下面就是我們將數(shù)值從1.0 過渡到0.0的動畫:

    //ValueAnimator
    private void startValueAnimation() {
        final ValueAnimator animator = ValueAnimator.ofFloat(1.0f, 0.0f);
        animator.setDuration(5000);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                Float newValue = (Float) valueAnimator.getAnimatedValue();
                Log.e("","startValueAnimation newValue :"+newValue);
                mImageView.setAlpha(newValue);
            }
        });
        animator.start();
    }

說明:

  1. ValueAnimator不提供任何動畫效果,它更像一個數(shù)值發(fā)生器丽惭,用來產(chǎn)生一定規(guī)律的數(shù)字击奶,從而讓調(diào)用者控制動畫的實現(xiàn)過程。
  2. 通常:在ValueAnimator的AnimatorUpdateListener中監(jiān)聽數(shù)值的變化吐根,從而完成動畫的變化正歼。

強調(diào):

  1. 啟動動畫后,每次更新屬性值時就會調(diào)用AnimatorUpdate函數(shù)拷橘,在這里獲取新的屬性值局义。
  2. 這里并沒有將這個值運動到具體的對象上,它只操作屬性值本身冗疮,這個值不屬于某個具體的對象萄唇,所以它的優(yōu)勢在于可以運用到任意的對象上。

5.3 AnimatorSet

  • 先看代碼:
    ObjectAnimator animator1 = ObjectAnimator.ofFloat(mImageView, "translationX", 0.0f, 200.0f, 0f);
    ObjectAnimator animator2 = ObjectAnimator.ofFloat(mImageView,"scaleX",1.0f,2.0f);
    ObjectAnimator animator3 = ObjectAnimator.ofFloat(mImageView,"rotationX",0.0F,90.0F,0.0F);

    AnimatorSet set = new AnimatorSet();
    set.setDuration(1000);
    set.play(animator1).with(animator2).after(animator3);

說明:

  1. 首先創(chuàng)建了3個ObjectAnimator术幔,分別是animator1 animator2 animator3另萤,然后創(chuàng)建了AnimatorSet。
  2. 先執(zhí)行animator3诅挑,然后同時執(zhí)行animator1和animator2四敞。
  3. 也可以調(diào)用set.playTogether(animator1,animator2)來使者兩種動畫同時執(zhí)行。
  4. AnimatorSet類提供了一個play()方法拔妥,我們將Animator對象(ValueAnimator或ObjectAnimator)忿危,將會返回一個AnimatorSet.Builder實例,我們看看play方法的源碼:
    public Builder play(Animator anim) {
        if (anim != null) {
            mNeedsSort = true;
            return new Builder(anim);
        }
    return null;
    }
  1. 我們可以看到play方法中創(chuàng)建了一個AnimatorSet.Builder類没龙,這個Builder類是AnimatorSet的內(nèi)部類铺厨,我們看看Builder類中有什么:
public class Builder {

        private Node mCurrentNode;

        Builder(Animator anim) {
            mCurrentNode = mNodeMap.get(anim);
            if (mCurrentNode == null) {
                mCurrentNode = new Node(anim);
                mNodeMap.put(anim, mCurrentNode);
                mNodes.add(mCurrentNode);
            }
        }

        public Builder with(Animator anim) {
            Node node = mNodeMap.get(anim);
            if (node == null) {
                node = new Node(anim);
                mNodeMap.put(anim, node);
                mNodes.add(node);
            }
            Dependency dependency = new Dependency(mCurrentNode, Dependency.WITH);
            node.addDependency(dependency);
            return this;
        }

        public Builder before(Animator anim) {
            mReversible = false;
            Node node = mNodeMap.get(anim);
            if (node == null) {
                node = new Node(anim);
                mNodeMap.put(anim, node);
                mNodes.add(node);
            }
            Dependency dependency = new Dependency(mCurrentNode, Dependency.AFTER);
            node.addDependency(dependency);
            return this;
        }

        public Builder after(Animator anim) {
            mReversible = false;
            Node node = mNodeMap.get(anim);
            if (node == null) {
                node = new Node(anim);
                mNodeMap.put(anim, node);
                mNodes.add(node);
            }
            Dependency dependency = new Dependency(node, Dependency.AFTER);
            mCurrentNode.addDependency(dependency);
            return this;
        }

        public Builder after(long delay) {
            // setup dummy ValueAnimator just to run the clock
            ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
            anim.setDuration(delay);
            after(anim);
            return this;
        }

    }
  1. 從Builder源碼中我們可以看出來它采用了建造者模式缎玫,每次調(diào)用方法時都返回Builder自身用于繼續(xù)構(gòu)建。AnimatorSet.Builder中包括以下4個方法:

after(Animator anim):將現(xiàn)有的動畫插入到傳入的動畫之后執(zhí)行解滓。

after(long delay):將現(xiàn)有動畫延遲指定毫秒之后執(zhí)行赃磨。

before(Animator anim):將現(xiàn)有動畫插入到傳入的動畫之前執(zhí)行。

with(Animator anim):將現(xiàn)有的動畫和傳入的動畫同時執(zhí)行洼裤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末邻辉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子逸邦,更是在濱河造成了極大的恐慌恩沛,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缕减,死亡現(xiàn)場離奇詭異雷客,居然都是意外死亡,警方通過查閱死者的電腦和手機桥狡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門搅裙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人裹芝,你說我怎么就攤上這事部逮。” “怎么了嫂易?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵兄朋,是天一觀的道長。 經(jīng)常有香客問我怜械,道長颅和,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任缕允,我火速辦了婚禮峡扩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘障本。我一直安慰自己教届,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布驾霜。 她就那樣靜靜地躺著案训,像睡著了一般。 火紅的嫁衣襯著肌膚如雪粪糙。 梳的紋絲不亂的頭發(fā)上萤衰,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天,我揣著相機與錄音猜旬,去河邊找鬼脆栋。 笑死,一個胖子當(dāng)著我的面吹牛洒擦,可吹牛的內(nèi)容都是我干的椿争。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼熟嫩,長吁一口氣:“原來是場噩夢啊……” “哼秦踪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起掸茅,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤椅邓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后昧狮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體景馁,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年逗鸣,在試婚紗的時候發(fā)現(xiàn)自己被綠了合住。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡撒璧,死狀恐怖透葛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情卿樱,我是刑警寧澤僚害,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站繁调,受9級特大地震影響萨蚕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜涉馁,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一门岔、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧烤送,春花似錦寒随、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至试和,卻和暖如春讯泣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阅悍。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工好渠, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留昨稼,地道東北人。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓拳锚,卻偏偏與公主長得像假栓,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子霍掺,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

推薦閱讀更多精彩內(nèi)容