鍍金池/ 教程/ Android/ ObjectAnimator 基本使用
聯(lián)合動(dòng)畫的 XML 實(shí)現(xiàn)與使用示例
Interpolator 插值器
高級(jí)進(jìn)階(二)
ObjectAnimator 基本使用
ValueAnimator 基本使用
alpha、scale、translate、rotate、set 的 xml 屬性及用法
PropertyValuesHolder 與 Keyframe
layoutAnimation 與 gridLayoutAnimation
自定義控件三部曲之動(dòng)畫篇(十三)——實(shí)現(xiàn)ListView Item進(jìn)入動(dòng)畫
自定義控件三部曲之動(dòng)畫篇(十二)——animateLayoutChanges與LayoutTransition
高級(jí)進(jìn)階(一)
代碼生成 alpha、scale、translate、rotate、set 及插值器動(dòng)畫
聯(lián)合動(dòng)畫的代碼實(shí)現(xiàn)

ObjectAnimator 基本使用

一、概述

1、引入

上幾篇給大家講了 ValueAnimator,但 ValueAnimator 有個(gè)缺點(diǎn),就是只能對(duì)數(shù)值對(duì)動(dòng)畫計(jì)算。我們要想對(duì)哪個(gè)控件操作,需要監(jiān)聽動(dòng)畫過程,在監(jiān)聽中對(duì)控件操作。這樣使用起來相比補(bǔ)間動(dòng)畫而言就相對(duì)比較麻煩。 為了能讓動(dòng)畫直接與對(duì)應(yīng)控件相關(guān)聯(lián),以使我們從監(jiān)聽動(dòng)畫過程中解放出來,谷歌的開發(fā)人員在 ValueAnimator 的基礎(chǔ)上,又派生了一個(gè)類 ObjectAnimator; 由于 ObjectAnimator 是派生自 ValueAnimator 的,所以 ValueAnimator 中所能使用的方法,在 ObjectAnimator 中都可以正常使用。 但 ObjectAnimator 也重寫了幾個(gè)方法,比如 ofInt(),ofFloat()等。我們先看看利用 ObjectAnimator 重寫的 ofFloat 方法如何實(shí)現(xiàn)一個(gè)動(dòng)畫:(改變透明度)

ObjectAnimator animator = ObjectAnimator.ofFloat(tv,"alpha",1,0,1);  
animator.setDuration(2000);  
animator.start(); 

效果圖如下:

http://wiki.jikexueyuan.com/project/android-animation/images/68.gif" alt="" />

我們這里還是直接使用上一篇的框架代碼;(當(dāng)點(diǎn)擊 start anim 時(shí)執(zhí)行動(dòng)畫)從上面的代碼中可以看到構(gòu)造 ObjectAnimator 的方法非常簡(jiǎn)單:

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) 
  • 第一個(gè)參數(shù)用于指定這個(gè)動(dòng)畫要操作的是哪個(gè)控件
  • 第二個(gè)參數(shù)用于指定這個(gè)動(dòng)畫要操作這個(gè)控件的哪個(gè)屬性
  • 第三個(gè)參數(shù)是可變長(zhǎng)參數(shù),這個(gè)就跟 ValueAnimator 中的可變長(zhǎng)參數(shù)的意義一樣了,就是指這個(gè)屬性值是從哪變到哪。像我們上面的代碼中指定的就是將 textview 的 alpha 屬性從 0 變到 1 再變到 0; 下面我們?cè)賮砜匆幌氯绾螌?shí)現(xiàn)旋轉(zhuǎn)效果:
ObjectAnimator animator = ObjectAnimator.ofFloat(tv,"rotation",0,180,0);  
animator.setDuration(2000);  
animator.start();

效果圖如下:

http://wiki.jikexueyuan.com/project/android-animation/images/69.gif" alt="" />

從代碼中可以看到,我們只需要改變 ofFloat()的第二個(gè)參數(shù)的值就可以實(shí)現(xiàn)對(duì)應(yīng)的動(dòng)畫; 那么問題來了,我們?cè)趺粗赖诙€(gè)參數(shù)的值是啥呢?

2、setter 函數(shù)

我們?cè)倩貋砜礃?gòu)造改變 rotation 值的 ObjectAnimator 的方法

ObjectAnimator animator = ObjectAnimator.ofFloat(tv,"rotation",0,180,0);

TextView 控件有 rotation 這個(gè)屬性嗎?沒有,不光 TextView 沒有,連它的父類 View 中也沒有這個(gè)屬性。那它是怎么來改變這個(gè)值的呢?其實(shí),ObjectAnimator 做動(dòng)畫,并不是根據(jù)控件 xml 中的屬性來改變的,而是通過指定屬性所對(duì)應(yīng)的 set 方法來改變的。比如,我們上面指定的改變 rotation 的屬性值,ObjectAnimator 在做動(dòng)畫時(shí)就會(huì)到指定控件(TextView)中去找對(duì)應(yīng)的 setRotation()方法來改變控件中對(duì)應(yīng)的值。同樣的道理,當(dāng)我們?cè)谧铋_始的示例代碼中,指定改變”alpha”屬性值的時(shí)候,ObjectAnimator 也會(huì)到 TextView 中去找對(duì)應(yīng)的 setAlpha()方法。那 TextView 中都有這些方法嗎,有的,這些方法都是從 View 中繼承過來的,在 View 中有關(guān)動(dòng)畫,總共有下面幾組 set 方法:

//1、透明度:alpha  
public void setAlpha(float alpha)  

//2、旋轉(zhuǎn)度數(shù):rotation、rotationX、rotationY  
public void setRotation(float rotation)  
public void setRotationX(float rotationX)  
public void setRotationY(float rotationY)  

//3、平移:translationX、translationY  
public void setTranslationX(float translationX)   
public void setTranslationY(float translationY)  

//縮放:scaleX、scaleY  
public void setScaleX(float scaleX)  
public void setScaleY(float scaleY) 

可以看到在 View 中已經(jīng)實(shí)現(xiàn)了有關(guān) alpha,rotaion,translate,scale 相關(guān)的 set 方法。所以我們?cè)跇?gòu)造 ObjectAnimator 時(shí)可以直接使用。 在開始逐個(gè)看這些函數(shù)的使用方法前,我們先做一個(gè)總結(jié): 1、要使用 ObjectAnimator 來構(gòu)造對(duì)畫,要操作的控件中,必須存在對(duì)應(yīng)的屬性的 set 方法 2、setter 方法的命名必須以駱駝拼寫法命名,即 set 后每個(gè)單詞首字母大寫,其余字母小寫,即類似于 setPropertyName 所對(duì)應(yīng)的屬性為 propertyName 下面我們就來看一下上面中各個(gè)方法的使用方法及作用。 有關(guān) alpha 的用法,上面已經(jīng)講過了,下面我們來看看其它的

(1)、setRotationX、setRotationY 與 setRotation

  • setRotationX(float rotationX):表示圍繞 X 軸旋轉(zhuǎn),rotationX 表示旋轉(zhuǎn)度數(shù)
  • setRotationY(rotationY):表示圍繞 Y 軸旋轉(zhuǎn),rotationY 表示旋轉(zhuǎn)度數(shù)
  • setRotation(float rotation):表示圍繞 Z 旋轉(zhuǎn),rotation 表示旋轉(zhuǎn)度數(shù)

先來看看 setRotationX 的效果:

ObjectAnimator animator = ObjectAnimator.ofFloat(tv,"rotationX",0,270,0);  
animator.setDuration(2000);  
animator.start();

效果圖如下:

http://wiki.jikexueyuan.com/project/android-animation/images/70.gif" alt="" />

從效果圖中明顯看出,textview 的旋轉(zhuǎn)方法是圍繞 X 軸旋轉(zhuǎn)的,我們?cè)O(shè)定為從 0 度旋轉(zhuǎn)到 270 度再返回 0 度。 然后再來看看 setRotationY 的使用方法與效果:

ObjectAnimator animator = ObjectAnimator.ofFloat(tv,"rotationY",0,180,0);  
animator.setDuration(2000);  
animator.start();

效果圖如下:

http://wiki.jikexueyuan.com/project/android-animation/images/71.gif" alt="" />

從效果圖中明顯可以看出圍繞 Y 軸旋轉(zhuǎn)的。 我們?cè)賮砜纯?setRotation 的用法與效果:

ObjectAnimator animator = ObjectAnimator.ofFloat(tv,"rotation",0,270,0);  
animator.setDuration(2000);  
animator.start();  

http://wiki.jikexueyuan.com/project/android-animation/images/72.gif" alt="" />

我們上面說了,setRotation 是圍繞 Z 軸旋轉(zhuǎn)的,可能有些同學(xué)不理解什么是 Z 軸,我們來看一張圖:

http://wiki.jikexueyuan.com/project/android-animation/images/13.png" alt="" />

從這張圖中,綠色框部分表示手機(jī)屏幕,很明顯可以看出 Z 軸就是從屏幕左上角原點(diǎn)向外伸出的一條軸。這樣,我們也就可以理解圍繞 Z 軸旋轉(zhuǎn),為什么是這樣子轉(zhuǎn)了。

(2)、setTranslationX 與 setTranslationY

  • setTranslationX(float translationX) :表示在 X 軸上的平移距離,以當(dāng)前控件為原點(diǎn),向右為正方向,參數(shù) translationX 表示移動(dòng)的距離。
  • setTranslationY(float translationY) :表示在 Y 軸上的平移距離,以當(dāng)前控件為原點(diǎn),向下為正方向,參數(shù) translationY 表示移動(dòng)的距離。 我們先看看 setTranslationX 的用法:
ObjectAnimator animator = ObjectAnimator.ofFloat(tv, "translationX", 0, 200, -200,0);  
animator.setDuration(2000);  
animator.start();  

效果圖如下:

http://wiki.jikexueyuan.com/project/android-animation/images/73.gif" alt="" />

所以,我們上面在構(gòu)造動(dòng)畫時(shí),指定的移動(dòng)距離是(0, 200, -200,0),所以控件會(huì)從自身所有位置向右移動(dòng) 200 像素,然后再移動(dòng)到距離原點(diǎn)-200 的位置,最后回到原點(diǎn); 然后我們來看看 setTranslateY 的用法:

ObjectAnimator animator = ObjectAnimator.ofFloat(tv, "translationY", 0, 200, -100,0);  
animator.setDuration(2000);  
animator.start(); 

效果圖如下:(為了方便看到效果,將 textview 垂直居中)

http://wiki.jikexueyuan.com/project/android-animation/images/74.gif" alt="" />

同樣,移動(dòng)位置的坐標(biāo)也都是以當(dāng)前控件所在位置為中心點(diǎn)的。所以對(duì)應(yīng)的移動(dòng)位置從原點(diǎn)移動(dòng)向下移動(dòng) 200 像素,然后再移動(dòng)到向下到距原點(diǎn) 200 像素的位置,最后再回到(0,0)從效果圖中很明顯可以看出來。 從上面可以看出:每次移動(dòng)距離的計(jì)算都是以原點(diǎn)為中心的;比如初始動(dòng)畫為 ObjectAnimator.ofFloat(tv, “translationY”, 0, 200, -100,0)表示首先從 0 移動(dòng)到正方向 200 的位置,然后再移動(dòng)到負(fù)方向 100 的位置,最后移動(dòng)到原點(diǎn)。

(3)、setScaleX 與 setScaleY

  • setScaleX(float scaleX):在 X 軸上縮放,scaleX 表示縮放倍數(shù)
  • setScaleY(float scaleY):在 Y 軸上縮放,scaleY 表示縮放倍數(shù) 我們來看看 setScaleX 的用法:
ObjectAnimator animator = ObjectAnimator.ofFloat(tv, "scaleX", 0, 3, 1);  
animator.setDuration(2000);  
animator.start();

效果圖如下:

http://wiki.jikexueyuan.com/project/android-animation/images/75.gif" alt="" />

在效果圖中,從 0 倍放大到 3 倍,然后再還原到 1 倍的原始狀態(tài)。 然后再來看看 setScaleY 的用法

ObjectAnimator animator = ObjectAnimator.ofFloat(tv, "scaleY", 0, 3, 1);  
animator.setDuration(2000);  
animator.start(); 

為了更好的看到效果,我把 textview 垂直居中了,效果圖如下:

http://wiki.jikexueyuan.com/project/android-animation/images/76.gif" alt="" />

源碼在文章底部給出 好了,到這里有關(guān) View 中自帶的 set 函數(shù)講完了,我們來看看 ObjectAnimator 是如何實(shí)現(xiàn)控件動(dòng)畫效果的。

3、ObjectAnimator 動(dòng)畫原理

我們先來看張圖:

http://wiki.jikexueyuan.com/project/android-animation/images/14.png" alt="" />

在這張圖中,將 ValueAnimator 的動(dòng)畫流程與 ObjectAnimator 的動(dòng)畫流程做了個(gè)對(duì)比。 可以看到 ObjectAnimator 的動(dòng)畫流程中,也是首先通過加速器產(chǎn)生當(dāng)前進(jìn)度的百分比,然后再經(jīng)過 Evaluator 生成對(duì)應(yīng)百分比所對(duì)應(yīng)的數(shù)字值。這兩步與 ValueAnimator 是完全一樣的,唯一不同的是最后一步,在 ValueAnimator 中,我們要通過添加監(jiān)聽器來監(jiān)聽當(dāng)前數(shù)字值。而在 ObjectAnimator 中,則是先根據(jù)屬性值拼裝成對(duì)應(yīng)的 set 函數(shù)的名字,比如這里的 scaleY 的拼裝方法就是將屬性的第一個(gè)字母強(qiáng)制大寫后,與 set 拼接,所以就是 setScaleY。然后通過反射找到對(duì)應(yīng)控件的 setScaleY(float scaleY)函數(shù),將當(dāng)前數(shù)字值做為 setScaleY(float scale)的參數(shù)將其傳入。 這里在找到控件的 set 函數(shù)以后,是通過反射來調(diào)用這個(gè)函數(shù)的,有關(guān)反射的使用大家可以參考《夯實(shí) JAVA 基本之二 —— 反射(1):基本類周邊信息獲取》 這就是 ObjectAnimator 的流程,最后一步總結(jié)起來就是調(diào)用對(duì)應(yīng)屬性的 set 方法,將動(dòng)畫當(dāng)前數(shù)字值做為參數(shù)傳進(jìn)去。

根據(jù)上面的流程,這里有幾個(gè)注意事項(xiàng): (1)、拼接 set 函數(shù)的方法:上面我們也說了是首先是強(qiáng)制將屬性的第一個(gè)字母大寫,然后與 set 拼接,就是對(duì)應(yīng)的 set 函數(shù)的名字。注意,只是強(qiáng)制將屬性的第一個(gè)字母大寫,后面的部分是保持不變的。反過來,如果我們的函數(shù)名命名為 setScalePointX(float ),那我們?cè)趯憣傩詴r(shí)可以寫成”scalePointX”或者寫成“ScalePointX”都是可以的,即第一個(gè)字母大小寫可以隨意,但后面的部分必須與 set 方法后的大小寫保持一致。 (2)、如何確定函數(shù)的參數(shù)類型:上面我們知道了如何找到對(duì)應(yīng)的函數(shù)名,那對(duì)應(yīng)的參數(shù)方法的參數(shù)類型如何確定呢?我們?cè)谥v ValueAnimator 的時(shí)候說過,動(dòng)畫過程中產(chǎn)生的數(shù)字值與構(gòu)造時(shí)傳入的值類型是一樣的。由于 ObjectAnimator 與 ValueAnimator 在插值器和 Evaluator 這兩步是完全一樣的,而當(dāng)前動(dòng)畫數(shù)值的產(chǎn)生是在 Evaluator 這一步產(chǎn)生的,所以 ObjectAnimator 的動(dòng)畫中產(chǎn)生的數(shù)值類型也是與構(gòu)造時(shí)的類型一樣的。那么問題來了,像我們的構(gòu)造方法。

ObjectAnimator animator = ObjectAnimator.ofFloat(tv, "scaleY", 0, 3, 1);

由于構(gòu)造時(shí)使用的是 ofFloat 函數(shù),所以中間值的類型應(yīng)該是 Float 類型的,所以在最后一步拼裝出來的 set 函數(shù)應(yīng)該是 setScaleY(float xxx)的樣式;這時(shí),系統(tǒng)就會(huì)利用反射來找到 setScaleY(float xxx)函數(shù),并把當(dāng)前的動(dòng)畫數(shù)值做為參數(shù)傳進(jìn)去。 那問題來了,如果沒有類似 setScaleY(float xxx)的函數(shù),我們只實(shí)現(xiàn)了一個(gè) setScaleY(int xxx)的函數(shù)怎么辦?這里雖然函數(shù)名一樣,但參數(shù)類型是不一樣的,那么系統(tǒng)就會(huì)報(bào)一個(gè)錯(cuò)誤:

http://wiki.jikexueyuan.com/project/android-animation/images/15.png" alt="" />

意思就是對(duì)應(yīng)函數(shù)的指定參數(shù)類型沒有找到。 (3)、調(diào)用 set 函數(shù)以后怎么辦?從 ObjectAnimator 的流程可以看到,ObjectAnimator 只負(fù)責(zé)把動(dòng)畫過程中的數(shù)值傳到對(duì)應(yīng)屬性的 set 函數(shù)中就結(jié)束了,注意傳給 set 函數(shù)以后就結(jié)束了!set 函數(shù)就相當(dāng)我們?cè)?ValueAnimator 中添加的監(jiān)聽的作用,set 函數(shù)中的對(duì)控件的操作還是需要我們自己來寫的。

那我們來看看 View 中的 setScaleY 是怎么實(shí)現(xiàn)的吧:

/** 
 * Sets the amount that the view is scaled in Y around the pivot point, as a proportion of 
 * the view's unscaled width. A value of 1 means that no scaling is applied. 
 * 
 * @param scaleY The scaling factor. 
 * @see #getPivotX() 
 * @see #getPivotY() 
 * 
 * @attr ref android.R.styleable#View_scaleY 
 */  
public void setScaleY(float scaleY) {  
    ensureTransformationInfo();  
    final TransformationInfo info = mTransformationInfo;  
    if (info.mScaleY != scaleY) {  
        invalidateParentCaches();  
        // Double-invalidation is necessary to capture view's old and new areas  
        invalidate(false);  
        info.mScaleY = scaleY;  
        info.mMatrixDirty = true;  
        mPrivateFlags |= DRAWN; // force another invalidation with the new orientation  
        invalidate(false);  
    }  
} 

大家不必理解這一坨代碼的意義,因?yàn)檫@些代碼是需要讀懂 View 的整體流程以后才能看得懂的,只需要跟著我的步驟來理解就行。這段代碼總共分為兩部分:第一步重新設(shè)置當(dāng)前控件的參數(shù),第二步調(diào)用 Invalidate()強(qiáng)制重繪; 所以在重繪時(shí),控件就會(huì)根據(jù)最新的控件參數(shù)來繪制了,所以我們就看到當(dāng)前控件被縮放了。 (4)、set 函數(shù)調(diào)用頻率是多少:由于我們知道動(dòng)畫在進(jìn)行時(shí),每隔十幾毫秒會(huì)刷新一次,所以我們的 set 函數(shù)也會(huì)每隔十幾毫秒會(huì)被調(diào)用一次。 講了這么多,就是為了強(qiáng)調(diào)一點(diǎn):ObjectAnimator 只負(fù)責(zé)把當(dāng)前運(yùn)動(dòng)動(dòng)畫的數(shù)值傳給 set 函數(shù)。至于 set 函數(shù)里面怎么來做,是我們自己的事了。 好了,在知道了 ObjectAnimator 的原理以后,下面就來看看如何自定義一個(gè) ObjectAnimator 的屬性吧。

二、自定義 ObjectAnimator 屬性

上面我們已經(jīng)看了使用 View 自帶的 set 函數(shù)所對(duì)應(yīng)屬性的方法,而且理解了 ObjectAnimator 的動(dòng)畫實(shí)現(xiàn)原理,下面我們來自定義一個(gè)屬性來看看實(shí)現(xiàn)效果吧。 我們?cè)陂_始之前再來捋一下 ObjectAnimator 的動(dòng)畫設(shè)置流程:ObjectAnimator 需要指定操作的控件對(duì)象,在開始動(dòng)畫時(shí),到控件類中去尋找設(shè)置屬性所對(duì)應(yīng)的 set 函數(shù),然后把動(dòng)畫中間值做為參數(shù)傳給這個(gè) set 函數(shù)并執(zhí)行它。 所以,我們說了,控件類中必須所要設(shè)置屬性所要對(duì)應(yīng)的 set 函數(shù)。所以為了自由控制控件的實(shí)現(xiàn),我們這里自定義一個(gè)控件。大家知道在這個(gè)自定義控件中,肯定存在一個(gè) set 函數(shù)與我們自定義的屬性相對(duì)應(yīng)。 我們先來看看這段要實(shí)現(xiàn)的效果:

http://wiki.jikexueyuan.com/project/android-animation/images/77.gif" alt="" />

這個(gè)效果圖與我們上篇自定義控件實(shí)現(xiàn)的效果差不多,這個(gè)控件中存在一個(gè)圓形,也是在動(dòng)畫時(shí)先將這個(gè)圓形放大,然后再將圓形還原。

1、保存圓形信息類——Point

為了,保存圓形的信息,我們先定義一個(gè)類:(Point.java)

public class Point {  
    private int mRadius;  

    public Point(int radius){  
        mRadius = radius;  
    }  

    public int getRadius() {  
        return mRadius;  
    }  

    public void setRadius(int radius) {  
        mRadius = radius;  
    }  
}  

這個(gè)類很好理解,只有一個(gè)成員變量 mRadius,表示圓的半徑。

2、自定義控件——MyPointView

然后我們自定義一個(gè)控件 MyPointView,完整代碼如下:

public class MyPointView extends View {  
    private Point mPoint = new Point(100);  

    public MyPointView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
    }  

    @Override  
    protected void onDraw(Canvas canvas) {  
        if (mPoint != null){  
            Paint paint = new Paint();  
            paint.setAntiAlias(true);  
            paint.setColor(Color.RED);  
            paint.setStyle(Paint.Style.FILL);  
            canvas.drawCircle(300,300,mPoint.getRadius(),paint);  
        }  
        super.onDraw(canvas);  
    }  

    void setPointRadius(int radius){  
        mPoint.setRadius(radius);  
        invalidate();  
    }  

}  

在這段代碼中,首先來看我們前面講到的 set 函數(shù):

void setPointRadius(int radius){  
    mPoint.setRadius(radius);  
    invalidate();  
}  

第一點(diǎn),這個(gè) set 函數(shù)所對(duì)應(yīng)的屬性應(yīng)該是 pointRadius 或者 PointRadius。前面我們已經(jīng)講了第一個(gè)字母大小寫無所謂,后面的字母必須保持與 set 函數(shù)完全一致。 第二點(diǎn),在 setPointRadius 中,先將當(dāng)前動(dòng)畫傳過來的值保存到 mPoint 中,做為當(dāng)前圓形的半徑。然后強(qiáng)制界面刷新 在界面刷新后,就開始執(zhí)行 onDraw()函數(shù):

@Override  
protected void onDraw(Canvas canvas) {  
    if (mPoint != null){  
        Paint paint = new Paint();  
        paint.setAntiAlias(true);  
        paint.setColor(Color.RED);  
        paint.setStyle(Paint.Style.FILL);  
        canvas.drawCircle(300,300,mPoint.getRadius(),paint);  
    }  
    super.onDraw(canvas);  
}  

在 onDraw 函數(shù)中,就是根據(jù)當(dāng)前 mPoint 的半徑值在(300,300)點(diǎn)外畫一個(gè)圓;有關(guān)畫圓的知識(shí),大家可以參考《android Graphics(一):概述及基本幾何圖形繪制》

3、使用 MyPointView

首先,在 MyActivity 的布局中添加 MyPointView 的使用(main.xml):

<?xml version="1.0" encoding="utf-8"?>  
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"  
                android:orientation="vertical"  
                android:layout_width="fill_parent"  
                android:layout_height="fill_parent">  

    <Button  
            android:id="@+id/btn"  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_alignParentLeft="true"  
            android:padding="10dp"  
            android:text="start anim"  
            />  

    <Button  
            android:id="@+id/btn_cancel"  
            android:layout_width="wrap_content"  
            android:layout_height="wrap_content"  
            android:layout_alignParentRight="true"  
            android:padding="10dp"  
            android:text="cancel anim"  
            />  
    <TextView  
            android:id="@+id/tv"  
            android:layout_width="100dp"  
            android:layout_height="wrap_content"  
            android:layout_centerHorizontal="true"  
            android:gravity="center"  
            android:padding="10dp"  
            android:background="#ffff00"  
            android:text="Hello qijian"/>  

    <com.example.BlogObjectAnimator1.MyPointView  
            android:id="@+id/pointview"  
            android:layout_width="match_parent"  
            android:layout_height="match_parent"  
            android:layout_below="@id/tv"/>  

</RelativeLayout> 

布局代碼很好理解,根據(jù)效果圖中的布局效果來理解,非常容易,就不再多講 然后看看在 MyActivity 中,點(diǎn)擊 start anim 后的處理方法:

public class MyActivity extends Activity {  
    private Button btnStart;  
    private MyPointView mPointView;  

    @Override  
    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  

        btnStart = (Button) findViewById(R.id.btn);  
        mPointView = (MyPointView)findViewById(R.id.pointview);  

        btnStart.setOnClickListener(new View.OnClickListener() {  
            @Override  
            public void onClick(View v) {  
                doPointViewAnimation();  
            }  
        });  
    }  
  …………  
} 

在點(diǎn)擊 start anim 按鈕后,開始執(zhí)行 doPointViewAnimation()函數(shù),doPointViewAnimation()函數(shù)代碼如下:

private void doPointViewAnimation(){  
     ObjectAnimator animator = ObjectAnimator.ofInt(mPointView, "pointRadius", 0, 300, 100);  
      animator.setDuration(2000);  
      animator.start();  
}  

在這段代碼中,著重看 ObjectAnimator 的構(gòu)造方法,首先要操作的控件對(duì)象是 mPointView,然后對(duì)應(yīng)的屬性是 pointRadius,然后值是從 0 到 300 再到 100; 所以在動(dòng)畫開始以后,ObjectAnimator 就會(huì)實(shí)時(shí)地把動(dòng)畫中產(chǎn)生的值做為參數(shù)傳給 MyPointView 類中的 setPointRadius(int radius)函數(shù),然后調(diào)用 setPointRadius(int radius)。由于我們?cè)?setPointRadius(int radius)中實(shí)時(shí)地設(shè)置圓形的半徑值然后強(qiáng)制重繪當(dāng)前界面,所以可以看到圓形的半徑會(huì)隨著動(dòng)畫的進(jìn)行而改變。 源碼在文章底部給出

四、注意——何時(shí)需要實(shí)現(xiàn)對(duì)應(yīng)屬性的 get 函數(shù)

我們?cè)賮砜匆幌?ObjectAinimator 的下面三個(gè)構(gòu)造方法:

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)  
public static ObjectAnimator ofInt(Object target, String propertyName, int... values)  
public static ObjectAnimator ofObject(Object target, String propertyName,TypeEvaluator evaluator, Object... values) 

前面我們已經(jīng)分別講過三個(gè)函數(shù)的使用方法,在上面的三個(gè)構(gòu)造方法中最后一個(gè)參數(shù)都是可變長(zhǎng)參數(shù)。我們也講了,他們的意義就是從哪個(gè)值變到哪個(gè)值的。 那么問題來了:前面我們都是定義多個(gè)值,即至少兩個(gè)值之間的變化,那如果我們只定義一個(gè)值呢,如下面的方式:(同樣以 MyPointView 為例)

ObjectAnimator animator = ObjectAnimator.ofInt(mPointView, "pointRadius",100);

我們?cè)谶@里只傳遞了一個(gè)變化值 100;那它從哪里開始變化呢?我們來看一下效果: 代碼如下:

ObjectAnimator animator = ObjectAnimator.ofInt(mPointView, "pointRadius",100);  
animator.setDuration(2000);  
animator.start();  

效果圖如下:

http://wiki.jikexueyuan.com/project/android-animation/images/78.gif" alt="" />

從效果圖中看起來是從 0 開始的,但是看 log 可以看出來已經(jīng)在出警告了:

http://wiki.jikexueyuan.com/project/android-animation/images/16.png" alt="" />

我們點(diǎn)了三次 start anim 按鈕,所以這里也報(bào)了三次,意思就是沒找到 pointRadius 屬性所對(duì)應(yīng)的 getPointRadius()函數(shù); 僅且僅當(dāng)我們只給動(dòng)畫設(shè)置一個(gè)值時(shí),程序才會(huì)調(diào)用屬性對(duì)應(yīng)的 get 函數(shù)來得到動(dòng)畫初始值。如果動(dòng)畫沒有初始值,那么就會(huì)使用系統(tǒng)默認(rèn)值。比如 ofInt()中使用的參數(shù)類型是 int 類型的,而系統(tǒng)的 Int 值的默認(rèn)值是 0,所以動(dòng)畫就會(huì)從 0 運(yùn)動(dòng)到 100;也就是系統(tǒng)雖然在找到不到屬性對(duì)應(yīng)的 get 函數(shù)時(shí),會(huì)給出警告,但同時(shí)會(huì)用系統(tǒng)默認(rèn)值做為動(dòng)畫初始值。 如果通過給自定義控件 MyPointView 設(shè)置了 get 函數(shù),那么將會(huì)以 get 函數(shù)的返回值做為初始值:

public class MyPointView extends View {  
    private Point mPoint = new Point(100);  

    public MyPointView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
    }  

    @Override  
    protected void onDraw(Canvas canvas) {  
        if (mPoint != null){  
            Paint paint = new Paint();  
            paint.setAntiAlias(true);  
            paint.setColor(Color.RED);  
            paint.setStyle(Paint.Style.FILL);  
            canvas.drawCircle(300,300,mPoint.getRadius(),paint);  
        }  
        super.onDraw(canvas);  
    }  

    public int getPointRadius(){  
        return 50;  
    }  

    public void setPointRadius(int radius){  
        mPoint.setRadius(radius);  
        invalidate();  
    }  

} 

我們?cè)谶@里添加了 getPointRadius 函數(shù),返回值是 Int.有些同學(xué)可能會(huì)疑惑:我怎么知道這里要返回 int 值呢? 我們前面說過當(dāng)且僅當(dāng)我們?cè)趧?chuàng)建 ObjectAnimator 時(shí),只給他傳遞了一個(gè)過渡值的時(shí)候,系統(tǒng)才會(huì)調(diào)用屬性對(duì)應(yīng)的 get 函數(shù)來得到動(dòng)畫的初始值!所以做為動(dòng)畫的初始值,那么在創(chuàng)建動(dòng)畫時(shí)過渡值傳的什么類型,這里的 get 函數(shù)就要返回類型

public static ObjectAnimator ofObject(Object target, String propertyName,TypeEvaluator evaluator, Object... values)

比如上面的 ofObject,get 函數(shù)所返回的類型就是與最后一個(gè)參數(shù) Object... values,相同類型的。 在我們?cè)?MyPointView 添加上 PointRadius 所對(duì)應(yīng)的 get 函數(shù)以后重新執(zhí)行動(dòng)畫:

ObjectAnimator animator = ObjectAnimator.ofInt(mPointView, "pointRadius",100);  
animator.setDuration(2000);  
animator.start();  

此時(shí)的效果圖如下:

http://wiki.jikexueyuan.com/project/android-animation/images/79.gif" alt="" />

從動(dòng)畫中可以看出,半徑已經(jīng)不是從 0 開始的了,而是從 50 開始的。 最后我們總結(jié)一下:當(dāng)且僅當(dāng)動(dòng)畫的只有一個(gè)過渡值時(shí),系統(tǒng)才會(huì)調(diào)用對(duì)應(yīng)屬性的 get 函數(shù)來得到動(dòng)畫的初始值。

源碼在文章底部給出

三、常用函數(shù)

有關(guān)常用函數(shù)這一節(jié)其實(shí)沒有太多講的必要。因?yàn)?ObjectAnimator 的函數(shù)都是從 ValueAnimator 中繼承而來的,所以用法和效果與 ValueAnimator 是完全一樣的。我們這里只講解一下 Evaluator 的用法,其它的也就不再講了。

1、使用 ArgbEvaluator

我們搜一下 TextView 所有的函數(shù)發(fā)現(xiàn),TextView 有一個(gè) set 函數(shù)能夠改變背景色:

public void setBackgroundColor(int color);  

大家可以回想到,我們?cè)?ValueAnimator 中也曾改變過背景色,使用的是 ArgbEvaluator。在這里我們?cè)倩仡櫹?ArgbEvaluator,它的實(shí)現(xiàn)代碼如下:

public class ArgbEvaluator implements TypeEvaluator {  
    public Object evaluate(float fraction, Object startValue, Object endValue) {  
        int startInt = (Integer) startValue;  
        int startA = (startInt >> 24);  
        int startR = (startInt >> 16) & 0xff;  
        int startG = (startInt >> 8) & 0xff;  
        int startB = startInt & 0xff;  

        int endInt = (Integer) endValue;  
        int endA = (endInt >> 24);  
        int endR = (endInt >> 16) & 0xff;  
        int endG = (endInt >> 8) & 0xff;  
        int endB = endInt & 0xff;  

        return (int)((startA + (int)(fraction * (endA - startA))) << 24) |  
                (int)((startR + (int)(fraction * (endR - startR))) << 16) |  
                (int)((startG + (int)(fraction * (endG - startG))) << 8) |  
                (int)((startB + (int)(fraction * (endB - startB))));  
    }  
}  

有關(guān)它具體實(shí)現(xiàn)的原理,前面篇章中我們已經(jīng)講過了,這里主要說一點(diǎn),ArgbEvaluator 的返回值是 Integer 類型,所以我們要使用 ArgbEvaluator 的話,構(gòu)造 ObjectAnimator 時(shí)必須使用 ofInt() 下面我們來看看使用 ArgbEvaluator 的代碼:

ObjectAnimator animator = ObjectAnimator.ofInt(tv, "BackgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);  
animator.setDuration(8000);  
animator.setEvaluator(new ArgbEvaluator());  
animator.start();

然后我們來看下代碼效果:

http://wiki.jikexueyuan.com/project/android-animation/images/80.gif" alt="" />

源碼在文章底部給出

2、其它函數(shù)

下面把其它所涉及到的函數(shù)的列表列在下面,大家可以參考 ValueAnimator 的使用方法來使用。有關(guān)自定義插值器和 Evaluator 的部分,可以參考《Animation 動(dòng)畫詳解(五)——高級(jí)進(jìn)階(一)》 (1)、常用函數(shù)

/** 
 * 設(shè)置動(dòng)畫時(shí)長(zhǎng),單位是毫秒 
 */  
ValueAnimator setDuration(long duration)  
/** 
 * 獲取 ValueAnimator 在運(yùn)動(dòng)時(shí),當(dāng)前運(yùn)動(dòng)點(diǎn)的值 
 */  
Object getAnimatedValue();  
/** 
 * 開始動(dòng)畫 
 */  
void start()  
/** 
 * 設(shè)置循環(huán)次數(shù),設(shè)置為 INFINITE 表示無限循環(huán) 
 */  
void setRepeatCount(int value)  
/** 
 * 設(shè)置循環(huán)模式 
 * value 取值有 RESTART,REVERSE, 
 */  
void setRepeatMode(int value)  
/** 
 * 取消動(dòng)畫 
 */  
void cancel()  
(2)、監(jiān)聽器相關(guān)
[java] view plain
/** 
 * 監(jiān)聽器一:監(jiān)聽動(dòng)畫變化時(shí)的實(shí)時(shí)值 
 */  
public static interface AnimatorUpdateListener {  
    void onAnimationUpdate(ValueAnimator animation);  
}  
//添加方法為:public void addUpdateListener(AnimatorUpdateListener listener)  
/** 
 * 監(jiān)聽器二:監(jiān)聽動(dòng)畫變化時(shí)四個(gè)狀態(tài) 
 */  
public static interface AnimatorListener {  
    void onAnimationStart(Animator animation);  
    void onAnimationEnd(Animator animation);  
    void onAnimationCancel(Animator animation);  
    void onAnimationRepeat(Animator animation);  
}  
//添加方法為:public void addListener(AnimatorListener listener)

(3)、插值器與 Evaluator

/** 
 * 設(shè)置插值器 
 */  
public void setInterpolator(TimeInterpolator value)  
/** 
 * 設(shè)置 Evaluator 
 */  
public void setEvaluator(TypeEvaluator value) 

到這里,有關(guān) ObjectAnimator 的知識(shí)就講完了,下篇再講講聯(lián)合動(dòng)畫和 xml 中實(shí)現(xiàn)動(dòng)畫的方法。

如果本文有幫到你,記得加關(guān)注哦 源碼下載地址:

csdn:http://download.csdn.net/detail/harvic880925/9445785

github:https://github.com/harvic/BlogResForGitHub

請(qǐng)大家尊重原創(chuàng)者版權(quán),轉(zhuǎn)載請(qǐng)標(biāo)明出處,謝謝