Android 動(dòng)畫

動(dòng)畫基礎(chǔ)概念

動(dòng)畫分類

Android 中動(dòng)畫分為兩種,一種是 Tween 動(dòng)畫、還有一種是 Frame 動(dòng)畫。

補(bǔ)間動(dòng)畫(Tween動(dòng)畫)

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

幀動(dòng)畫(Frame 動(dòng)畫)

傳統(tǒng)的動(dòng)畫方法,通過(guò)順序的播放排列好的圖片來(lái)實(shí)現(xiàn),類似電影、gif。

屬性動(dòng)畫(Property animation)

自Android 3.0版本開(kāi)始,系統(tǒng)給我們提供了一種全新的動(dòng)畫模式,屬性動(dòng)畫(property animation),它的功能非常強(qiáng)大,彌補(bǔ)了之前補(bǔ)間動(dòng)畫的一些缺陷,幾乎是可以完全替代掉補(bǔ)間動(dòng)畫了。

補(bǔ)間動(dòng)畫(Tween動(dòng)畫)

1、透明度動(dòng)畫AlphaAnimation

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

            @Override
            public void onAnimationStart(Animation animation) {
                System.out.println("動(dòng)畫開(kāi)始回調(diào)");
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
                System.out.println("動(dòng)畫重復(fù)回調(diào)");

            }

            @Override
            public void onAnimationEnd(Animation animation) {
                System.out.println("動(dòng)畫結(jié)束回調(diào)");
                Toast.makeText(getApplicationContext(), "動(dòng)畫結(jié)束,進(jìn)入陌陌關(guān)心你界面",
                        Toast.LENGTH_LONG).show();

            }
        });
        iconIv.startAnimation(alphaAnimation);

    }

2、平移動(dòng)畫TranslateAnimation

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

    }

3、縮放動(dòng)畫ScaleAnimation

/* ScaleAnimation (float fromX, 
                float toX, 
                float fromY, 
                float toY, 
                int pivotXType, 
                float pivotXValue, 
                int pivotYType, 
                float pivotYValue)
    fromX: 縮放起始比例-水平方向
    toX: 縮放最終比例-水平方向
    pivotXType(中心點(diǎn)相較于原點(diǎn) x方向的類型): 
            Animation.ABSOLUTE
            Animation.RELATIVE_TO_SELF
            RELATIVE_TO_PARENT.
    pivotXValue: 絕對(duì)值/百分比    
*/
    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);// 每次動(dòng)畫持續(xù)時(shí)間3秒
        scaleAnimation.setFillAfter(true);// 動(dòng)畫最后停留在終止的狀態(tài)
        scaleAnimation.setRepeatCount(1);// 動(dòng)畫重復(fù)的次數(shù)
        iconIv.startAnimation(scaleAnimation);

    }

4、旋轉(zhuǎn)動(dòng)畫 rotate

創(chuàng)建一個(gè)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:無(wú)限次數(shù) 
      起始度數(shù)大于終止度數(shù),則能逆時(shí)針旋轉(zhuǎn),否則順時(shí)針
      android:pivotX="50%":旋轉(zhuǎn)圍繞的軸心,x方向位置,相對(duì)于自己的寬度的一半
      android:pivotX="50%p":相對(duì)于父控件寬度的一半
      -->
    

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

復(fù)合動(dòng)畫

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

幀動(dòng)畫(Frame 動(dòng)畫)

方式一;使用XML的方式;

1、創(chuàng)建一個(gè)AnimationDrawable 的XML文件;

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false" >//這個(gè)是反復(fù)執(zhí)行的設(shè)置;
     <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對(duì)象
    android:duration[long]//每一幀動(dòng)畫的持續(xù)時(shí)間(單位ms)
    android:oneshot[boolean]//動(dòng)畫是否只運(yùn)行一次,true運(yùn)行一次,false重復(fù)運(yùn)行
    android:visible[boolean]//Drawable對(duì)象的初始能見(jiàn)度狀態(tài),true可見(jiàn),false不可見(jiàn)(默認(rèn)為false)-->

2、第二步就是需要將這個(gè)幀動(dòng)畫設(shè)置到一個(gè)容器中去;imageview;
android:src="@drawable/animation" />

3、可以從控件中獲取到這個(gè)幀動(dòng)畫的圖片然后再對(duì)他進(jìn)行操作;

drawable = (AnimationDrawable) imageView.getDrawable();

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

方式二:使用代碼的方式進(jìn)行;

方式1:添加多個(gè)幀 Drawable

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

        mImageView.setImageDrawable(mAnimationDrawable);//設(shè)置幀動(dòng)畫,默認(rèn)是停止?fàn)顟B(tài)

方式2:引用xml 幀動(dòng)畫drawable

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

屬性動(dòng)畫(Property animation)

為什么要引入屬性動(dòng)畫?

Android之前的補(bǔ)間動(dòng)畫機(jī)制其實(shí)還算是比較健全的,在android.view.animation包下面有好多的類可以供我們操作,來(lái)完成一系列的動(dòng)畫效果,比如說(shuō)對(duì)View進(jìn)行移動(dòng)、縮放、旋轉(zhuǎn)和淡入淡出,并且我們還可以借助AnimationSet來(lái)將這些動(dòng)畫效果組合起來(lái)使用,除此之外還可以通過(guò)配置Interpolator來(lái)控制動(dòng)畫的播放速度等等等等。那么這里大家可能要產(chǎn)生疑問(wèn)了,既然之前的動(dòng)畫機(jī)制已經(jīng)這么健全了,為什么還要引入屬性動(dòng)畫呢?

其實(shí)上面所謂的健全都是相對(duì)的,如果你的需求中只需要對(duì)View進(jìn)行移動(dòng)、縮放、旋轉(zhuǎn)和淡入淡出操作,那么補(bǔ)間動(dòng)畫確實(shí)已經(jīng)足夠健全了。但是很顯然,這些功能是不足以覆蓋所有的場(chǎng)景的,一旦我們的需求超出了移動(dòng)、縮放、旋轉(zhuǎn)和淡入淡出這四種對(duì)View的操作,那么補(bǔ)間動(dòng)畫就不能再幫我們忙了,也就是說(shuō)它在功能和可擴(kuò)展方面都有相當(dāng)大的局限性,那么下面我們就來(lái)看看補(bǔ)間動(dòng)畫所不能勝任的場(chǎng)景。

注意上面我在介紹補(bǔ)間動(dòng)畫的時(shí)候都有使用“對(duì)View進(jìn)行操作”這樣的描述,沒(méi)錯(cuò),補(bǔ)間動(dòng)畫是只能夠作用在View上的。也就是說(shuō),我們可以對(duì)一個(gè)Button、TextView、甚至是LinearLayout、或者其它任何繼承自View的組件進(jìn)行動(dòng)畫操作,但是如果我們想要對(duì)一個(gè)非View的對(duì)象進(jìn)行動(dòng)畫操作,抱歉,補(bǔ)間動(dòng)畫就幫不上忙了??赡苡械呐笥褧?huì)感到不能理解,我怎么會(huì)需要對(duì)一個(gè)非View的對(duì)象進(jìn)行動(dòng)畫操作呢?這里我舉一個(gè)簡(jiǎn)單的例子,比如說(shuō)我們有一個(gè)自定義的View,在這個(gè)View當(dāng)中有一個(gè)Point對(duì)象用于管理坐標(biāo),然后在onDraw()方法當(dāng)中就是根據(jù)這個(gè)Point對(duì)象的坐標(biāo)值來(lái)進(jìn)行繪制的。也就是說(shuō),如果我們可以對(duì)Point對(duì)象進(jìn)行動(dòng)畫操作,那么整個(gè)自定義View的動(dòng)畫效果就有了。顯然,補(bǔ)間動(dòng)畫是不具備這個(gè)功能的,這是它的第一個(gè)缺陷。

然后補(bǔ)間動(dòng)畫還有一個(gè)缺陷,就是它只能夠?qū)崿F(xiàn)移動(dòng)、縮放、旋轉(zhuǎn)和淡入淡出這四種動(dòng)畫操作,那如果我們希望可以對(duì)View的背景色進(jìn)行動(dòng)態(tài)地改變呢?很遺憾,我們只能靠自己去實(shí)現(xiàn)了。說(shuō)白了,之前的補(bǔ)間動(dòng)畫機(jī)制就是使用硬編碼的方式來(lái)完成的,功能限定死就是這些,基本上沒(méi)有任何擴(kuò)展性可言。

最后,補(bǔ)間動(dòng)畫還有一個(gè)致命的缺陷,就是它只是改變了View的顯示效果而已,而不會(huì)真正去改變View的屬性。什么意思呢?比如說(shuō),現(xiàn)在屏幕的左上角有一個(gè)按鈕,然后我們通過(guò)補(bǔ)間動(dòng)畫將它移動(dòng)到了屏幕的右下角,現(xiàn)在你可以去嘗試點(diǎn)擊一下這個(gè)按鈕,點(diǎn)擊事件是絕對(duì)不會(huì)觸發(fā)的,因?yàn)閷?shí)際上這個(gè)按鈕還是停留在屏幕的左上角,只不過(guò)補(bǔ)間動(dòng)畫將這個(gè)按鈕繪制到了屏幕的右下角而已。

也正是因?yàn)檫@些原因,Android開(kāi)發(fā)團(tuán)隊(duì)決定在3.0版本當(dāng)中引入屬性動(dòng)畫這個(gè)功能,那么屬性動(dòng)畫是不是就把上述的問(wèn)題全部解決掉了?下面我們就來(lái)一起看一看。

新引入的屬性動(dòng)畫機(jī)制已經(jīng)不再是針對(duì)于View來(lái)設(shè)計(jì)的了,也不限定于只能實(shí)現(xiàn)移動(dòng)、縮放、旋轉(zhuǎn)和淡入淡出這幾種動(dòng)畫操作,同時(shí)也不再只是一種視覺(jué)上的動(dòng)畫效果了。它實(shí)際上是一種不斷地對(duì)值進(jìn)行操作的機(jī)制,并將值賦值到指定對(duì)象的指定屬性上,可以是任意對(duì)象的任意屬性。所以我們?nèi)匀豢梢詫⒁粋€(gè)View進(jìn)行移動(dòng)或者縮放,但同時(shí)也可以對(duì)自定義View中的Point對(duì)象進(jìn)行動(dòng)畫操作了。我們只需要告訴系統(tǒng)動(dòng)畫的運(yùn)行時(shí)長(zhǎng),需要執(zhí)行哪種類型的動(dòng)畫,以及動(dòng)畫的初始值和結(jié)束值,剩下的工作就可以全部交給系統(tǒng)去完成了。

既然屬性動(dòng)畫的實(shí)現(xiàn)機(jī)制是通過(guò)對(duì)目標(biāo)對(duì)象進(jìn)行賦值并修改其屬性來(lái)實(shí)現(xiàn)的,那么之前所說(shuō)的按鈕顯示的問(wèn)題也就不復(fù)存在了,如果我們通過(guò)屬性動(dòng)畫來(lái)移動(dòng)一個(gè)按鈕,那么這個(gè)按鈕就是真正的移動(dòng)了,而不再是僅僅在另外一個(gè)位置繪制了而已。

ValueAnimator

ValueAnimator是整個(gè)屬性動(dòng)畫機(jī)制當(dāng)中最核心的一個(gè)類,前面我們已經(jīng)提到了,屬性動(dòng)畫的運(yùn)行機(jī)制是通過(guò)不斷地對(duì)值進(jìn)行操作來(lái)實(shí)現(xiàn)的,而初始值和結(jié)束值之間的動(dòng)畫過(guò)渡就是由ValueAnimator這個(gè)類來(lái)負(fù)責(zé)計(jì)算的。
它的內(nèi)部使用一種時(shí)間循環(huán)的機(jī)制來(lái)計(jì)算值與值之間的動(dòng)畫過(guò)渡,我們只需要將初始值和結(jié)束值提供給ValueAnimator,并且告訴它動(dòng)畫所需運(yùn)行的時(shí)長(zhǎng),那么ValueAnimator就會(huì)自動(dòng)幫我們完成從初始值平滑地過(guò)渡到結(jié)束值這樣的效果。除此之外,ValueAnimator還負(fù)責(zé)管理動(dòng)畫的播放次數(shù)、播放模式、以及對(duì)動(dòng)畫設(shè)置監(jiān)聽(tīng)器等,確實(shí)是一個(gè)非常重要的類。

但是ValueAnimator的用法卻一點(diǎn)都不復(fù)雜,我們先從最簡(jiǎn)單的功能看起吧,比如說(shuō)想要將一個(gè)值從0平滑過(guò)渡到1,時(shí)長(zhǎng)300毫秒,就可以這樣寫:

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

怎么樣?很簡(jiǎn)單吧,調(diào)用ValueAnimator的ofFloat()方法就可以構(gòu)建出一個(gè)ValueAnimator的實(shí)例,ofFloat()方法當(dāng)中允許傳入多個(gè)float類型的參數(shù),這里傳入0和1就表示將值從0平滑過(guò)渡到1,然后調(diào)用ValueAnimator的setDuration()方法來(lái)設(shè)置動(dòng)畫運(yùn)行的時(shí)長(zhǎng),最后調(diào)用start()方法啟動(dòng)動(dòng)畫。

用法就是這么簡(jiǎn)單,現(xiàn)在如果你運(yùn)行一下上面的代碼,動(dòng)畫就會(huì)執(zhí)行了??墒沁@只是一個(gè)將值從0過(guò)渡到1的動(dòng)畫,又看不到任何界面效果,我們?cè)鯓硬拍苤肋@個(gè)動(dòng)畫是不是已經(jīng)真正運(yùn)行了呢?這就需要借助監(jiān)聽(tīng)器來(lái)實(shí)現(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();  

可以看到,這里我們通過(guò)addUpdateListener()方法來(lái)添加一個(gè)動(dòng)畫的監(jiān)聽(tīng)器,在動(dòng)畫執(zhí)行的過(guò)程中會(huì)不斷地進(jìn)行回調(diào),我們只需要在回調(diào)方法當(dāng)中將當(dāng)前的值取出并打印出來(lái),就可以知道動(dòng)畫有沒(méi)有真正運(yùn)行了。運(yùn)行上述代碼,控制臺(tái)打印如下所示:

從打印日志的值我們就可以看出,ValueAnimator確實(shí)已經(jīng)在正常工作了,值在300毫秒的時(shí)間內(nèi)從0平滑過(guò)渡到了1,而這個(gè)計(jì)算工作就是由ValueAnimator幫助我們完成的。另外ofFloat()方法當(dāng)中是可以傳入任意多個(gè)參數(shù)的,因此我們還可以構(gòu)建出更加復(fù)雜的動(dòng)畫邏輯,比如說(shuō)將一個(gè)值在5秒內(nèi)從0過(guò)渡到5,再過(guò)渡到3,再過(guò)渡到10,就可以這樣寫:

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

當(dāng)然也許你并不需要小數(shù)位數(shù)的動(dòng)畫過(guò)渡,可能你只是希望將一個(gè)整數(shù)值從0平滑地過(guò)渡到100,那么也很簡(jiǎn)單,只需要調(diào)用ValueAnimator的ofInt()方法就可以了,如下所示:

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

常用方法

ValueAnimator當(dāng)中最常用的應(yīng)該就是ofFloat()和ofInt()這兩個(gè)方法了,另外還有一個(gè)ofObject()方法,我會(huì)在下篇文章進(jìn)行講解。

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

ObjectAnimator

對(duì)任意對(duì)象的任意屬性進(jìn)行動(dòng)畫操作

相比于ValueAnimator,ObjectAnimator可能才是我們最常接觸到的類,因?yàn)閂alueAnimator只不過(guò)是對(duì)值進(jìn)行了一個(gè)平滑的動(dòng)畫過(guò)渡,但我們實(shí)際使用到這種功能的場(chǎng)景好像并不多。而ObjectAnimator則就不同了,它是可以直接任意對(duì)象的任意屬性進(jìn)行動(dòng)畫操作的,比如說(shuō)View的alpha屬性。

不過(guò)雖說(shuō)ObjectAnimator會(huì)更加常用一些,但是它其實(shí)是繼承自ValueAnimator的,底層的動(dòng)畫實(shí)現(xiàn)機(jī)制也是基于ValueAnimator來(lái)完成的,因此ValueAnimator仍然是整個(gè)屬性動(dòng)畫當(dāng)中最核心的一個(gè)類。

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.

那么既然是繼承關(guān)系,說(shuō)明ValueAnimator中可以使用的方法在ObjectAnimator中也是可以正常使用的,它們的用法也非常類似。

alpha

這里如果我們想要將一個(gè)TextView在5秒中內(nèi)從常規(guī)變換成全透明,再?gòu)娜该髯儞Q成常規(guī),就可以這樣寫:

ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f, 1f);  
/*
參數(shù)1:傳入一個(gè)object對(duì)象,我們想要對(duì)哪個(gè)對(duì)象進(jìn)行動(dòng)畫操作就傳入什么,這里我傳入了一個(gè)textview。
參數(shù)2:想要對(duì)該對(duì)象的哪個(gè)屬性進(jìn)行動(dòng)畫操作,由于我們想要改變TextView的不透明度,因此這里傳入"alpha"。
參數(shù)3:不固定長(zhǎng)度,想要完成什么樣的動(dòng)畫就傳入什么值,這里傳入的值就表示將TextView從常規(guī)變換成全透明,再?gòu)娜该髯儞Q成常規(guī)。
*/
animator.setDuration(5000);  
animator.start();  
rotation

學(xué)會(huì)了這一個(gè)用法之后,其它的用法我們就可以舉一反三了,那比如說(shuō)我們想要將TextView進(jìn)行一次360度的旋轉(zhuǎn),就可以這樣寫:

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

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


translationX

那么如果想要將TextView先向左移出屏幕,然后再移動(dòng)回來(lái),就可以這樣寫:

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

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


scaleY

然后我們還可以TextView進(jìn)行縮放操作,比如說(shuō)將TextView在垂直方向上放大3倍再還原,就可以這樣寫:

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

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


工作機(jī)制

ofFloat()方法的第二個(gè)參數(shù)到底可以傳哪些值呢?目前我們使用過(guò)了alpha、rotation、translationX和scaleY這幾個(gè)值,分別可以完成淡入淡出、旋轉(zhuǎn)、水平移動(dòng)、垂直縮放這幾種動(dòng)畫,那么還有哪些值是可以使用的呢?

我們可以傳入任意的值到ofFloat()方法的第二個(gè)參數(shù)當(dāng)中。

因?yàn)镺bjectAnimator在設(shè)計(jì)的時(shí)候就沒(méi)有針對(duì)于View來(lái)進(jìn)行設(shè)計(jì),而是針對(duì)于任意對(duì)象的,它所負(fù)責(zé)的工作就是不斷地向某個(gè)對(duì)象中的某個(gè)屬性進(jìn)行賦值,然后對(duì)象根據(jù)屬性值的改變?cè)賮?lái)決定如何展現(xiàn)出來(lái)。

那么比如說(shuō)我們調(diào)用下面這樣一段代碼:

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

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

那么textview對(duì)象中是不是有alpha屬性這個(gè)值呢?沒(méi)有,不僅textview沒(méi)有這個(gè)屬性,連它所有的父類也是沒(méi)有這個(gè)屬性的!這就奇怪了,textview當(dāng)中并沒(méi)有alpha這個(gè)屬性,ObjectAnimator是如何進(jìn)行操作的呢?其實(shí)ObjectAnimator內(nèi)部的工作機(jī)制并不是直接對(duì)我們傳入的屬性名進(jìn)行操作的,而是會(huì)去尋找這個(gè)屬性名對(duì)應(yīng)的get和set方法,因此alpha屬性所對(duì)應(yīng)的get和set方法應(yīng)該就是:

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

那么textview對(duì)象中是否有這兩個(gè)方法呢?確實(shí)有,并且這兩個(gè)方法是由View對(duì)象提供的,也就是說(shuō)不僅TextView可以使用這個(gè)屬性來(lái)進(jìn)行淡入淡出動(dòng)畫操作,任何繼承自View的對(duì)象都可以的。

既然alpha是這個(gè)樣子,相信大家一定已經(jīng)明白了,前面我們所用的所有屬性都是這個(gè)工作原理,那么View當(dāng)中一定也存在著setRotation()、getRotation()、setTranslationX()、getTranslationX()、setScaleY()、getScaleY()這些方法,不信的話你可以到View當(dāng)中去找一下。

組合動(dòng)畫-AnimatorSet

實(shí)現(xiàn)組合動(dòng)畫功能主要需要借助AnimatorSet這個(gè)類,這個(gè)類提供了一個(gè)play()方法,如果我們向這個(gè)方法中傳入一個(gè)Animator對(duì)象(ValueAnimator或ObjectAnimator)將會(huì)返回一個(gè)AnimatorSet.Builder的實(shí)例。

AnimatorSet.Builder

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

好的,有了這四個(gè)方法,我們就可以完成組合動(dòng)畫的邏輯了,那么比如說(shuō)我們想要讓TextView先從屏幕外移動(dòng)進(jìn)屏幕,然后開(kāi)始旋轉(zhuǎn)360度,旋轉(zhuǎn)的同時(shí)進(jìn)行淡入淡出操作,就可以這樣寫:

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

可以看到,這里我們先是把三個(gè)動(dòng)畫的對(duì)象全部創(chuàng)建出來(lái),然后new出一個(gè)AnimatorSet對(duì)象之后將這三個(gè)動(dòng)畫對(duì)象進(jìn)行播放排序,讓旋轉(zhuǎn)和淡入淡出動(dòng)畫同時(shí)進(jìn)行,并把它們插入到了平移動(dòng)畫的后面,最后是設(shè)置動(dòng)畫時(shí)長(zhǎng)以及啟動(dòng)動(dòng)畫。運(yùn)行一下上述代碼,效果如下圖所示:


Animator監(jiān)聽(tīng)器

AnimatorListener

Animator類當(dāng)中提供了一個(gè)addListener()方法,這個(gè)方法接收一個(gè)AnimatorListener,我們只需要去實(shí)現(xiàn)這個(gè)AnimatorListener就可以監(jiān)聽(tīng)動(dòng)畫的各種事件了。

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

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

anim.addListener(new AnimatorListener() {  
    @Override  
    public void onAnimationStart(Animator animation) {  
        //動(dòng)畫開(kāi)始的時(shí)候調(diào)用
    }  
  
    @Override  
    public void onAnimationRepeat(Animator animation) {  
        //動(dòng)畫重復(fù)執(zhí)行的時(shí)候調(diào)用
    }  
  
    @Override  
    public void onAnimationEnd(Animator animation) {  
        //動(dòng)畫結(jié)束的時(shí)候調(diào)用
    }  
  
    @Override  
    public void onAnimationCancel(Animator animation) {  
        //動(dòng)畫被取消的時(shí)候調(diào)用
    }  
});  

可以看到,我們需要實(shí)現(xiàn)接口中的四個(gè)方法,onAnimationStart()方法會(huì)在動(dòng)畫開(kāi)始的時(shí)候調(diào)用,onAnimationRepeat()方法會(huì)在動(dòng)畫重復(fù)執(zhí)行的時(shí)候調(diào)用,onAnimationEnd()方法會(huì)在動(dòng)畫結(jié)束的時(shí)候調(diào)用,onAnimationCancel()方法會(huì)在動(dòng)畫被取消的時(shí)候調(diào)用。

AnimatorListenerAdapter

但是也許很多時(shí)候我們并不想要監(jiān)聽(tīng)那么多個(gè)事件,可能我只想要監(jiān)聽(tīng)動(dòng)畫結(jié)束這一個(gè)事件,那么每次都要將四個(gè)接口全部實(shí)現(xiàn)一遍就顯得非常繁瑣。沒(méi)關(guān)系,為此Android提供了一個(gè)適配器類,叫作AnimatorListenerAdapter,使用這個(gè)類就可以解決掉實(shí)現(xiàn)接口繁瑣的問(wèn)題了,如下所示:

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

這里我們向addListener()方法中傳入這個(gè)適配器對(duì)象,由于AnimatorListenerAdapter中已經(jīng)將每個(gè)接口都實(shí)現(xiàn)好了,所以這里不用實(shí)現(xiàn)任何一個(gè)方法也不會(huì)報(bào)錯(cuò)。那么如果我想監(jiān)聽(tīng)動(dòng)畫結(jié)束這個(gè)事件,就只需要單獨(dú)重寫這一個(gè)方法就可以了,如下所示:

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

使用XML編寫動(dòng)畫

我們可以使用代碼來(lái)編寫所有的動(dòng)畫功能,這也是最常用的一種做法。不過(guò),過(guò)去的補(bǔ)間動(dòng)畫除了使用代碼編寫之外也是可以使用XML編寫的,因此屬性動(dòng)畫也提供了這一功能,即通過(guò)XML來(lái)完成和代碼一樣的屬性動(dòng)畫功能。

通過(guò)XML來(lái)編寫動(dòng)畫可能會(huì)比通過(guò)代碼來(lái)編寫動(dòng)畫要慢一些,但是在重用方面將會(huì)變得非常輕松,比如某個(gè)將通用的動(dòng)畫編寫到XML里面,我們就可以在各個(gè)界面當(dāng)中輕松去重用它。

如果想要使用XML來(lái)編寫動(dòng)畫,首先要在res目錄下面新建一個(gè)animator文件夾,所有屬性動(dòng)畫的XML文件都應(yīng)該存放在這個(gè)文件夾當(dāng)中。

XML標(biāo)簽

animato

對(duì)應(yīng)代碼中的ValueAnimator

objectAnimator

對(duì)應(yīng)代碼中的ObjectAnimator

set

對(duì)應(yīng)代碼中的AnimatorSet

那么比如說(shuō)我們想要實(shí)現(xiàn)一個(gè)從0到100平滑過(guò)渡的動(dòng)畫,在XML當(dāng)中就可以這樣寫:

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

而如果我們想將一個(gè)視圖的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來(lái)完成復(fù)雜的組合動(dòng)畫操作,比如將一個(gè)視圖先從屏幕外移動(dòng)進(jìn)屏幕,然后開(kāi)始旋轉(zhuǎn)360度,旋轉(zhuǎn)的同時(shí)進(jìn)行淡入淡出操作,就可以這樣寫:

<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實(shí)現(xiàn)的效果和我們剛才通過(guò)代碼來(lái)實(shí)現(xiàn)的組合動(dòng)畫的效果是一模一樣的。

在代碼中加載XML文件-AnimatorInflater

在代碼中把文件加載進(jìn)來(lái)并將動(dòng)畫啟動(dòng):

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

調(diào)用AnimatorInflater的loadAnimator來(lái)將XML動(dòng)畫文件加載進(jìn)來(lái),然后再調(diào)用setTarget()方法將這個(gè)動(dòng)畫設(shè)置到某一個(gè)對(duì)象上面,最后再調(diào)用start()方法啟動(dòng)動(dòng)畫就可以了。

ValueAnimator的高級(jí)用法

目標(biāo):通過(guò)對(duì)對(duì)象進(jìn)行值操作來(lái)實(shí)現(xiàn)動(dòng)畫效果

實(shí)現(xiàn)目標(biāo)

比如說(shuō)我們有一個(gè)自定義的View,在這個(gè)View當(dāng)中有一個(gè)Point對(duì)象用于管理坐標(biāo),然后在onDraw()方法當(dāng)中就是根據(jù)這個(gè)Point對(duì)象的坐標(biāo)值來(lái)進(jìn)行繪制的。也就是說(shuō),如果我們可以對(duì)Point對(duì)象進(jìn)行動(dòng)畫操作,那么整個(gè)自定義View的動(dòng)畫效果就有了。

效果圖:
效果圖

TypeEvaluator

簡(jiǎn)單來(lái)說(shuō),就是告訴動(dòng)畫系統(tǒng)如何從初始值過(guò)度到結(jié)束值。

ValueAnimator.ofFloat()方法就是實(shí)現(xiàn)了初始值與結(jié)束值之間的平滑過(guò)度,那么這個(gè)平滑過(guò)度是怎么做到的呢?
其實(shí)就是系統(tǒng)內(nèi)置了一個(gè)FloatEvaluator,它通過(guò)計(jì)算告知?jiǎng)赢嬒到y(tǒng)如何從初始值過(guò)度到結(jié)束值,我們來(lái)看一下FloatEvaluator的代碼實(shí)現(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實(shí)現(xiàn)了TypeEvaluator接口,然后重寫evaluate()方法。
evaluate()方法當(dāng)中傳入了三個(gè)參數(shù):
參數(shù)1:fraction用于表示動(dòng)畫的完成度的,我們應(yīng)該根據(jù)它來(lái)計(jì)算當(dāng)前動(dòng)畫的值應(yīng)該是多少,
參數(shù)2、3:分別表示動(dòng)畫的初始值和結(jié)束值。
代碼邏輯:用結(jié)束值減去初始值,算出它們之間的差值,然后乘以fraction這個(gè)系數(shù),再加上初始值,那么就得到當(dāng)前動(dòng)畫的值了。

ValueAnimator的ofFloat()和ofInt()方法,分別用于對(duì)浮點(diǎn)型和整型的數(shù)據(jù)進(jìn)行動(dòng)畫操作的,ValueAnimator中還有一個(gè)ofObject()方法,是用于對(duì)任意對(duì)象進(jìn)行動(dòng)畫操作的。
但是相比于浮點(diǎn)型或整型數(shù)據(jù),對(duì)象的動(dòng)畫操作明顯要更復(fù)雜一些,因?yàn)橄到y(tǒng)將完全無(wú)法知道如何從初始對(duì)象過(guò)度到結(jié)束對(duì)象,因此這個(gè)時(shí)候我們就需要實(shí)現(xiàn)一個(gè)自己的TypeEvaluator來(lái)告知系統(tǒng)如何進(jìn)行過(guò)度。

實(shí)現(xiàn)步驟

先定義一個(gè)Point類:

Point類非常簡(jiǎn)單,只有x和y兩個(gè)變量用于記錄坐標(biāo)的位置,并提供了構(gòu)造方法來(lái)設(shè)置坐標(biāo),以及get方法來(lái)獲取坐標(biāo)。

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:

實(shí)現(xiàn)TypeEvaluator接口并重寫了evaluate()方法:先是將startValue和endValue強(qiáng)轉(zhuǎn)成Point對(duì)象,然后同樣根據(jù)fraction來(lái)計(jì)算當(dāng)前動(dòng)畫的x和y的值,最后組裝到一個(gè)新的Point對(duì)象當(dāng)中并返回。

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編寫完成了,接下來(lái)我們就可以非常輕松地對(duì)Point對(duì)象進(jìn)行動(dòng)畫操作了,比如說(shuō)我們有兩個(gè)Point對(duì)象,現(xiàn)在需要將Point1通過(guò)動(dòng)畫平滑過(guò)度到Point2,就可以這樣寫:
需要注意的是,ofObject()方法要求多傳入一個(gè)TypeEvaluator參數(shù),這里我們只需要傳入剛才定義好的PointEvaluator的實(shí)例就可以了。

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的全部用法,掌握了這些知識(shí)之后,我們就可以來(lái)嘗試一下如何通過(guò)對(duì)Point對(duì)象進(jìn)行動(dòng)畫操作,從而實(shí)現(xiàn)整個(gè)自定義View的動(dòng)畫效果。

自定義MyAnimView:

首先在自定義View的構(gòu)造方法當(dāng)中初始化了一個(gè)Paint對(duì)象作為畫筆,并將畫筆顏色設(shè)置為藍(lán)色,接著在onDraw()方法當(dāng)中進(jìn)行繪制。
這里我們繪制的邏輯是由currentPoint這個(gè)對(duì)象控制的,如果currentPoint對(duì)象不等于空,那么就調(diào)用drawCircle()方法在currentPoint的坐標(biāo)位置畫出一個(gè)半徑為50的圓,如果currentPoint對(duì)象是空,那么就調(diào)用startAnimation()方法來(lái)啟動(dòng)動(dòng)畫。

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()方法中的代碼:
就是對(duì)Point對(duì)象進(jìn)行了一個(gè)動(dòng)畫操作而已。
這里我們定義了一個(gè)startPoint和一個(gè)endPoint,坐標(biāo)分別是View的左上角和右下角,并將動(dòng)畫的時(shí)長(zhǎng)設(shè)為5秒。然后有一點(diǎn)需要大家注意的,就是我們通過(guò)監(jiān)聽(tīng)器對(duì)動(dòng)畫的過(guò)程進(jìn)行了監(jiān)聽(tīng),每當(dāng)Point值有改變的時(shí)候都會(huì)回調(diào)onAnimationUpdate()方法。在這個(gè)方法當(dāng)中,我們對(duì)currentPoint對(duì)象進(jìn)行了重新賦值,并調(diào)用了invalidate()方法,這樣的話onDraw()方法就會(huì)重新調(diào)用,并且由于currentPoint對(duì)象的坐標(biāo)已經(jīng)改變了,那么繪制的位置也會(huì)改變,于是一個(gè)平移的動(dòng)畫效果也就實(shí)現(xiàn)了。

在布局文件當(dāng)中引入這個(gè)自定義控件:
<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的高級(jí)用法

實(shí)現(xiàn)目標(biāo)

動(dòng)態(tài)改變View的顏色。

效果圖:
效果圖

實(shí)現(xiàn)步驟

自定義MyAnimView屬性

ObjectAnimator內(nèi)部的工作機(jī)制是通過(guò)尋找特定屬性的get和set方法,然后通過(guò)方法不斷地對(duì)值進(jìn)行改變,從而實(shí)現(xiàn)動(dòng)畫效果的。
  因此我們就需要在MyAnimView中定義一個(gè)color屬性,并提供它的get和set方法。這里我們可以將color屬性設(shè)置為字符串類型,使用#RRGGBB這種格式來(lái)表示顏色值,代碼如下所示:
  在setColor()方法當(dāng)中,將畫筆的顏色設(shè)置成方法參數(shù)傳入的顏色,然后調(diào)用了invalidate()方法。即在改變了畫筆顏色之后立即刷新視圖,然后onDraw()方法就會(huì)調(diào)用。在onDraw()方法當(dāng)中會(huì)根據(jù)當(dāng)前畫筆的顏色來(lái)進(jìn)行繪制,這樣顏色也就會(huì)動(dòng)態(tài)進(jìn)行改變了。

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()方法得到調(diào)用了,在使用ObjectAnimator之前我們還要完成一個(gè)非常重要的工作,就是編寫一個(gè)用于告知系統(tǒng)如何進(jìn)行顏色過(guò)度的TypeEvaluator。
創(chuàng)建ColorEvaluator并實(shí)現(xiàn)TypeEvaluator接口,代碼如下所示:
  首先在evaluate()方法當(dāng)中獲取到顏色的初始值和結(jié)束值,并通過(guò)字符串截取的方式將顏色分為RGB三個(gè)部分,并將RGB的值轉(zhuǎn)換成十進(jìn)制數(shù)字,那么每個(gè)顏色的取值范圍就是0-255。接下來(lái)計(jì)算一下初始顏色值到結(jié)束顏色值之間的差值,這個(gè)差值很重要,決定著顏色變化的快慢,如果初始顏色值和結(jié)束顏色值很相近,那么顏色變化就會(huì)比較緩慢,而如果顏色值相差很大,比如說(shuō)從黑到白,那么就要經(jīng)歷255*3這個(gè)幅度的顏色過(guò)度,變化就會(huì)非???。

那么控制顏色變化的速度是通過(guò)getCurrentColor()這個(gè)方法來(lái)實(shí)現(xiàn)的,這個(gè)方法會(huì)根據(jù)當(dāng)前的fraction值來(lái)計(jì)算目前應(yīng)該過(guò)度到什么顏色,并且這里會(huì)根據(jù)初始和結(jié)束的顏色差值來(lái)控制變化速度,最終將計(jì)算出的顏色進(jìn)行返回。

最后,由于我們計(jì)算出的顏色是十進(jìn)制數(shù)字,這里還需要調(diào)用一下getHexString()方法把它們轉(zhuǎn)換成十六進(jìn)制字符串,再將RGB顏色拼裝起來(lái)之后作為最終的結(jié)果返回。

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;  
        }  
        // 計(jì)算初始顏色和結(jié)束顏色之間的差值  
        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);  
        }  
        // 將計(jì)算出的當(dāng)前顏色的值組裝返回  
        String currentColor = "#" + getHexString(mCurrentRed)  
                + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);  
        return currentColor;  
    }  
  
    /** 
     * 根據(jù)fraction值來(lái)計(jì)算當(dāng)前的顏色。 
     */  
    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進(jìn)制顏色值轉(zhuǎn)換成16進(jìn)制。 
     */  
    private String getHexString(int value) {  
        String hexString = Integer.toHexString(value);  
        if (hexString.length() == 1) {  
            hexString = "0" + hexString;  
        }  
        return hexString;  
    }  
  
}  
調(diào)用

比如說(shuō)我們想要實(shí)現(xiàn)從藍(lán)色到紅色的動(dòng)畫過(guò)度,歷時(shí)5秒,就可以這樣寫:

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

接下來(lái)我們需要將上面一段代碼移到MyAnimView類當(dāng)中,讓它和剛才的Point移動(dòng)動(dòng)畫可以結(jié)合到一起播放,這就要借助我們?cè)谏掀恼庐?dāng)中學(xué)到的組合動(dòng)畫的技術(shù)了。修改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();  
    }  
  
}  

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

Interpolator的用法

Interpolator這個(gè)東西很難進(jìn)行翻譯,直譯過(guò)來(lái)的話是補(bǔ)間器的意思,它的主要作用是可以控制動(dòng)畫的變化速率,比如去實(shí)現(xiàn)一種非線性運(yùn)動(dòng)的動(dòng)畫效果。那么什么叫做非線性運(yùn)動(dòng)的動(dòng)畫效果呢?就是說(shuō)動(dòng)畫改變的速率不是一成不變的,像加速運(yùn)動(dòng)以及減速運(yùn)動(dòng)都屬于非線性運(yùn)動(dòng)。

不過(guò)Interpolator并不是屬性動(dòng)畫中新增的技術(shù),實(shí)際上從Android 1.0版本開(kāi)始就一直存在Interpolator接口了,而之前的補(bǔ)間動(dòng)畫當(dāng)然也是支持這個(gè)功能的。只不過(guò)在屬性動(dòng)畫新增了一個(gè)TimeInterpolator接口,這個(gè)接口是用于兼容之前的Interpolator的,這使得所有過(guò)去的Interpolator實(shí)現(xiàn)類都可以直接拿過(guò)來(lái)放到屬性動(dòng)畫當(dāng)中使用。

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


TimeInterpolator接口已經(jīng)有非常多的實(shí)現(xiàn)類了,這些都是Android系統(tǒng)內(nèi)置好的并且我們可以直接使用的Interpolator。每個(gè)Interpolator都有它各自的實(shí)現(xiàn)效果,比如說(shuō)AccelerateInterpolator就是一個(gè)加速運(yùn)動(dòng)的Interpolator,而DecelerateInterpolator就是一個(gè)減速運(yùn)動(dòng)的Interpolator。

AccelerateDecelerateInterpolator

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


可以看到,一開(kāi)始的值變化速度明顯比較慢,僅0.0開(kāi)頭的就打印了4次,之后開(kāi)始加速,最后階段又開(kāi)始減速,因此我們可以很明顯地看出這一個(gè)先加速后減速的Interpolator。

上文中的小球也是:一開(kāi)始運(yùn)動(dòng)速度比較慢,然后逐漸加速,中間的部分運(yùn)動(dòng)速度就比較快,接下來(lái)開(kāi)始減速,最后緩緩?fù)W A硗忸伾兓彩沁@種規(guī)律,一開(kāi)始顏色變化的比較慢,中間顏色變化的很快,最后階段顏色變化的又比較慢。

從以上幾點(diǎn)我們就可以總結(jié)出一個(gè)結(jié)論了,使用屬性動(dòng)畫時(shí),系統(tǒng)默認(rèn)的Interpolator其實(shí)就是一個(gè)先加速后減速的Interpolator,對(duì)應(yīng)的實(shí)現(xiàn)類就是AccelerateDecelerateInterpolator。

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

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

但是默認(rèn)情況下小球的下降速度肯定是先加速后減速的,我們需要改為下降速度越來(lái)越快的。
調(diào)用Animator的setInterpolator()方法就可以了,這個(gè)方法要求傳入一個(gè)實(shí)現(xiàn)TimeInterpolator接口的實(shí)例,那么比如說(shuō)我們想要實(shí)現(xiàn)小球下降越來(lái)越快的效果,就可以使用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));//實(shí)現(xiàn)小球下降越來(lái)越快  
    /*AccelerateInterpolator的構(gòu)建函數(shù)可以接收一個(gè)float類型的參數(shù),這個(gè)參數(shù)是用于控制加速度的*/
    anim.setDuration(2500);  
    anim.start();  
}  

效果如下:


BounceInterpolator

現(xiàn)在要讓小球撞擊到地面之后應(yīng)該要反彈起來(lái),然后再次落下,接著再反彈起來(lái),又再次落下,以此反復(fù),最后靜止。這個(gè)功能我們當(dāng)然可以自己去寫,只不過(guò)比較復(fù)雜,所幸的是,Android系統(tǒng)中已經(jīng)提供好了這樣一種Interpolator,我們只需要簡(jiǎn)單地替換一下就可以完成上面的描述的效果,代碼如下所示:
將設(shè)置的Interpolator換成了BounceInterpolator的實(shí)例,而BounceInterpolator就是一種可以模擬物理規(guī)律,實(shí)現(xiàn)反復(fù)彈起效果的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內(nèi)部實(shí)現(xiàn)機(jī)制

首先看一下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);  
}  

只有一個(gè)getInterpolation()方法。
getInterpolation()方法中接收一個(gè)input參數(shù),這個(gè)參數(shù)的值會(huì)隨著動(dòng)畫的運(yùn)行而不斷變化,不過(guò)它的變化是非常有規(guī)律的,就是根據(jù)設(shè)定的動(dòng)畫時(shí)長(zhǎng)勻速增加,變化范圍是0到1。也就是說(shuō)當(dāng)動(dòng)畫一開(kāi)始的時(shí)候input的值是0,到動(dòng)畫結(jié)束的時(shí)候input的值是1,而中間的值則是隨著動(dòng)畫運(yùn)行的時(shí)長(zhǎng)在0到1之間變化的。

input的值決定了上文中fraction的值。
input的值是由系統(tǒng)經(jīng)過(guò)計(jì)算傳入getInterpolation()方法中的,然后我們可以自己實(shí)現(xiàn)getInterpolation()方法中的算法,根據(jù)input的值來(lái)計(jì)算出一個(gè)返回值,而這個(gè)返回值就是fraction了。
因此,最簡(jiǎn)單的情況就是input值和fraction值是相同的,這種情況由于input值是勻速增加的,因而fraction的值也是勻速增加的,所以動(dòng)畫的運(yùn)動(dòng)情況也是勻速的。

系統(tǒng)中內(nèi)置的LinearInterpolator就是一種勻速運(yùn)動(dòng)的Interpolator,那么我們來(lái)看一下它的源碼是怎么實(shí)現(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的值的,這就是勻速運(yùn)動(dòng)的Interpolator的實(shí)現(xiàn)方式。
    }  
  
    /** @hide */  
    @Override  
    public long createNativeInterpolator() {  
        return NativeInterpolatorFactoryHelper.createLinearInterpolator();  
    }  
}  

當(dāng)然這是最簡(jiǎn)單的一種Interpolator的實(shí)現(xiàn)了,我們?cè)賮?lái)看一個(gè)稍微復(fù)雜一點(diǎn)的。既然現(xiàn)在大家都知道了系統(tǒng)在默認(rèn)情況下使用的是AccelerateDecelerateInterpolator,那我們就來(lái)看一下它的源碼吧,如下所示:

/** 
 * 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(π)的結(jié)果是-1,cos(2π)的結(jié)果是1,那么這個(gè)值再除以2加上0.5之后,getInterpolation()方法最終返回的結(jié)果值還是在0到1之間。只不過(guò)經(jīng)過(guò)了余弦運(yùn)算之后,最終的結(jié)果不再是勻速增加的了,而是經(jīng)歷了一個(gè)先加速后減速的過(guò)程。*/
    }  
  
    /** @hide */  
    @Override  
    public long createNativeInterpolator() {  
        return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();  
    }  
}  

getInterpolation()方法中的邏輯變復(fù)雜了,我們可以將這個(gè)算法的執(zhí)行情況通過(guò)曲線圖的方式繪制出來(lái),結(jié)果如下圖所示:


可以看到,這是一個(gè)S型的曲線圖,當(dāng)橫坐標(biāo)從0變化到0.2的時(shí)候,縱坐標(biāo)的變化幅度很小,但是之后就開(kāi)始明顯加速,最后橫坐標(biāo)從0.8變化到1的時(shí)候,縱坐標(biāo)的變化幅度又變得很小。

自定義Interpolator

實(shí)現(xiàn):先減速后加速Interpolator
新建DecelerateAccelerateInterpolator類,讓它實(shí)現(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ù)來(lái)實(shí)現(xiàn)先減速后加速的功能的,因?yàn)檎液瘮?shù)初始弧度的變化值非常大,剛好和余弦函數(shù)是相反的,而隨著弧度的增加,正弦函數(shù)的變化值也會(huì)逐漸變小,這樣也就實(shí)現(xiàn)了減速的效果。當(dāng)弧度大于π/2之后,整個(gè)過(guò)程相反了過(guò)來(lái),現(xiàn)在正弦函數(shù)的弧度變化值非常小,漸漸隨著弧度繼續(xù)增加,變化值越來(lái)越大,弧度到π時(shí)結(jié)束,這樣從0過(guò)度到π,也就實(shí)現(xiàn)了先減速后加速的效果。

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


可以看到,這也是一個(gè)S型的曲線圖,只不過(guò)曲線的方向和剛才是相反的。從上圖中我們可以很清楚地看出來(lái),一開(kāi)始縱坐標(biāo)的變化幅度很大,然后逐漸變小,橫坐標(biāo)到0.5的時(shí)候縱坐標(biāo)變化幅度趨近于零,之后隨著橫坐標(biāo)繼續(xù)增加縱坐標(biāo)的變化幅度又開(kāi)始變大,的確是先減速后加速的效果。

那么現(xiàn)在我們將DecelerateAccelerateInterpolator在代碼中進(jì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.setInterpolator(new DecelerateAccelerateInterpolator());//替換成自定義Interpolator
    anim.setDuration(3000);  
    anim.start();  
}  

非常簡(jiǎn)單,就是將DecelerateAccelerateInterpolator的實(shí)例傳入到setInterpolator()方法當(dāng)中。重新運(yùn)行一下代碼,效果如下圖所示:


ViewPropertyAnimator

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

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

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

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

用法

使用ViewPropertyAnimator來(lái)實(shí)現(xiàn)同樣的效果,ViewPropertyAnimator提供了更加易懂、更加面向?qū)ο蟮腁PI,如下所示:

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

animate()方法就是在Android 3.1系統(tǒng)上新增的一個(gè)方法,這個(gè)方法的返回值是一個(gè)ViewPropertyAnimator對(duì)象,也就是說(shuō)拿到這個(gè)對(duì)象之后我們就可以調(diào)用它的各種方法來(lái)實(shí)現(xiàn)動(dòng)畫效果了,這里我們調(diào)用了alpha()方法并轉(zhuǎn)入0,表示將當(dāng)前的textview變成透明狀態(tài)。

ViewPropertyAnimator還可以很輕松地將多個(gè)動(dòng)畫組合到一起,比如我們想要讓textview運(yùn)動(dòng)到500,500這個(gè)坐標(biāo)點(diǎn)上,就可以這樣寫:

textview.animate().x(500).y(500);  //讓textview運(yùn)動(dòng)到500,500這個(gè)坐標(biāo)點(diǎn)

ViewPropertyAnimator是支持連綴用法的,我們想讓textview移動(dòng)到橫坐標(biāo)500這個(gè)位置上時(shí)調(diào)用了x(500)這個(gè)方法,然后讓textview移動(dòng)到縱坐標(biāo)500這個(gè)位置上時(shí)調(diào)用了y(500)這個(gè)方法,將所有想要組合的動(dòng)畫通過(guò)這種連綴的方式拼接起來(lái),這樣全部動(dòng)畫就都會(huì)一起被執(zhí)行。

設(shè)定動(dòng)畫的運(yùn)行時(shí)長(zhǎng):

textview.animate().x(500).y(500).setDuration(5000);  //設(shè)定動(dòng)畫的運(yùn)行時(shí)長(zhǎng)

Interpolator也可以應(yīng)用在ViewPropertyAnimator上面:

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

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

注意

整個(gè)ViewPropertyAnimator的功能都是建立在View類新增的animate()方法之上的,這個(gè)方法會(huì)創(chuàng)建并返回一個(gè)ViewPropertyAnimator的實(shí)例,之后的調(diào)用的所有方法,設(shè)置的所有屬性都是通過(guò)這個(gè)實(shí)例完成的。
  在使用ViewPropertyAnimator時(shí),我們自始至終沒(méi)有調(diào)用過(guò)start()方法,這是因?yàn)樾碌慕涌谥惺褂昧穗[式啟動(dòng)動(dòng)畫的功能,只要我們將動(dòng)畫定義完成之后,動(dòng)畫就會(huì)自動(dòng)啟動(dòng)。并且這個(gè)機(jī)制對(duì)于組合動(dòng)畫也同樣有效,只要我們不斷地連綴新的方法,那么動(dòng)畫就不會(huì)立刻執(zhí)行,等到所有在ViewPropertyAnimator上設(shè)置的方法都執(zhí)行完畢后,動(dòng)畫就會(huì)自動(dòng)啟動(dòng)。當(dāng)然如果不想使用這一默認(rèn)機(jī)制的話,我們也可以顯式地調(diào)用start()方法來(lái)啟動(dòng)動(dòng)畫。
  ViewPropertyAnimator的所有接口都是使用連綴的語(yǔ)法來(lái)設(shè)計(jì)的,每個(gè)方法的返回值都是它自身的實(shí)例,因此調(diào)用完一個(gè)方法之后可以直接連綴調(diào)用它的另一個(gè)方法,這樣把所有的功能都串接起來(lái),我們甚至可以僅通過(guò)一行代碼就完成任意復(fù)雜度的動(dòng)畫功能。

動(dòng)畫常見(jiàn)問(wèn)題

修改 Activity 進(jìn)入和退出動(dòng)畫

可以通過(guò)兩種方式,一是通過(guò)定義 Activity 的主題,二是通過(guò)覆寫 Activity 的 overridePendingTransition 方法。
方式1:通過(guò)設(shè)置主題樣式
在 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);

屬性動(dòng)畫,例如一個(gè) button 從 A 移動(dòng)到 B 點(diǎn),B 點(diǎn)還是可以響應(yīng)點(diǎn)擊事件,這個(gè)原理是什么?

補(bǔ)間動(dòng)畫只是顯示的位置變動(dòng),View 的實(shí)際位置未改變,表現(xiàn)為 View 移動(dòng)到其他地方,點(diǎn)擊事件仍在原處才能響應(yīng)。而屬性動(dòng)畫控件移動(dòng)后事件相應(yīng)就在控件移動(dòng)后本身進(jìn)行處理。

引用:
Android屬性動(dòng)畫完全解析(上),初識(shí)屬性動(dòng)畫的基本用法
Android屬性動(dòng)畫完全解析(中),ValueAnimator和ObjectAnimator的高級(jí)用法
Android屬性動(dòng)畫完全解析(下),Interpolator和ViewPropertyAnimator的用法

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。