Android 動畫

動畫基礎概念

動畫分類

Android 中動畫分為兩種蓝翰,一種是 Tween 動畫旁壮、還有一種是 Frame 動畫绩衷。

補間動畫(Tween動畫)

這種實現(xiàn)方式可以使視圖組件移動卒暂、放大、縮小以及產(chǎn)生透明度的變化撞叽;

幀動畫(Frame 動畫)

傳統(tǒng)的動畫方法姻成,通過順序的播放排列好的圖片來實現(xiàn),類似電影能扒、gif佣渴。

屬性動畫(Property animation)

自Android 3.0版本開始辫狼,系統(tǒng)給我們提供了一種全新的動畫模式初斑,屬性動畫(property animation),它的功能非常強大膨处,彌補了之前補間動畫的一些缺陷见秤,幾乎是可以完全替代掉補間動畫了。

補間動畫(Tween動畫)

1真椿、透明度動畫AlphaAnimation

    // 透明度動畫
    public void alpha(View v) {
        AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
/*      AlphaAnimation (float fromAlpha, float toAlpha)
        fromAlpha: 動畫的起始alpha值 (范圍:0:完全透明 -1:完全不透明)
        toAlpha:終止的值鹃答,動畫結束的值 */      
        alphaAnimation.setDuration(3000);// 每次動畫持續(xù)時間3秒
        alphaAnimation.setFillAfter(true);// 動畫最后是否停留在終止的狀態(tài)
        alphaAnimation.setRepeatCount(3);// 動畫重復的次數(shù)
        alphaAnimation.setRepeatMode(Animation.REVERSE);// REVERSE: 反轉模式
                                                        // RESTART:重新開始
        // 動畫監(jiān)聽
        alphaAnimation.setAnimationListener(new AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {
                System.out.println("動畫開始回調");
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
                System.out.println("動畫重復回調");

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                System.out.println("動畫結束回調");
                Toast.makeText(getApplicationContext(), "動畫結束,進入陌陌關心你界面",
                        Toast.LENGTH_LONG).show();

            }
        });
        iconIv.startAnimation(alphaAnimation);

    }

2突硝、平移動畫TranslateAnimation

/* TranslateAnimation (int fromXType, 
                float fromXValue, 
                int toXType, 
                float toXValue, 
                int fromYType, 
                float fromYValue, 
                int toYType, 
                float toYValue)
    原點:控件第一次繪制的左上角的坐標點
    fromXType(起點测摔,相對于原點偏移方式):
            Animation.ABSOLUTE 絕對值,像素值
            Animation.RELATIVE_TO_SELF 相對于自己
            Animation.RELATIVE_TO_PARENT 相對于父控件.
    fromXValue(起點解恰,相對于原點偏移量):
            絕對值/百分比     
*/      
        TranslateAnimation translateAnimation = new TranslateAnimation(
                Animation.ABSOLUTE,
                iconIv.getWidth(), // 當前屏幕密度 :240 標準的屏幕密度:160 則dp轉px :
                                    // px=dp*240/160
                Animation.ABSOLUTE, iconIv.getWidth(), 
                Animation.ABSOLUTE, 0,
                Animation.RELATIVE_TO_SELF, 1);
        translateAnimation.setDuration(3000);// 每次動畫持續(xù)時間3秒
        translateAnimation.setFillAfter(true);// 動畫最后停留在終止的狀態(tài)
        translateAnimation.setRepeatCount(3);// 動畫重復的次數(shù)
        translateAnimation.setRepeatMode(Animation.REVERSE);// REVERSE: 反轉模式
                                                            // RESTART:重新開始
        translateAnimation.setInterpolator(new BounceInterpolator());// 設置特效锋八,彈簧效果
        iconIv.startAnimation(translateAnimation);
        System.out.println("控件的寬度" + iconIv.getWidth());

    }

3、縮放動畫ScaleAnimation

/* ScaleAnimation (float fromX, 
                float toX, 
                float fromY, 
                float toY, 
                int pivotXType, 
                float pivotXValue, 
                int pivotYType, 
                float pivotYValue)
    fromX: 縮放起始比例-水平方向
    toX: 縮放最終比例-水平方向
    pivotXType(中心點相較于原點 x方向的類型): 
            Animation.ABSOLUTE
            Animation.RELATIVE_TO_SELF
            RELATIVE_TO_PARENT.
    pivotXValue: 絕對值/百分比    
*/
    public void scale(View v) {
        ScaleAnimation scaleAnimation =new ScaleAnimation
                (0, 2, 0, 2, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        scaleAnimation.setDuration(3000);// 每次動畫持續(xù)時間3秒
        scaleAnimation.setFillAfter(true);// 動畫最后停留在終止的狀態(tài)
        scaleAnimation.setRepeatCount(1);// 動畫重復的次數(shù)
        iconIv.startAnimation(scaleAnimation);

    }

4嗅绰、旋轉動畫 rotate

創(chuàng)建一個Animation類型的XML文件缚去;

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:toDegrees="180"
    android:duration="3000"
    android:interpolator="@android:anim/overshoot_interpolator"
    android:fillAfter="true"
    android:repeatCount="2"
    android:repeatMode="reverse"
    android:pivotX="50%"
    android:pivotY="50%"
    >
    <!--fromDegrees:起始的度數(shù)
      toDegrees:終止的度數(shù)
      infinite:無限次數(shù) 
      起始度數(shù)大于終止度數(shù)撕氧,則能逆時針旋轉,否則順時針
      android:pivotX="50%":旋轉圍繞的軸心紊服,x方向位置,相對于自己的寬度的一半
      android:pivotX="50%p":相對于父控件寬度的一半
      -->
    

</rotate>
Animation animation1 = AnimationUtils.loadAnimation(this,R.anim.rotate);
imageView.startAnimation(animation1);

復合動畫

AnimationSet animationSet=new AnimationSet(false);
animationSet.addAnimation(animation1);
//這樣在這里面添加就可以了胸竞;     
Animation rotateAnimation = AnimationUtils.loadAnimation(this, R.anim.rotate);
animationSet.addAnimation(rotateAnimation);

幀動畫(Frame 動畫)

方式一欺嗤;使用XML的方式;

1卫枝、創(chuàng)建一個AnimationDrawable 的XML文件剂府;

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false" >//這個是反復執(zhí)行的設置;
     <item android:drawable="@drawable/emoji_088" android:duration="150" />
    <item android:drawable="@drawable/emoji_095" android:duration="150" />
    <item android:drawable="@drawable/emoji_096" android:duration="150" />
    <item android:drawable="@drawable/emoji_097" android:duration="150" />
    <item android:drawable="@drawable/emoji_098" android:duration="150" />
    <item android:drawable="@drawable/emoji_099" android:duration="150" />
    <item android:drawable="@drawable/emoji_100" android:duration="150" />
    <item android:drawable="@drawable/emoji_101" android:duration="150" />
    <item android:drawable="@drawable/emoji_102" android:duration="50" />
    <item android:drawable="@drawable/emoji_103" android:duration="50" />
    <item android:drawable="@drawable/emoji_104" android:duration="50" />
</animation-list>
<!--android:drawable[drawable]//加載Drawable對象
    android:duration[long]//每一幀動畫的持續(xù)時間(單位ms)
    android:oneshot[boolean]//動畫是否只運行一次剃盾,true運行一次腺占,false重復運行
    android:visible[boolean]//Drawable對象的初始能見度狀態(tài)淤袜,true可見,false不可見(默認為false)-->

2衰伯、第二步就是需要將這個幀動畫設置到一個容器中去铡羡;imageview;
android:src="@drawable/animation" />

3、可以從控件中獲取到這個幀動畫的圖片然后再對他進行操作意鲸;

drawable = (AnimationDrawable) imageView.getDrawable();

if (drawable.isRunning()) {
            drawable.stop();
        }else{
            drawable.start();
        }

方式二:使用代碼的方式進行烦周;

方式1:添加多個幀 Drawable

        mAnimationDrawable = new AnimationDrawable();
        mAnimationDrawable.setOneShot(false);//是否執(zhí)行一次
        //添加多個幀 Drawable
        for(int i=0;i<11;i++){
            Drawable frame = getResources().getDrawable(R.drawable.girl_1+i);
            mAnimationDrawable.addFrame(frame, 200);
        }

        mImageView.setImageDrawable(mAnimationDrawable);//設置幀動畫,默認是停止狀態(tài)

方式2:引用xml 幀動畫drawable

        
        // 引用xml 幀動畫drawable
        mAnimationDrawable=(AnimationDrawable) getResources().getDrawable(R.drawable.frame);
        mImageView.setImageDrawable(mAnimationDrawable);

屬性動畫(Property animation)

為什么要引入屬性動畫怎顾?

Android之前的補間動畫機制其實還算是比較健全的读慎,在android.view.animation包下面有好多的類可以供我們操作,來完成一系列的動畫效果槐雾,比如說對View進行移動夭委、縮放、旋轉和淡入淡出募强,并且我們還可以借助AnimationSet來將這些動畫效果組合起來使用株灸,除此之外還可以通過配置Interpolator來控制動畫的播放速度等等等等。那么這里大家可能要產(chǎn)生疑問了擎值,既然之前的動畫機制已經(jīng)這么健全了慌烧,為什么還要引入屬性動畫呢?

其實上面所謂的健全都是相對的鸠儿,如果你的需求中只需要對View進行移動屹蚊、縮放、旋轉和淡入淡出操作进每,那么補間動畫確實已經(jīng)足夠健全了汹粤。但是很顯然,這些功能是不足以覆蓋所有的場景的品追,一旦我們的需求超出了移動玄括、縮放、旋轉和淡入淡出這四種對View的操作肉瓦,那么補間動畫就不能再幫我們忙了遭京,也就是說它在功能和可擴展方面都有相當大的局限性,那么下面我們就來看看補間動畫所不能勝任的場景泞莉。

注意上面我在介紹補間動畫的時候都有使用“對View進行操作”這樣的描述哪雕,沒錯,補間動畫是只能夠作用在View上的鲫趁。也就是說斯嚎,我們可以對一個Button、TextView、甚至是LinearLayout堡僻、或者其它任何繼承自View的組件進行動畫操作糠惫,但是如果我們想要對一個非View的對象進行動畫操作,抱歉钉疫,補間動畫就幫不上忙了硼讽。可能有的朋友會感到不能理解牲阁,我怎么會需要對一個非View的對象進行動畫操作呢固阁?這里我舉一個簡單的例子,比如說我們有一個自定義的View城菊,在這個View當中有一個Point對象用于管理坐標备燃,然后在onDraw()方法當中就是根據(jù)這個Point對象的坐標值來進行繪制的。也就是說凌唬,如果我們可以對Point對象進行動畫操作并齐,那么整個自定義View的動畫效果就有了。顯然法瑟,補間動畫是不具備這個功能的冀膝,這是它的第一個缺陷唁奢。

然后補間動畫還有一個缺陷霎挟,就是它只能夠實現(xiàn)移動、縮放麻掸、旋轉和淡入淡出這四種動畫操作酥夭,那如果我們希望可以對View的背景色進行動態(tài)地改變呢?很遺憾脊奋,我們只能靠自己去實現(xiàn)了熬北。說白了,之前的補間動畫機制就是使用硬編碼的方式來完成的诚隙,功能限定死就是這些讶隐,基本上沒有任何擴展性可言。

最后久又,補間動畫還有一個致命的缺陷巫延,就是它只是改變了View的顯示效果而已,而不會真正去改變View的屬性地消。什么意思呢炉峰?比如說,現(xiàn)在屏幕的左上角有一個按鈕脉执,然后我們通過補間動畫將它移動到了屏幕的右下角疼阔,現(xiàn)在你可以去嘗試點擊一下這個按鈕,點擊事件是絕對不會觸發(fā)的,因為實際上這個按鈕還是停留在屏幕的左上角婆廊,只不過補間動畫將這個按鈕繪制到了屏幕的右下角而已迅细。

也正是因為這些原因,Android開發(fā)團隊決定在3.0版本當中引入屬性動畫這個功能淘邻,那么屬性動畫是不是就把上述的問題全部解決掉了疯攒?下面我們就來一起看一看。

新引入的屬性動畫機制已經(jīng)不再是針對于View來設計的了列荔,也不限定于只能實現(xiàn)移動敬尺、縮放、旋轉和淡入淡出這幾種動畫操作贴浙,同時也不再只是一種視覺上的動畫效果了砂吞。它實際上是一種不斷地對值進行操作的機制,并將值賦值到指定對象的指定屬性上崎溃,可以是任意對象的任意屬性蜻直。所以我們仍然可以將一個View進行移動或者縮放,但同時也可以對自定義View中的Point對象進行動畫操作了袁串。我們只需要告訴系統(tǒng)動畫的運行時長概而,需要執(zhí)行哪種類型的動畫,以及動畫的初始值和結束值囱修,剩下的工作就可以全部交給系統(tǒng)去完成了赎瑰。

既然屬性動畫的實現(xiàn)機制是通過對目標對象進行賦值并修改其屬性來實現(xiàn)的,那么之前所說的按鈕顯示的問題也就不復存在了破镰,如果我們通過屬性動畫來移動一個按鈕餐曼,那么這個按鈕就是真正的移動了,而不再是僅僅在另外一個位置繪制了而已鲜漩。

ValueAnimator

ValueAnimator是整個屬性動畫機制當中最核心的一個類源譬,前面我們已經(jīng)提到了,屬性動畫的運行機制是通過不斷地對值進行操作來實現(xiàn)的孕似,而初始值和結束值之間的動畫過渡就是由ValueAnimator這個類來負責計算的踩娘。
它的內部使用一種時間循環(huán)的機制來計算值與值之間的動畫過渡,我們只需要將初始值和結束值提供給ValueAnimator喉祭,并且告訴它動畫所需運行的時長养渴,那么ValueAnimator就會自動幫我們完成從初始值平滑地過渡到結束值這樣的效果。除此之外臂拓,ValueAnimator還負責管理動畫的播放次數(shù)厚脉、播放模式、以及對動畫設置監(jiān)聽器等胶惰,確實是一個非常重要的類傻工。

但是ValueAnimator的用法卻一點都不復雜,我們先從最簡單的功能看起吧,比如說想要將一個值從0平滑過渡到1中捆,時長300毫秒鸯匹,就可以這樣寫:

ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);  
anim.setDuration(300);  
anim.start();  

怎么樣?很簡單吧泄伪,調用ValueAnimator的ofFloat()方法就可以構建出一個ValueAnimator的實例殴蓬,ofFloat()方法當中允許傳入多個float類型的參數(shù),這里傳入0和1就表示將值從0平滑過渡到1蟋滴,然后調用ValueAnimator的setDuration()方法來設置動畫運行的時長染厅,最后調用start()方法啟動動畫。

用法就是這么簡單津函,現(xiàn)在如果你運行一下上面的代碼肖粮,動畫就會執(zhí)行了《啵可是這只是一個將值從0過渡到1的動畫涩馆,又看不到任何界面效果,我們怎樣才能知道這個動畫是不是已經(jīng)真正運行了呢允坚?這就需要借助監(jiān)聽器來實現(xiàn)了魂那,如下所示:

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();  

可以看到,這里我們通過addUpdateListener()方法來添加一個動畫的監(jiān)聽器稠项,在動畫執(zhí)行的過程中會不斷地進行回調涯雅,我們只需要在回調方法當中將當前的值取出并打印出來,就可以知道動畫有沒有真正運行了皿渗。運行上述代碼斩芭,控制臺打印如下所示:

從打印日志的值我們就可以看出轻腺,ValueAnimator確實已經(jīng)在正常工作了乐疆,值在300毫秒的時間內從0平滑過渡到了1,而這個計算工作就是由ValueAnimator幫助我們完成的贬养。另外ofFloat()方法當中是可以傳入任意多個參數(shù)的挤土,因此我們還可以構建出更加復雜的動畫邏輯,比如說將一個值在5秒內從0過渡到5误算,再過渡到3仰美,再過渡到10,就可以這樣寫:

ValueAnimator anim = ValueAnimator.ofFloat(0f, 5f, 3f, 10f);  
anim.setDuration(5000);  
anim.start();  

當然也許你并不需要小數(shù)位數(shù)的動畫過渡儿礼,可能你只是希望將一個整數(shù)值從0平滑地過渡到100咖杂,那么也很簡單,只需要調用ValueAnimator的ofInt()方法就可以了蚊夫,如下所示:

ValueAnimator anim = ValueAnimator.ofInt(0, 100);  

常用方法

ValueAnimator當中最常用的應該就是ofFloat()和ofInt()這兩個方法了诉字,另外還有一個ofObject()方法,我會在下篇文章進行講解。

setStartDelay():動畫延遲播放的時間
setRepeatCount():動畫循環(huán)播放的次數(shù)
setRepeatMode():循環(huán)播放的模式壤圃,循環(huán)模式包括RESTART和REVERSE兩種陵霉,分別表示重新播放和倒序播放的意思。

ObjectAnimator

對任意對象的任意屬性進行動畫操作

相比于ValueAnimator伍绳,ObjectAnimator可能才是我們最常接觸到的類踊挠,因為ValueAnimator只不過是對值進行了一個平滑的動畫過渡,但我們實際使用到這種功能的場景好像并不多冲杀。而ObjectAnimator則就不同了效床,它是可以直接任意對象的任意屬性進行動畫操作的,比如說View的alpha屬性权谁。

不過雖說ObjectAnimator會更加常用一些扁凛,但是它其實是繼承自ValueAnimator的,底層的動畫實現(xiàn)機制也是基于ValueAnimator來完成的闯传,因此ValueAnimator仍然是整個屬性動畫當中最核心的一個類谨朝。

ObjectAnimator.ofFloat

ObjectAnimator ofFloat (Object target, 
                String propertyName, 
                float... values)
Constructs and returns an ObjectAnimator that animates between float values. A single value implies that that value is the one being animated to, in which case the start value will be derived from the property being animated and the target object when start() is called for the first time. Two values imply starting and ending values. More than two values imply a starting value, values to animate through along the way, and an ending value (these values will be distributed evenly across the duration of the animation).
Parameters
target
    Object: The object whose property is to be animated. This object should have a public method on it called setName(), where name is the value of the propertyName parameter.
propertyName
    String: The name of the property being animated.
values
    float: A set of values that the animation will animate between over time.
    
Returns
    ObjectAnimator
An ObjectAnimator object that is set up to animate between the given values.

那么既然是繼承關系,說明ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的甥绿,它們的用法也非常類似字币。

alpha

這里如果我們想要將一個TextView在5秒中內從常規(guī)變換成全透明,再從全透明變換成常規(guī)共缕,就可以這樣寫:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);  
/*
參數(shù)1:傳入一個object對象洗出,我們想要對哪個對象進行動畫操作就傳入什么,這里我傳入了一個textview图谷。
參數(shù)2:想要對該對象的哪個屬性進行動畫操作翩活,由于我們想要改變TextView的不透明度,因此這里傳入"alpha"便贵。
參數(shù)3:不固定長度菠镇,想要完成什么樣的動畫就傳入什么值,這里傳入的值就表示將TextView從常規(guī)變換成全透明承璃,再從全透明變換成常規(guī)利耍。
*/
animator.setDuration(5000);  
animator.start();  
rotation

學會了這一個用法之后,其它的用法我們就可以舉一反三了盔粹,那比如說我們想要將TextView進行一次360度的旋轉隘梨,就可以這樣寫:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "rotation", 0f, 360f);  
animator.setDuration(5000);  
animator.start();  

可以看到,這里我們將第二個參數(shù)改成了"rotation"舷嗡,然后將動畫的初始值和結束值分別設置成0和360轴猎,現(xiàn)在運行一下代碼,效果如下圖所示:


translationX

那么如果想要將TextView先向左移出屏幕进萄,然后再移動回來捻脖,就可以這樣寫:

float curTranslationX = textview.getTranslationX();  
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "translationX", curTranslationX, -500f, curTranslationX);  
animator.setDuration(5000);  
animator.start();  

這里我們先是調用了TextView的getTranslationX()方法來獲取到當前TextView的translationX的位置烦秩,然后ofFloat()方法的第二個參數(shù)傳入"translationX",緊接著后面三個參數(shù)用于告訴系統(tǒng)TextView應該怎么移動郎仆,現(xiàn)在運行一下代碼只祠,效果如下圖所示:


scaleY

然后我們還可以TextView進行縮放操作九昧,比如說將TextView在垂直方向上放大3倍再還原挣轨,就可以這樣寫:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "scaleY", 1f, 3f, 1f);  
animator.setDuration(5000);  
animator.start();  

這里將ofFloat()方法的第二個參數(shù)改成了"scaleY",表示在垂直方向上進行縮放沐兵,現(xiàn)在重新運行一下程序曙旭,效果如下圖所示:


工作機制

ofFloat()方法的第二個參數(shù)到底可以傳哪些值呢盗舰?目前我們使用過了alpha、rotation桂躏、translationX和scaleY這幾個值钻趋,分別可以完成淡入淡出、旋轉剂习、水平移動蛮位、垂直縮放這幾種動畫,那么還有哪些值是可以使用的呢鳞绕?

我們可以傳入任意的值到ofFloat()方法的第二個參數(shù)當中失仁。

因為ObjectAnimator在設計的時候就沒有針對于View來進行設計,而是針對于任意對象的们何,它所負責的工作就是不斷地向某個對象中的某個屬性進行賦值萄焦,然后對象根據(jù)屬性值的改變再來決定如何展現(xiàn)出來。

那么比如說我們調用下面這樣一段代碼:

ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f);  

其實這段代碼的意思就是ObjectAnimator會幫我們不斷地改變textview對象中alpha屬性的值冤竹,從1f變化到0f拂封。然后textview對象需要根據(jù)alpha屬性值的改變來不斷刷新界面的顯示,從而讓用戶可以看出淡入淡出的動畫效果鹦蠕。

那么textview對象中是不是有alpha屬性這個值呢冒签?沒有,不僅textview沒有這個屬性片部,連它所有的父類也是沒有這個屬性的镣衡!這就奇怪了,textview當中并沒有alpha這個屬性档悠,ObjectAnimator是如何進行操作的呢?其實ObjectAnimator內部的工作機制并不是直接對我們傳入的屬性名進行操作的望浩,而是會去尋找這個屬性名對應的get和set方法辖所,因此alpha屬性所對應的get和set方法應該就是:

public void setAlpha(float value);  
public float getAlpha();  

那么textview對象中是否有這兩個方法呢?確實有磨德,并且這兩個方法是由View對象提供的缘回,也就是說不僅TextView可以使用這個屬性來進行淡入淡出動畫操作吆视,任何繼承自View的對象都可以的。

既然alpha是這個樣子酥宴,相信大家一定已經(jīng)明白了啦吧,前面我們所用的所有屬性都是這個工作原理,那么View當中一定也存在著setRotation()拙寡、getRotation()授滓、setTranslationX()、getTranslationX()肆糕、setScaleY()般堆、getScaleY()這些方法,不信的話你可以到View當中去找一下诚啃。

組合動畫-AnimatorSet

實現(xiàn)組合動畫功能主要需要借助AnimatorSet這個類淮摔,這個類提供了一個play()方法,如果我們向這個方法中傳入一個Animator對象(ValueAnimator或ObjectAnimator)將會返回一個AnimatorSet.Builder的實例始赎。

AnimatorSet.Builder

AnimatorSet.Builder中包括以下四個方法:
after(Animator anim) 將現(xiàn)有動畫插入到傳入的動畫之后執(zhí)行
after(long delay) 將現(xiàn)有動畫延遲指定毫秒后執(zhí)行
before(Animator anim) 將現(xiàn)有動畫插入到傳入的動畫之前執(zhí)行
with(Animator anim) 將現(xiàn)有動畫和傳入的動畫同時執(zhí)行

好的和橙,有了這四個方法,我們就可以完成組合動畫的邏輯了造垛,那么比如說我們想要讓TextView先從屏幕外移動進屏幕胃碾,然后開始旋轉360度,旋轉的同時進行淡入淡出操作筋搏,就可以這樣寫:

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();  

可以看到仆百,這里我們先是把三個動畫的對象全部創(chuàng)建出來,然后new出一個AnimatorSet對象之后將這三個動畫對象進行播放排序奔脐,讓旋轉和淡入淡出動畫同時進行俄周,并把它們插入到了平移動畫的后面,最后是設置動畫時長以及啟動動畫髓迎。運行一下上述代碼峦朗,效果如下圖所示:


Animator監(jiān)聽器

AnimatorListener

Animator類當中提供了一個addListener()方法,這個方法接收一個AnimatorListener排龄,我們只需要去實現(xiàn)這個AnimatorListener就可以監(jiān)聽動畫的各種事件了波势。

大家已經(jīng)知道,ObjectAnimator是繼承自ValueAnimator的橄维,而ValueAnimator又是繼承自Animator的尺铣,因此不管是ValueAnimator還是ObjectAnimator都是可以使用addListener()這個方法的。另外AnimatorSet也是繼承自Animator的争舞,因此addListener()這個方法算是個通用的方法凛忿。

添加一個監(jiān)聽器的代碼如下所示:

anim.addListener(new AnimatorListener() {  
    @Override  
    public void onAnimationStart(Animator animation) {  
        //動畫開始的時候調用
    }  
  
    @Override  
    public void onAnimationRepeat(Animator animation) {  
        //動畫重復執(zhí)行的時候調用
    }  
  
    @Override  
    public void onAnimationEnd(Animator animation) {  
        //動畫結束的時候調用
    }  
  
    @Override  
    public void onAnimationCancel(Animator animation) {  
        //動畫被取消的時候調用
    }  
});  

可以看到,我們需要實現(xiàn)接口中的四個方法竞川,onAnimationStart()方法會在動畫開始的時候調用店溢,onAnimationRepeat()方法會在動畫重復執(zhí)行的時候調用叁熔,onAnimationEnd()方法會在動畫結束的時候調用,onAnimationCancel()方法會在動畫被取消的時候調用床牧。

AnimatorListenerAdapter

但是也許很多時候我們并不想要監(jiān)聽那么多個事件荣回,可能我只想要監(jiān)聽動畫結束這一個事件,那么每次都要將四個接口全部實現(xiàn)一遍就顯得非常繁瑣戈咳。沒關系心软,為此Android提供了一個適配器類,叫作AnimatorListenerAdapter除秀,使用這個類就可以解決掉實現(xiàn)接口繁瑣的問題了糯累,如下所示:

anim.addListener(new AnimatorListenerAdapter() {  
});  

這里我們向addListener()方法中傳入這個適配器對象,由于AnimatorListenerAdapter中已經(jīng)將每個接口都實現(xiàn)好了册踩,所以這里不用實現(xiàn)任何一個方法也不會報錯泳姐。那么如果我想監(jiān)聽動畫結束這個事件,就只需要單獨重寫這一個方法就可以了暂吉,如下所示:

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

使用XML編寫動畫

我們可以使用代碼來編寫所有的動畫功能胖秒,這也是最常用的一種做法。不過慕的,過去的補間動畫除了使用代碼編寫之外也是可以使用XML編寫的阎肝,因此屬性動畫也提供了這一功能,即通過XML來完成和代碼一樣的屬性動畫功能肮街。

通過XML來編寫動畫可能會比通過代碼來編寫動畫要慢一些风题,但是在重用方面將會變得非常輕松,比如某個將通用的動畫編寫到XML里面嫉父,我們就可以在各個界面當中輕松去重用它沛硅。

如果想要使用XML來編寫動畫,首先要在res目錄下面新建一個animator文件夾绕辖,所有屬性動畫的XML文件都應該存放在這個文件夾當中摇肌。

XML標簽

animato

對應代碼中的ValueAnimator

objectAnimator

對應代碼中的ObjectAnimator

set

對應代碼中的AnimatorSet

那么比如說我們想要實現(xiàn)一個從0到100平滑過渡的動畫,在XML當中就可以這樣寫:

<animator xmlns:android="http://schemas.android.com/apk/res/android"  
    android:valueFrom="0"  
    android:valueTo="100"  
    android:valueType="intType"/>  

而如果我們想將一個視圖的alpha屬性從1變成0仪际,就可以這樣寫:

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"  
    android:valueFrom="1"  
    android:valueTo="0"  
    android:valueType="floatType"  
    android:propertyName="alpha"/>  

另外围小,我們也可以使用XML來完成復雜的組合動畫操作,比如將一個視圖先從屏幕外移動進屏幕树碱,然后開始旋轉360度肯适,旋轉的同時進行淡入淡出操作,就可以這樣寫:

<set xmlns:android="http://schemas.android.com/apk/res/android"  
    android:ordering="sequentially" >  
  
    <objectAnimator  
        android:duration="2000"  
        android:propertyName="translationX"  
        android:valueFrom="-500"  
        android:valueTo="0"  
        android:valueType="floatType" >  
    </objectAnimator>  
  
    <set android:ordering="together" >  
        <objectAnimator  
            android:duration="3000"  
            android:propertyName="rotation"  
            android:valueFrom="0"  
            android:valueTo="360"  
            android:valueType="floatType" >  
        </objectAnimator>  
  
        <set android:ordering="sequentially" >  
            <objectAnimator  
                android:duration="1500"  
                android:propertyName="alpha"  
                android:valueFrom="1"  
                android:valueTo="0"  
                android:valueType="floatType" >  
            </objectAnimator>  
            <objectAnimator  
                android:duration="1500"  
                android:propertyName="alpha"  
                android:valueFrom="0"  
                android:valueTo="1"  
                android:valueType="floatType" >  
            </objectAnimator>  
        </set>  
    </set>  
  
</set>  

這段XML實現(xiàn)的效果和我們剛才通過代碼來實現(xiàn)的組合動畫的效果是一模一樣的赴恨。

在代碼中加載XML文件-AnimatorInflater

在代碼中把文件加載進來并將動畫啟動:

Animator animator = AnimatorInflater.loadAnimator(context, R.animator.anim_file);  
animator.setTarget(view);  
animator.start();  

調用AnimatorInflater的loadAnimator來將XML動畫文件加載進來疹娶,然后再調用setTarget()方法將這個動畫設置到某一個對象上面,最后再調用start()方法啟動動畫就可以了伦连。

ValueAnimator的高級用法

目標:通過對對象進行值操作來實現(xiàn)動畫效果

實現(xiàn)目標

比如說我們有一個自定義的View雨饺,在這個View當中有一個Point對象用于管理坐標,然后在onDraw()方法當中就是根據(jù)這個Point對象的坐標值來進行繪制的惑淳。也就是說额港,如果我們可以對Point對象進行動畫操作,那么整個自定義View的動畫效果就有了歧焦。

效果圖:
效果圖

TypeEvaluator

簡單來說移斩,就是告訴動畫系統(tǒng)如何從初始值過度到結束值。

ValueAnimator.ofFloat()方法就是實現(xiàn)了初始值與結束值之間的平滑過度绢馍,那么這個平滑過度是怎么做到的呢向瓷?
其實就是系統(tǒng)內置了一個FloatEvaluator,它通過計算告知動畫系統(tǒng)如何從初始值過度到結束值舰涌,我們來看一下FloatEvaluator的代碼實現(xiàn):

public class FloatEvaluator implements TypeEvaluator {  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        float startFloat = ((Number) startValue).floatValue();  
        return startFloat + fraction * (((Number) endValue).floatValue() - startFloat);  
    }  
}  

FloatEvaluator實現(xiàn)了TypeEvaluator接口猖任,然后重寫evaluate()方法。
evaluate()方法當中傳入了三個參數(shù):
參數(shù)1:fraction用于表示動畫的完成度的瓷耙,我們應該根據(jù)它來計算當前動畫的值應該是多少朱躺,
參數(shù)2、3:分別表示動畫的初始值和結束值搁痛。
代碼邏輯:用結束值減去初始值长搀,算出它們之間的差值,然后乘以fraction這個系數(shù)鸡典,再加上初始值源请,那么就得到當前動畫的值了。

ValueAnimator的ofFloat()和ofInt()方法彻况,分別用于對浮點型和整型的數(shù)據(jù)進行動畫操作的谁尸,ValueAnimator中還有一個ofObject()方法,是用于對任意對象進行動畫操作的疗垛。
但是相比于浮點型或整型數(shù)據(jù)症汹,對象的動畫操作明顯要更復雜一些,因為系統(tǒng)將完全無法知道如何從初始對象過度到結束對象贷腕,因此這個時候我們就需要實現(xiàn)一個自己的TypeEvaluator來告知系統(tǒng)如何進行過度背镇。

實現(xiàn)步驟

先定義一個Point類:

Point類非常簡單,只有x和y兩個變量用于記錄坐標的位置泽裳,并提供了構造方法來設置坐標瞒斩,以及get方法來獲取坐標。

public class Point {  
  
    private float x;  
  
    private float y;  
  
    public Point(float x, float y) {  
        this.x = x;  
        this.y = y;  
    }  
  
    public float getX() {  
        return x;  
    }  
  
    public float getY() {  
        return y;  
    }  
  
}  
自定義TypeEvaluator:

實現(xiàn)TypeEvaluator接口并重寫了evaluate()方法:先是將startValue和endValue強轉成Point對象涮总,然后同樣根據(jù)fraction來計算當前動畫的x和y的值胸囱,最后組裝到一個新的Point對象當中并返回。

public class PointEvaluator implements TypeEvaluator{  
  
    @Override  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        Point startPoint = (Point) startValue;  
        Point endPoint = (Point) endValue;  
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());  
        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());  
        Point point = new Point(x, y);  
        return point;  
    }  
  
}  

這樣我們就將PointEvaluator編寫完成了瀑梗,接下來我們就可以非常輕松地對Point對象進行動畫操作了烹笔,比如說我們有兩個Point對象裳扯,現(xiàn)在需要將Point1通過動畫平滑過度到Point2,就可以這樣寫:
需要注意的是谤职,ofObject()方法要求多傳入一個TypeEvaluator參數(shù)饰豺,這里我們只需要傳入剛才定義好的PointEvaluator的實例就可以了。

Point point1 = new Point(0, 0);  
Point point2 = new Point(300, 300);  
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1, point2);  
anim.setDuration(5000);  
anim.start();  

好的允蜈,這就是自定義TypeEvaluator的全部用法冤吨,掌握了這些知識之后,我們就可以來嘗試一下如何通過對Point對象進行動畫操作饶套,從而實現(xiàn)整個自定義View的動畫效果漩蟆。

自定義MyAnimView:

首先在自定義View的構造方法當中初始化了一個Paint對象作為畫筆,并將畫筆顏色設置為藍色妓蛮,接著在onDraw()方法當中進行繪制怠李。
這里我們繪制的邏輯是由currentPoint這個對象控制的,如果currentPoint對象不等于空仔引,那么就調用drawCircle()方法在currentPoint的坐標位置畫出一個半徑為50的圓扔仓,如果currentPoint對象是空,那么就調用startAnimation()方法來啟動動畫咖耘。

public class MyAnimView extends View {  
  
    public static final float RADIUS = 50f;  
  
    private Point currentPoint;  
  
    private Paint mPaint;  
  
    public MyAnimView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
        mPaint.setColor(Color.BLUE);  
    }  
  
    @Override  
    protected void onDraw(Canvas canvas) {  
        if (currentPoint == null) {  
            currentPoint = new Point(RADIUS, RADIUS);  
            drawCircle(canvas);  
            startAnimation();  
        } else {  
            drawCircle(canvas);  
        }  
    }  
  
    private void drawCircle(Canvas canvas) {  
        float x = currentPoint.getX();  
        float y = currentPoint.getY();  
        canvas.drawCircle(x, y, RADIUS, mPaint);  
    }  
  
    private void startAnimation() {  
        Point startPoint = new Point(RADIUS, RADIUS);  
        Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);  
        ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);  
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
            @Override  
            public void onAnimationUpdate(ValueAnimator animation) {  
                currentPoint = (Point) animation.getAnimatedValue();  
                invalidate();  
            }  
        });  
        anim.setDuration(5000);  
        anim.start();  
    }  
  
}  

startAnimation()方法中的代碼:
就是對Point對象進行了一個動畫操作而已翘簇。
這里我們定義了一個startPoint和一個endPoint,坐標分別是View的左上角和右下角儿倒,并將動畫的時長設為5秒版保。然后有一點需要大家注意的,就是我們通過監(jiān)聽器對動畫的過程進行了監(jiān)聽夫否,每當Point值有改變的時候都會回調onAnimationUpdate()方法彻犁。在這個方法當中,我們對currentPoint對象進行了重新賦值凰慈,并調用了invalidate()方法汞幢,這樣的話onDraw()方法就會重新調用,并且由于currentPoint對象的坐標已經(jīng)改變了微谓,那么繪制的位置也會改變森篷,于是一個平移的動畫效果也就實現(xiàn)了。

在布局文件當中引入這個自定義控件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:layout_width="match_parent"  
    android:layout_height="match_parent"  
    >  
  
    <com.example.tony.myapplication.MyAnimView  
        android:layout_width="match_parent"  
        android:layout_height="match_parent" />  
  
</RelativeLayout>  

ObjectAnimator的高級用法

實現(xiàn)目標

動態(tài)改變View的顏色豺型。

效果圖:
效果圖

實現(xiàn)步驟

自定義MyAnimView屬性

ObjectAnimator內部的工作機制是通過尋找特定屬性的get和set方法仲智,然后通過方法不斷地對值進行改變,從而實現(xiàn)動畫效果的姻氨。
  因此我們就需要在MyAnimView中定義一個color屬性钓辆,并提供它的get和set方法。這里我們可以將color屬性設置為字符串類型,使用#RRGGBB這種格式來表示顏色值前联,代碼如下所示:
  在setColor()方法當中功戚,將畫筆的顏色設置成方法參數(shù)傳入的顏色,然后調用了invalidate()方法蛀恩。即在改變了畫筆顏色之后立即刷新視圖疫铜,然后onDraw()方法就會調用茂浮。在onDraw()方法當中會根據(jù)當前畫筆的顏色來進行繪制双谆,這樣顏色也就會動態(tài)進行改變了。

public class MyAnimView extends View {  
  
    ...  
  
    private String color;  
  
    public String getColor() {  
        return color;  
    }  
  
    public void setColor(String color) {  
        this.color = color;  
        mPaint.setColor(Color.parseColor(color));  
        invalidate();  
    }  
  
    ...  
  
}  
自定義TypeEvaluator:

要借助ObjectAnimator類讓setColor()方法得到調用了席揽,在使用ObjectAnimator之前我們還要完成一個非常重要的工作顽馋,就是編寫一個用于告知系統(tǒng)如何進行顏色過度的TypeEvaluator
創(chuàng)建ColorEvaluator并實現(xiàn)TypeEvaluator接口幌羞,代碼如下所示:
  首先在evaluate()方法當中獲取到顏色的初始值和結束值寸谜,并通過字符串截取的方式將顏色分為RGB三個部分,并將RGB的值轉換成十進制數(shù)字属桦,那么每個顏色的取值范圍就是0-255熊痴。接下來計算一下初始顏色值到結束顏色值之間的差值,這個差值很重要聂宾,決定著顏色變化的快慢果善,如果初始顏色值和結束顏色值很相近,那么顏色變化就會比較緩慢系谐,而如果顏色值相差很大巾陕,比如說從黑到白,那么就要經(jīng)歷255*3這個幅度的顏色過度纪他,變化就會非潮擅海快。

那么控制顏色變化的速度是通過getCurrentColor()這個方法來實現(xiàn)的茶袒,這個方法會根據(jù)當前的fraction值來計算目前應該過度到什么顏色梯刚,并且這里會根據(jù)初始和結束的顏色差值來控制變化速度,最終將計算出的顏色進行返回薪寓。

最后亡资,由于我們計算出的顏色是十進制數(shù)字,這里還需要調用一下getHexString()方法把它們轉換成十六進制字符串预愤,再將RGB顏色拼裝起來之后作為最終的結果返回沟于。

public class ColorEvaluator implements TypeEvaluator {  
  
    private int mCurrentRed = -1;  
  
    private int mCurrentGreen = -1;  
  
    private int mCurrentBlue = -1;  
  
    @Override  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        String startColor = (String) startValue;  
        String endColor = (String) endValue;  
        int startRed = Integer.parseInt(startColor.substring(1, 3), 16);  
        int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);  
        int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);  
        int endRed = Integer.parseInt(endColor.substring(1, 3), 16);  
        int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);  
        int endBlue = Integer.parseInt(endColor.substring(5, 7), 16);  
        // 初始化顏色的值  
        if (mCurrentRed == -1) {  
            mCurrentRed = startRed;  
        }  
        if (mCurrentGreen == -1) {  
            mCurrentGreen = startGreen;  
        }  
        if (mCurrentBlue == -1) {  
            mCurrentBlue = startBlue;  
        }  
        // 計算初始顏色和結束顏色之間的差值  
        int redDiff = Math.abs(startRed - endRed);  
        int greenDiff = Math.abs(startGreen - endGreen);  
        int blueDiff = Math.abs(startBlue - endBlue);  
        int colorDiff = redDiff + greenDiff + blueDiff;  
        if (mCurrentRed != endRed) {  
            mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,  
                    fraction);  
        } else if (mCurrentGreen != endGreen) {  
            mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,  
                    redDiff, fraction);  
        } else if (mCurrentBlue != endBlue) {  
            mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,  
                    redDiff + greenDiff, fraction);  
        }  
        // 將計算出的當前顏色的值組裝返回  
        String currentColor = "#" + getHexString(mCurrentRed)  
                + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);  
        return currentColor;  
    }  
  
    /** 
     * 根據(jù)fraction值來計算當前的顏色。 
     */  
    private int getCurrentColor(int startColor, int endColor, int colorDiff,  
            int offset, float fraction) {  
        int currentColor;  
        if (startColor > endColor) {  
            currentColor = (int) (startColor - (fraction * colorDiff - offset));  
            if (currentColor < endColor) {  
                currentColor = endColor;  
            }  
        } else {  
            currentColor = (int) (startColor + (fraction * colorDiff - offset));  
            if (currentColor > endColor) {  
                currentColor = endColor;  
            }  
        }  
        return currentColor;  
    }  
      
    /** 
     * 將10進制顏色值轉換成16進制植康。 
     */  
    private String getHexString(int value) {  
        String hexString = Integer.toHexString(value);  
        if (hexString.length() == 1) {  
            hexString = "0" + hexString;  
        }  
        return hexString;  
    }  
  
}  
調用

比如說我們想要實現(xiàn)從藍色到紅色的動畫過度旷太,歷時5秒,就可以這樣寫:

ObjectAnimator anim = ObjectAnimator.ofObject(myAnimView, "color", new ColorEvaluator(),   
    "#0000FF", "#FF0000");  
anim.setDuration(5000);  
anim.start();  

接下來我們需要將上面一段代碼移到MyAnimView類當中,讓它和剛才的Point移動動畫可以結合到一起播放供璧,這就要借助我們在上篇文章當中學到的組合動畫的技術了存崖。修改MyAnimView中的代碼,如下所示:

public class MyAnimView extends View {  
  
    ...  
  
    private void startAnimation() {  
        Point startPoint = new Point(RADIUS, RADIUS);  
        Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);  
        ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);  
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
            @Override  
            public void onAnimationUpdate(ValueAnimator animation) {  
                currentPoint = (Point) animation.getAnimatedValue();  
                invalidate();  
            }  
        });  
        ObjectAnimator anim2 = ObjectAnimator.ofObject(this, "color", new ColorEvaluator(),   
                "#0000FF", "#FF0000");  
        AnimatorSet animSet = new AnimatorSet();  
        animSet.play(anim).with(anim2);  
        animSet.setDuration(5000);  
        animSet.start();  
    }  
  
}  

可以看到,我們并沒有改動太多的代碼,重點只是修改了startAnimation()方法中的部分內容抖拴。這里先是將顏色過度的代碼邏輯移動到了startAnimation()方法當中瞒滴,注意由于這段代碼本身就是在MyAnimView當中執(zhí)行的,因此ObjectAnimator.ofObject()的第一個參數(shù)直接傳this就可以了露氮。接著我們又創(chuàng)建了一個AnimatorSet,并把兩個動畫設置成同時播放,動畫時長為五秒葛虐,最后啟動動畫。

Interpolator的用法

Interpolator這個東西很難進行翻譯棉钧,直譯過來的話是補間器的意思屿脐,它的主要作用是可以控制動畫的變化速率,比如去實現(xiàn)一種非線性運動的動畫效果宪卿。那么什么叫做非線性運動的動畫效果呢的诵?就是說動畫改變的速率不是一成不變的,像加速運動以及減速運動都屬于非線性運動佑钾。

不過Interpolator并不是屬性動畫中新增的技術西疤,實際上從Android 1.0版本開始就一直存在Interpolator接口了,而之前的補間動畫當然也是支持這個功能的次绘。只不過在屬性動畫新增了一個TimeInterpolator接口瘪阁,這個接口是用于兼容之前的Interpolator的,這使得所有過去的Interpolator實現(xiàn)類都可以直接拿過來放到屬性動畫當中使用邮偎。

TimeInterpolator接口的所有實現(xiàn)類:


TimeInterpolator接口已經(jīng)有非常多的實現(xiàn)類了管跺,這些都是Android系統(tǒng)內置好的并且我們可以直接使用的Interpolator。每個Interpolator都有它各自的實現(xiàn)效果禾进,比如說AccelerateInterpolator就是一個加速運動的Interpolator豁跑,而DecelerateInterpolator就是一個減速運動的Interpolator。

AccelerateDecelerateInterpolator

上文使用ValueAnimator所打印的值如下所示:


可以看到泻云,一開始的值變化速度明顯比較慢艇拍,僅0.0開頭的就打印了4次,之后開始加速宠纯,最后階段又開始減速卸夕,因此我們可以很明顯地看出這一個先加速后減速的Interpolator。

上文中的小球也是:一開始運動速度比較慢婆瓜,然后逐漸加速快集,中間的部分運動速度就比較快贡羔,接下來開始減速,最后緩緩停住个初。另外顏色變化也是這種規(guī)律乖寒,一開始顏色變化的比較慢,中間顏色變化的很快院溺,最后階段顏色變化的又比較慢楣嘁。

從以上幾點我們就可以總結出一個結論了,使用屬性動畫時珍逸,系統(tǒng)默認的Interpolator其實就是一個先加速后減速的Interpolator逐虚,對應的實現(xiàn)類就是AccelerateDecelerateInterpolator

我們可以很輕松地修改這一默認屬性弄息,將它替換成任意一個系統(tǒng)內置好的Interpolator痊班。
比如,上文MyAnimView中的startAnimation()方法是開啟動畫效果的入口摹量,這里我們對Point對象的坐標稍做一下修改,讓它變成一種垂直掉落的效果馒胆,代碼如下所示:
對Point構造函數(shù)中的坐標值進行了一下改動缨称,那么現(xiàn)在小球運動的動畫效果應該是從屏幕正中央的頂部掉落到底部。

private void startAnimation() {  
    Point startPoint = new Point(getWidth() / 2, RADIUS);  
    Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);  
    ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);  
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
        @Override  
        public void onAnimationUpdate(ValueAnimator animation) {  
            currentPoint = (Point) animation.getAnimatedValue();  
            invalidate();  
        }  
    });  
    anim.setDuration(2000);  
    anim.start();  
}  

但是默認情況下小球的下降速度肯定是先加速后減速的祝迂,我們需要改為下降速度越來越快的睦尽。
調用Animator的setInterpolator()方法就可以了,這個方法要求傳入一個實現(xiàn)TimeInterpolator接口的實例型雳,那么比如說我們想要實現(xiàn)小球下降越來越快的效果当凡,就可以使用AccelerateInterpolator,代碼如下所示:

private void startAnimation() {  
    Point startPoint = new Point(getWidth() / 2, RADIUS);  
    Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);  
    ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);  
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
        @Override  
        public void onAnimationUpdate(ValueAnimator animation) {  
            currentPoint = (Point) animation.getAnimatedValue();  
            invalidate();  
        }  
    });  
    anim.setInterpolator(new AccelerateInterpolator(2f));//實現(xiàn)小球下降越來越快  
    /*AccelerateInterpolator的構建函數(shù)可以接收一個float類型的參數(shù)纠俭,這個參數(shù)是用于控制加速度的*/
    anim.setDuration(2500);  
    anim.start();  
}  

效果如下:


BounceInterpolator

現(xiàn)在要讓小球撞擊到地面之后應該要反彈起來沿量,然后再次落下,接著再反彈起來冤荆,又再次落下朴则,以此反復,最后靜止钓简。這個功能我們當然可以自己去寫乌妒,只不過比較復雜,所幸的是外邓,Android系統(tǒng)中已經(jīng)提供好了這樣一種Interpolator撤蚊,我們只需要簡單地替換一下就可以完成上面的描述的效果,代碼如下所示:
將設置的Interpolator換成了BounceInterpolator的實例损话,而BounceInterpolator就是一種可以模擬物理規(guī)律侦啸,實現(xiàn)反復彈起效果的Interpolator。

private void startAnimation() {  
    Point startPoint = new Point(getWidth() / 2, RADIUS);  
    Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);  
    ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);  
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
        @Override  
        public void onAnimationUpdate(ValueAnimator animation) {  
            currentPoint = (Point) animation.getAnimatedValue();  
            invalidate();  
        }  
    });  
    anim.setInterpolator(new BounceInterpolator());  
    anim.setDuration(3000);  
    anim.start();  
}  

效果如下:


BounceInterpolator.gif

Interpolator內部實現(xiàn)機制

首先看一下TimeInterpolator的接口定義,代碼如下所示:

/** 
 * A time interpolator defines the rate of change of an animation. This allows animations 
 * to have non-linear motion, such as acceleration and deceleration. 
 */  
public interface TimeInterpolator {  
  
    /** 
     * Maps a value representing the elapsed fraction of an animation to a value that represents 
     * the interpolated fraction. This interpolated value is then multiplied by the change in 
     * value of an animation to derive the animated value at the current elapsed animation time. 
     * 
     * @param input A value between 0 and 1.0 indicating our current point 
     *        in the animation where 0 represents the start and 1.0 represents 
     *        the end 
     * @return The interpolation value. This value can be more than 1.0 for 
     *         interpolators which overshoot their targets, or less than 0 for 
     *         interpolators that undershoot their targets. 
     */  
    float getInterpolation(float input);  
}  

只有一個getInterpolation()方法匹中。
getInterpolation()方法中接收一個input參數(shù)夏漱,這個參數(shù)的值會隨著動畫的運行而不斷變化,不過它的變化是非常有規(guī)律的顶捷,就是根據(jù)設定的動畫時長勻速增加挂绰,變化范圍是0到1。也就是說當動畫一開始的時候input的值是0服赎,到動畫結束的時候input的值是1葵蒂,而中間的值則是隨著動畫運行的時長在0到1之間變化的。

input的值決定了上文中fraction的值重虑。
input的值是由系統(tǒng)經(jīng)過計算傳入getInterpolation()方法中的践付,然后我們可以自己實現(xiàn)getInterpolation()方法中的算法,根據(jù)input的值來計算出一個返回值缺厉,而這個返回值就是fraction了永高。
因此,最簡單的情況就是input值和fraction值是相同的提针,這種情況由于input值是勻速增加的命爬,因而fraction的值也是勻速增加的,所以動畫的運動情況也是勻速的辐脖。

系統(tǒng)中內置的LinearInterpolator就是一種勻速運動的Interpolator饲宛,那么我們來看一下它的源碼是怎么實現(xiàn)的:

/** 
 * An interpolator where the rate of change is constant 
 */  
@HasNativeInterpolator  
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {  
  
    public LinearInterpolator() {  
    }  
  
    public LinearInterpolator(Context context, AttributeSet attrs) {  
    }  
  
    public float getInterpolation(float input) {  
        return input;  //把參數(shù)中傳遞的input值直接返回了,因此fraction的值就是等于input的值的嗜价,這就是勻速運動的Interpolator的實現(xiàn)方式艇抠。
    }  
  
    /** @hide */  
    @Override  
    public long createNativeInterpolator() {  
        return NativeInterpolatorFactoryHelper.createLinearInterpolator();  
    }  
}  

當然這是最簡單的一種Interpolator的實現(xiàn)了,我們再來看一個稍微復雜一點的久锥。既然現(xiàn)在大家都知道了系統(tǒng)在默認情況下使用的是AccelerateDecelerateInterpolator家淤,那我們就來看一下它的源碼吧,如下所示:

/** 
 * An interpolator where the rate of change starts and ends slowly but 
 * accelerates through the middle. 
 *  
 */  
@HasNativeInterpolator  
public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {  
    public AccelerateDecelerateInterpolator() {  
    }  
      
    @SuppressWarnings({"UnusedDeclaration"})  
    public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {  
    }  
      
    public float getInterpolation(float input) {  
        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;  
        /*算法中主要使用了余弦函數(shù)奴拦,由于input的取值范圍是0到1媒鼓,那么cos函數(shù)中的取值范圍就是π到2π。而cos(π)的結果是-1错妖,cos(2π)的結果是1绿鸣,那么這個值再除以2加上0.5之后,getInterpolation()方法最終返回的結果值還是在0到1之間暂氯。只不過經(jīng)過了余弦運算之后潮模,最終的結果不再是勻速增加的了,而是經(jīng)歷了一個先加速后減速的過程痴施。*/
    }  
  
    /** @hide */  
    @Override  
    public long createNativeInterpolator() {  
        return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();  
    }  
}  

getInterpolation()方法中的邏輯變復雜了擎厢,我們可以將這個算法的執(zhí)行情況通過曲線圖的方式繪制出來究流,結果如下圖所示:


可以看到,這是一個S型的曲線圖动遭,當橫坐標從0變化到0.2的時候芬探,縱坐標的變化幅度很小,但是之后就開始明顯加速厘惦,最后橫坐標從0.8變化到1的時候偷仿,縱坐標的變化幅度又變得很小。

自定義Interpolator

實現(xiàn):先減速后加速Interpolator
新建DecelerateAccelerateInterpolator類宵蕉,讓它實現(xiàn)TimeInterpolator接口酝静,代碼如下所示:

public class DecelerateAccelerateInterpolator implements TimeInterpolator{  
    @Override  
    public float getInterpolation(float input) {  
        float result;  
        if (input <= 0.5) {  
            result = (float) (Math.sin(Math.PI * input)) / 2;  
        } else {  
            result = (float) (2 - Math.sin(Math.PI * input)) / 2;  
        }  
        return result;  
    }  
}  

這段代碼是使用正弦函數(shù)來實現(xiàn)先減速后加速的功能的,因為正弦函數(shù)初始弧度的變化值非常大羡玛,剛好和余弦函數(shù)是相反的别智,而隨著弧度的增加,正弦函數(shù)的變化值也會逐漸變小稼稿,這樣也就實現(xiàn)了減速的效果薄榛。當弧度大于π/2之后,整個過程相反了過來渺杉,現(xiàn)在正弦函數(shù)的弧度變化值非常小蛇数,漸漸隨著弧度繼續(xù)增加,變化值越來越大是越,弧度到π時結束,這樣從0過度到π碌上,也就實現(xiàn)了先減速后加速的效果倚评。

同樣我們可以將這個算法的執(zhí)行情況通過曲線圖的方式繪制出來,結果如下圖所示:


可以看到馏予,這也是一個S型的曲線圖天梧,只不過曲線的方向和剛才是相反的。從上圖中我們可以很清楚地看出來霞丧,一開始縱坐標的變化幅度很大呢岗,然后逐漸變小,橫坐標到0.5的時候縱坐標變化幅度趨近于零蛹尝,之后隨著橫坐標繼續(xù)增加縱坐標的變化幅度又開始變大后豫,的確是先減速后加速的效果。

那么現(xiàn)在我們將DecelerateAccelerateInterpolator在代碼中進行替換突那,如下所示:

private void startAnimation() {  
    Point startPoint = new Point(getWidth() / 2, RADIUS);  
    Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);  
    ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);  
    anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {  
        @Override  
        public void onAnimationUpdate(ValueAnimator animation) {  
            currentPoint = (Point) animation.getAnimatedValue();  
            invalidate();  
        }  
    });  
    anim.setInterpolator(new DecelerateAccelerateInterpolator());//替換成自定義Interpolator
    anim.setDuration(3000);  
    anim.start();  
}  

非常簡單挫酿,就是將DecelerateAccelerateInterpolator的實例傳入到setInterpolator()方法當中。重新運行一下代碼愕难,效果如下圖所示:


ViewPropertyAnimator

它并不是在3.0系統(tǒng)當中引入的早龟,而是在3.1系統(tǒng)當中附增的一個新的功能惫霸。為View的動畫操作提供一種更加便捷的用法。

屬性動畫的機制已經(jīng)不是再針對于View而進行設計的了葱弟,而是一種不斷地對值進行操作的機制壹店,它可以將值賦值到指定對象的指定屬性上。但是芝加,在絕大多數(shù)情況下硅卢,我相信大家主要都還是對View進行動畫操作的。

那我們先來回顧一下之前的用法吧妖混,比如我們想要讓一個TextView從常規(guī)狀態(tài)變成透明狀態(tài)老赤,就可以這樣寫:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 0f);  
animator.start();  

用法

使用ViewPropertyAnimator來實現(xiàn)同樣的效果,ViewPropertyAnimator提供了更加易懂制市、更加面向對象的API抬旺,如下所示:

textview.animate().alpha(0f);  //將當前的textview變成透明狀態(tài)

animate()方法就是在Android 3.1系統(tǒng)上新增的一個方法,這個方法的返回值是一個ViewPropertyAnimator對象祥楣,也就是說拿到這個對象之后我們就可以調用它的各種方法來實現(xiàn)動畫效果了开财,這里我們調用了alpha()方法并轉入0,表示將當前的textview變成透明狀態(tài)误褪。

ViewPropertyAnimator還可以很輕松地將多個動畫組合到一起责鳍,比如我們想要讓textview運動到500,500這個坐標點上,就可以這樣寫:

textview.animate().x(500).y(500);  //讓textview運動到500,500這個坐標點

ViewPropertyAnimator是支持連綴用法的兽间,我們想讓textview移動到橫坐標500這個位置上時調用了x(500)這個方法历葛,然后讓textview移動到縱坐標500這個位置上時調用了y(500)這個方法,將所有想要組合的動畫通過這種連綴的方式拼接起來嘀略,這樣全部動畫就都會一起被執(zhí)行恤溶。

設定動畫的運行時長:

textview.animate().x(500).y(500).setDuration(5000);  //設定動畫的運行時長

Interpolator也可以應用在ViewPropertyAnimator上面:

textview.animate().x(500).y(500).setDuration(5000)  
        .setInterpolator(new BounceInterpolator());  //使用Interpolator

ViewPropertyAnimator用法基本大同小異,需要用到什么功能就連綴一下帜羊,因此更多的用法大家只需要去查閱一下文檔咒程,看看還支持哪些功能,有哪些接口可以調用就可以了讼育。

注意

整個ViewPropertyAnimator的功能都是建立在View類新增的animate()方法之上的帐姻,這個方法會創(chuàng)建并返回一個ViewPropertyAnimator的實例,之后的調用的所有方法奶段,設置的所有屬性都是通過這個實例完成的饥瓷。
  在使用ViewPropertyAnimator時,我們自始至終沒有調用過start()方法忧饭,這是因為新的接口中使用了隱式啟動動畫的功能扛伍,只要我們將動畫定義完成之后,動畫就會自動啟動词裤。并且這個機制對于組合動畫也同樣有效刺洒,只要我們不斷地連綴新的方法鳖宾,那么動畫就不會立刻執(zhí)行,等到所有在ViewPropertyAnimator上設置的方法都執(zhí)行完畢后逆航,動畫就會自動啟動鼎文。當然如果不想使用這一默認機制的話,我們也可以顯式地調用start()方法來啟動動畫因俐。
  ViewPropertyAnimator的所有接口都是使用連綴的語法來設計的拇惋,每個方法的返回值都是它自身的實例,因此調用完一個方法之后可以直接連綴調用它的另一個方法抹剩,這樣把所有的功能都串接起來撑帖,我們甚至可以僅通過一行代碼就完成任意復雜度的動畫功能。

動畫常見問題

修改 Activity 進入和退出動畫

可以通過兩種方式澳眷,一是通過定義 Activity 的主題胡嘿,二是通過覆寫 Activity 的 overridePendingTransition 方法。
方式1:通過設置主題樣式
在 styles.xml 中編輯如下代碼:

<style name="AnimationActivity" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/slide_in_left</item>
<item name="android:activityOpenExitAnimation">@anim/slide_out_left</item>
<item name="android:activityCloseEnterAnimation">@anim/slide_in_right</item>
<item name="android:activityCloseExitAnimation">@anim/slide_out_right</item>
</style>

添加 themes.xml 文件:

<style name="ThemeActivity">
<item name="android:windowAnimationStyle">@style/AnimationActivity</item>
<item name="android:windowNoTitle">true</item>
</style>

在 AndroidManifest.xml 中給指定的 Activity 指定 theme钳踊。

方式2:覆寫 overridePendingTransition 方法
overridePendingTransition(R.anim.fade, R.anim.hold);

屬性動畫衷敌,例如一個 button 從 A 移動到 B 點,B 點還是可以響應點擊事件拓瞪,這個原理是什么缴罗?

補間動畫只是顯示的位置變動,View 的實際位置未改變祭埂,表現(xiàn)為 View 移動到其他地方面氓,點擊事件仍在原處才能響應。而屬性動畫控件移動后事件相應就在控件移動后本身進行處理蛆橡。

引用:
Android屬性動畫完全解析(上)侧但,初識屬性動畫的基本用法
Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的高級用法
Android屬性動畫完全解析(下)航罗,Interpolator和ViewPropertyAnimator的用法

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市屁药,隨后出現(xiàn)的幾起案子粥血,更是在濱河造成了極大的恐慌,老刑警劉巖酿箭,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件复亏,死亡現(xiàn)場離奇詭異,居然都是意外死亡缭嫡,警方通過查閱死者的電腦和手機缔御,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來妇蛀,“玉大人耕突,你說我怎么就攤上這事笤成。” “怎么了眷茁?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵炕泳,是天一觀的道長。 經(jīng)常有香客問我上祈,道長培遵,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任登刺,我火速辦了婚禮籽腕,結果婚禮上,老公的妹妹穿的比我還像新娘纸俭。我一直安慰自己皇耗,他們只是感情好,可當我...
    茶點故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布掉蔬。 她就那樣靜靜地躺著廊宪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪女轿。 梳的紋絲不亂的頭發(fā)上箭启,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天,我揣著相機與錄音蛉迹,去河邊找鬼傅寡。 笑死,一個胖子當著我的面吹牛北救,可吹牛的內容都是我干的荐操。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼珍策,長吁一口氣:“原來是場噩夢啊……” “哼托启!你這毒婦竟也來了?” 一聲冷哼從身側響起攘宙,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤屯耸,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后蹭劈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疗绣,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年铺韧,在試婚紗的時候發(fā)現(xiàn)自己被綠了多矮。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,100評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡哈打,死狀恐怖塔逃,靈堂內的尸體忽然破棺而出讯壶,到底是詐尸還是另有隱情,我是刑警寧澤患雏,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布鹏溯,位于F島的核電站,受9級特大地震影響淹仑,放射性物質發(fā)生泄漏丙挽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一匀借、第九天 我趴在偏房一處隱蔽的房頂上張望颜阐。 院中可真熱鬧,春花似錦吓肋、人聲如沸凳怨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肤舞。三九已至,卻和暖如春均蜜,著一層夾襖步出監(jiān)牢的瞬間李剖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工囤耳, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留篙顺,地道東北人。 一個月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓充择,卻偏偏與公主長得像德玫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子椎麦,可洞房花燭夜當晚...
    茶點故事閱讀 42,834評論 2 345