天天看點

Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法

轉載請注明出處:http://blog.csdn.net/guolin_blog/article/details/43536355

大家好,在上一篇文章當中,我們學習了android屬性動畫的基本用法,當然也是最常用的一些用法,這些用法足以覆寫我們平時大多情況下的動畫需求了。但是,正如上篇文章當中所說到的,屬性動畫對補間動畫進行了很大幅度的改進,之前補間動畫可以做到的屬性動畫也能做到,補間動畫做不到的現在屬性動畫也可以做到了。是以,今天我們就來學習一下屬性動畫的進階用法,看看如何實作一些補間動畫所無法實作的功能。

閱讀本篇文章需要你對屬性動畫有一定的了解,并且掌握屬性動畫的基本用法,如果你還對屬性動畫不夠了解的話,建議先去閱讀 android屬性動畫完全解析(上),初識屬性動畫的基本用法 。

在上篇文章中介紹補間動畫缺點的時候有提到過,補間動畫是隻能對view對象進行動畫操作的。而屬性動畫就不再受這個限制,它可以對任意對象進行動畫操作。那麼大家應該還記得在上篇文章當中我舉的一個例子,比如說我們有一個自定義的view,在這個view當中有一個point對象用于管理坐标,然後在ondraw()方法當中就是根據這個point對象的坐标值來進行繪制的。也就是說,如果我們可以對point對象進行動畫操作,那麼整個自定義view的動畫效果就有了。ok,下面我們就來學習一下如何實作這樣的效果。

在開始動手之前,我們還需要掌握另外一個知識點,就是typeevaluator的用法。可能在大多數情況下我們使用屬性動畫的時候都不會用到typeevaluator,但是大家還是應該了解一下它的用法,以防止當我們遇到一些解決不掉的問題時能夠想起來還有這樣的一種解決方案。

那麼typeevaluator的作用到底是什麼呢?簡單來說,就是告訴動畫系統如何從初始值過度到結束值。我們在上一篇文章中學到的valueanimator.offloat()方法就是實作了初始值與結束值之間的平滑過度,那麼這個平滑過度是怎麼做到的呢?其實就是系統内置了一個floatevaluator,它通過計算告知動畫系統如何從初始值過度到結束值,我們來看一下floatevaluator的代碼實作:

[java] view

plaincopy

Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法
Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法

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實作了typeevaluator接口,然後重寫evaluate()方法。evaluate()方法當中傳入了三個參數,第一個參數fraction非常重要,這個參數用于表示動畫的完成度的,我們應該根據它來計算目前動畫的值應該是多少,第二第三個參數分别表示動畫的初始值和結束值。那麼上述代碼的邏輯就比較清晰了,用結束值減去初始值,算出它們之間的內插補點,然後乘以fraction這個系數,再加上初始值,那麼就得到目前動畫的值了。

好的,那floatevaluator是系統内置好的功能,并不需要我們自己去編寫,但介紹它的實作方法是要為我們後面的功能鋪路的。前面我們使用過了valueanimator的offloat()和ofint()方法,分别用于對浮點型和整型的資料進行動畫操作的,但實際上valueanimator中還有一個ofobject()方法,是用于對任意對象進行動畫操作的。但是相比于浮點型或整型資料,對象的動畫操作明顯要更複雜一些,因為系統将完全無法知道如何從初始對象過度到結束對象,是以這個時候我們就需要實作一個自己的typeevaluator來告知系統如何進行過度。

下面來先定義一個point類,如下所示:

Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法
Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法

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;  

point類非常簡單,隻有x和y兩個變量用于記錄坐标的位置,并提供了構造方法來設定坐标,以及get方法來擷取坐标。接下來定義pointevaluator,如下所示:

Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法
Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法

public class pointevaluator implements typeevaluator{  

    @override  

        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同樣實作了typeevaluator接口并重寫了evaluate()方法。其實evaluate()方法中的邏輯還是非常簡單的,先是将startvalue和endvalue強轉成point對象,然後同樣根據fraction來計算目前動畫的x和y的值,最後組裝到一個新的point對象當中并傳回。

這樣我們就将pointevaluator編寫完成了,接下來我們就可以非常輕松地對point對象進行動畫操作了,比如說我們有兩個point對象,現在需要将point1通過動畫平滑過度到point2,就可以這樣寫:

Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法
Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法

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

代碼很簡單,這裡我們先是new出了兩個point對象,并在構造函數中分别設定了它們的坐标點。然後調用valueanimator的ofobject()方法來建構valueanimator的執行個體,這裡需要注意的是,ofobject()方法要求多傳入一個typeevaluator參數,這裡我們隻需要傳入剛才定義好的pointevaluator的執行個體就可以了。

好的,這就是自定義typeevaluator的全部用法,掌握了這些知識之後,我們就可以來嘗試一下如何通過對point對象進行動畫操作,進而實作整個自定義view的動畫效果。

建立一個myanimview繼承自view,代碼如下所示:

Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法
Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法

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

    protected void ondraw(canvas canvas) {  

        if (currentpoint == null) {  

            currentpoint = new point(radius, radius);  

            drawcircle(canvas);  

            startanimation();  

        } else {  

        }  

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

基本上還是很簡單的,總共也沒幾行代碼。首先在自定義view的構造方法當中初始化了一個paint對象作為畫筆,并将畫筆顔色設定為藍色,接着在ondraw()方法當中進行繪制。這裡我們繪制的邏輯是由currentpoint這個對象控制的,如果currentpoint對象不等于空,那麼就調用drawcircle()方法在currentpoint的坐标位置畫出一個半徑為50的圓,如果currentpoint對象是空,那麼就調用startanimation()方法來啟動動畫。

那麼我們來觀察一下startanimation()方法中的代碼,其實大家應該很熟悉了,就是對point對象進行了一個動畫操作而已。這裡我們定義了一個startpoint和一個endpoint,坐标分别是view的左上角和右下角,并将動畫的時長設為5秒。然後有一點需要大家注意的,就是我們通過監聽器對動畫的過程進行了監聽,每當point值有改變的時候都會回調onanimationupdate()方法。在這個方法當中,我們對currentpoint對象進行了重新指派,并調用了invalidate()方法,這樣的話ondraw()方法就會重新調用,并且由于currentpoint對象的坐标已經改變了,那麼繪制的位置也會改變,于是一個平移的動畫效果也就實作了。

下面我們隻需要在布局檔案當中引入這個自定義控件:

[html] view

Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法
Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法

<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>  

最後運作一下程式,效果如下圖所示:

Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法

ok!這樣我們就成功實作了通過對對象進行值操作來實作動畫效果的功能,這就是valueanimator的進階用法。

objectanimator的基本用法和工作原理在上一篇文章當中都已經講解過了,相信大家都已經掌握。那麼大家應該都還記得,我們在吐槽補間動畫的時候有提到過,補間動畫是隻能實作移動、縮放、旋轉和淡入淡出這四種動畫操作的,功能限定死就是這些,基本上沒有任何擴充性可言。比如我們想要實作對view的顔色進行動态改變,補間動畫是沒有辦法做到的。

但是屬性動畫就不會受這些條條框框的限制,它的擴充性非常強,對于動态改變view的顔色這種功能是完全可是勝任的,那麼下面我們就來學習一下如何實作這樣的效果。

大家應該都還記得,objectanimator内部的工作機制是通過尋找特定屬性的get和set方法,然後通過方法不斷地對值進行改變,進而實作動畫效果的。是以我們就需要在myanimview中定義一個color屬性,并提供它的get和set方法。這裡我們可以将color屬性設定為字元串類型,使用#rrggbb這種格式來表示顔色值,代碼如下所示:

Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法
Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法

    ...  

    private string color;  

    public string getcolor() {  

        return color;  

    public void setcolor(string color) {  

        this.color = color;  

        mpaint.setcolor(color.parsecolor(color));  

        invalidate();  

注意在setcolor()方法當中,我們編寫了一個非常簡單的邏輯,就是将畫筆的顔色設定成方法參數傳入的顔色,然後調用了invalidate()方法。這段代碼雖然隻有三行,但是卻執行了一個非常核心的功能,就是在改變了畫筆顔色之後立即重新整理視圖,然後ondraw()方法就會調用。在ondraw()方法當中會根據目前畫筆的顔色來進行繪制,這樣顔色也就會動态進行改變了。

那麼接下來的問題就是怎樣讓setcolor()方法得到調用了,毫無疑問,當然是要借助objectanimator類,但是在使用objectanimator之前我們還要完成一個非常重要的工作,就是編寫一個用于告知系統如何進行顔色過度的typeevaluator。建立colorevaluator并實作typeevaluator接口,代碼如下所示:

Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法
Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法

public class colorevaluator implements typeevaluator {  

    private int mcurrentred = -1;  

    private int mcurrentgreen = -1;  

    private int mcurrentblue = -1;  

        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;  

    /** 

     * 根據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;  

            currentcolor = (int) (startcolor + (fraction * colordiff - offset));  

            if (currentcolor > endcolor) {  

     * 将10進制顔色值轉換成16進制。 

    private string gethexstring(int value) {  

        string hexstring = integer.tohexstring(value);  

        if (hexstring.length() == 1) {  

            hexstring = "0" + hexstring;  

        return hexstring;  

這大概是我們整個動畫操作當中最複雜的一個類了。沒錯,屬性動畫的進階用法中最有技術含量的也就是如何編寫出一個合适的typeevaluator。好在剛才我們已經編寫了一個pointevaluator,對它的基本工作原理已經有了了解,那麼這裡我們主要學習一下colorevaluator的邏輯流程吧。

首先在evaluate()方法當中擷取到顔色的初始值和結束值,并通過字元串截取的方式将顔色分為rgb三個部分,并将rgb的值轉換成十進制數字,那麼每個顔色的取值範圍就是0-255。接下來計算一下初始顔色值到結束顔色值之間的內插補點,這個內插補點很重要,決定着顔色變化的快慢,如果初始顔色值和結束顔色值很相近,那麼顔色變化就會比較緩慢,而如果顔色值相差很大,比如說從黑到白,那麼就要經曆255*3這個幅度的顔色過度,變化就會非常快。

那麼控制顔色變化的速度是通過getcurrentcolor()這個方法來實作的,這個方法會根據目前的fraction值來計算目前應該過度到什麼顔色,并且這裡會根據初始和結束的顔色內插補點來控制變化速度,最終将計算出的顔色進行傳回。

最後,由于我們計算出的顔色是十進制數字,這裡還需要調用一下gethexstring()方法把它們轉換成十六進制字元串,再将rgb顔色拼裝起來之後作為最終的結果傳回。

好了,colorevaluator寫完之後我們就把最複雜的工作完成了,剩下的就是一些簡單調用的問題了,比如說我們想要實作從藍色到紅色的動畫過度,曆時5秒,就可以這樣寫:

Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法
Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法

objectanimator anim = objectanimator.ofobject(myanimview, "color", new colorevaluator(),   

    "#0000ff", "#ff0000");  

用法非常簡單易懂,相信不需要我再進行解釋了。

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

Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法
Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法

        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當中執行的,是以objectanimator.ofobject()的第一個參數直接傳this就可以了。接着我們又建立了一個animatorset,并把兩個動畫設定成同時播放,動畫時長為五秒,最後啟動動畫。現在重新運作一下代碼,效果如下圖所示:

Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的進階用法

ok,位置動畫和顔色動畫非常融洽的結合到一起了,看上去效果還是相當不錯的,這樣我們就把objectanimator的進階用法也掌握了。

繼續閱讀