淺析Android動畫(二),屬性動畫高級實例探究


轉載請注明出處!http://www.cnblogs.com/wondertwo/p/5312482.html


ObjectAnimator實現屬性動畫

為了寫好Android動畫這幾篇博客,在動筆之前我是下過很大決心的,我對自己的要求是盡量把一個小知識點寫清楚寫明白,說白了就是相對於大而全的長篇大論,我更傾向於去寫小而美的博客!為了保證在高產的同時能堅持每篇博客質量上讓我滿意,我翻閱了很多大牛的博客,這其中尤以郭霖大神的博客我印象最為深刻,也給我帶來了很多啟發,在此表示感謝並在博客的最后貼了出來供大家參考!另外值得一提的是,在這篇博客的第三部分,會對兩個非常酷炫的屬性動畫實例進行分析,分別是桌面彈球動畫和仿Win10系統開機小圓點旋轉動畫,為了激發大家閱讀的興趣,我們先來看一下效果如何?

下面言歸正傳,要了解屬性動畫我還是習慣先去翻看谷歌的API文檔介紹,相對視圖動畫Added in API level 1,屬性動畫Added in API level 11,那我們可能會糾結既然已經有了視圖動畫,為什么還要加入屬性動畫呢?仔細來看屬性動畫雖然叫做動畫,但是其意義已經不僅僅局限於實現炫酷的動畫啦,借助於插值器(Interpolator)和估值器(TypeEvaluator),我們可以更具體的描述他:一種按照一定變化率對屬性值進行操作的機制,變化率就是依賴Interpolator控制,而值操作則是TypeEvaluator控制!由此可見,屬性動畫不是一般的強大,與視圖動畫的區別主要在以下幾點:

  • 屬性動畫作用的對象可以是任何一個Object對象,也就是說我們完全可以給任意Object對象設置屬性動畫,而這個對象可以不是一個View組件,也不管這個對象是否是可見的,而視圖動畫的作用對象只能是一個View對象,這是最大的不同;
  • 視圖動畫的一個致命缺陷就是,通過視圖動畫將一個View對象(比如一個TextViewButton)位置改編后,該對象的觸摸事件的焦點依然在原位置,而這在實際的開發中是不能容忍的,屬性動畫就很好的解決了這一缺陷;
  • 屬性動畫可以控制動畫執行過程中的任意時刻的任意屬性值,這么說可能不好理解,但是大家肯定知道,我在第一篇博客 [淺析Android動畫(一),View動畫高級實例 http://www.cnblogs.com/wondertwo/p/5295976.html ] 中也提及,視圖動畫從本質上來說是一種補間動畫,他只對動畫的起始值和結束值進行賦值,而動畫中間執行過程中的屬性值則是系統幫我們計算的。那我們怎樣自己用代碼控制動畫執行過程中的屬性值呢?屬性動畫就提供了很好地解決方案,就是自定義估值器;

那么屬性動畫是怎樣完美的解決上述問題的呢?下面就開始學習屬性動畫的基本用法,我們來看屬性動畫的繼承關系,如下如所示:

顯然關注的焦點應該是ValueAnimatorObjectAnimator這兩個類啦,ObjectAnimator繼承自ValueAnimator,是屬性動畫中非常重要的一個實現類,通過ObjectAnimator類的靜態歐工廠方法來創建ObjectAnimator對象,這些靜態工廠方法包括:ObjectAnimator.ofFloat()ObjectAnimator.ofInt()等等,當然最為重要的一個靜態工廠方法是ObjectAnimator.ofObject(),可以接收一個Object對象並為其設置屬性動畫,瞬間高大上了有木有?這些靜態工廠方法接收的參數分別是:

  1. 要設置動畫的目標對象;
  2. 動畫的屬性類型;
  3. 一個或多個屬性值;當只指定一個屬性值,系統默認此值為結束值;當指定兩個屬性值,系統默認分別為起始值和結束值;當指定三個或三個以上時,系統默認線性插值;

ValueAnimator是整個屬性動畫機制當中最核心的一個類,前面我們已經提到了,屬性動畫的運行機制是通過不斷地對值進行操作來實現的,而初始值和結束值之間的動畫過渡就是由ValueAnimator這個類來負責計算的,ValueAnimator對過渡動畫值的計算依靠一個時間因子fraction,而這個時間因子fraction是系統由setDuration()方法設置的動畫執行時間通過計算得來的,所以ValueAnimator還負責管理動畫的持續時間、播放次數、播放模式、以及對動畫設置監聽器等,確實是一個非常重要的類。ValueAnimator使用起來也很簡單,會在本篇博客的的二部分詳細講解!下面看一個實例:通過ValueAnimator的子類ObjectAnimator實現影子效果,類似於西游記中的元神出竅,哈哈是不是很好玩?先看效果如下:

那這個效果是怎么實現的呢?你所看到的四個影子並不是真實的影子,而是把四張相同的圖片設置了半透明效果,並同時向四個不同的方向做位移變換。布局很簡單,就是在根布局RelativeLayout中放置五個ImageViewsrc值都引用同一個圖片的資源id,這樣五張圖片就會重疊在一起,因此造成了你看到的只有一張圖片的假象,動畫實現類EffectAni的代碼如下:

package com.wondertwo.effect;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.animation.BounceInterpolator;
import android.widget.ImageView;
import android.widget.Toast;

import com.wondertwo.R;

import java.util.ArrayList;


/**
 * 屬性動畫PropertyAni
 *
 * 常用的屬性動畫的屬性值有:
 *       - translationX、translationY----控制view對象相對其左上角坐標在X、Y軸上偏移的距離
 *       - rotation、rotationX、rotationY----控制view對象繞支點進行2D和3D旋轉
 *       - scaleX、scaleY----控制view對象繞支點進行2D縮放
 *       - pivotX、pivotY----控制view對象的支點位置,這個位置一般就是view對象的中心點。圍繞這個支點可以進行旋轉和縮放處理
 *       - x、y----描述view對象在容器中的最終位置,是最初的左上角坐標和translationX、translationY值的累計和
 *       - alpha----表示view對象的透明度。默認值是1(完全透明)、0(不透明)
 *
 * Created by wondertwo on 2016/3/11.
 */
public class EffectAni extends AppCompatActivity implements View.OnClickListener {

    // ImageView組件id數組
    private int[] mRes = new int[]{R.id.iv_a, R.id.iv_b, R.id.iv_c, R.id.iv_d, R.id.iv_e};
    // ImageView對象集合
    private ArrayList<ImageView> mImViews = new ArrayList<>();
    private boolean flag = true;// 啟動動畫、關閉動畫的標記位

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_effect);
        // for循環創建ImageView對象,並添加到集合中
        for (int i = 0; i < mRes.length; i++) {
            ImageView iv_a = (ImageView) findViewById(mRes[i]);
            iv_a.setOnClickListener(this);
            mImViews.add(iv_a);
        }
    }

    // 按鈕點擊事件
    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.iv_a:
                if (flag) {
                    startAnim();
                } else {
                    closeAnim();
                }
                break;
            default:
                Toast.makeText(EffectAni.this, "" + v.getId(), Toast.LENGTH_SHORT).show();
                break;
        }
    }

    // 關閉動畫
    private void closeAnim() {
        // 創建ObjectAnimator屬性對象,參數分別是動畫要設置的View對象、動畫屬性、屬性值
        ObjectAnimator animator0 = ObjectAnimator.ofFloat(mImViews.get(0),
                                                            "alpha",
                                                            0.5F,
                                                            1F);
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(mImViews.get(1),
                                                            "translationY",
                                                            200F,
                                                            0);
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(mImViews.get(2),
                                                            "translationX",
                                                            200F,
                                                            0);
        ObjectAnimator animator3 = ObjectAnimator.ofFloat(mImViews.get(3),
                                                            "translationY",
                                                            -200F,
                                                            0);
        ObjectAnimator animator4 = ObjectAnimator.ofFloat(mImViews.get(4),
                                                            "translationX",
                                                            -200F,
                                                            0);
        AnimatorSet aniSet = new AnimatorSet();
        aniSet.setDuration(4000);
        aniSet.setInterpolator(new BounceInterpolator());// 彈跳效果的插值器
        aniSet.playTogether(animator0,
                            animator1,
                            animator2,
                            animator3,
                            animator4);// 同時啟動5個動畫
        aniSet.start();

        // 重置標記位
        flag = true;
    }

    // 啟動動畫
    private void startAnim() {
        // 創建ObjectAnimator屬性對象,參數分別是動畫要設置的View對象、動畫屬性、屬性值
        ObjectAnimator animator0 = ObjectAnimator.ofFloat(
                                                        mImViews.get(0),
                                                        "alpha",
                                                        1f,
                                                        0.5f);
        ObjectAnimator animator1 = ObjectAnimator.ofFloat(
                                                        mImViews.get(1),
                                                        "translationY",
                                                        200f);
        ObjectAnimator animator2 = ObjectAnimator.ofFloat(
                                                        mImViews.get(2),
                                                        "translationX",
                                                        200f);
        ObjectAnimator animator3 = ObjectAnimator.ofFloat(
                                                        mImViews.get(3),
                                                        "translationY",
                                                        -200f);
        ObjectAnimator animator4 = ObjectAnimator.ofFloat(
                                                        mImViews.get(4),
                                                        "translationX",
                                                        -200f);
        AnimatorSet aniSet = new AnimatorSet();
        aniSet.setDuration(4000);
        aniSet.setInterpolator(new BounceInterpolator());// 彈跳效果的插值器
        aniSet.playTogether(animator0,
                            animator1,
                            animator2,
                            animator3,
                            animator4);// 同時啟動5個動畫
        aniSet.start();

        // 重置標記位
        flag = false;
    }
}

上面代碼看起來挺長,但是實現屬性動畫的代碼也沒幾行代碼,屬性動畫的邏輯就是startAnim()closeAnim()這兩個方法,先來看啟動動畫的方法startAnim(),上面也提到過,首先是通過ObjectAnimator.ofFloat()靜態工廠方法創建ObjectAnimator對象,可以看到上面一共創建了5個ObjectAnimator對象,傳入的第一個參數就是我們布局文件中的五個ImageView,但是注意了,給他們設置的屬性動畫卻是各不相同的,對應的第二個參數就表示要設置的屬性對應的字符串,系統會自動解析它們對應的是哪個屬性,我們分別傳入了"alpha""translationY""translationX",你肯定很清楚是用來設置透明度、Y軸方向平移、X軸方向平移的。除了上述幾個,還可以傳入"rotation""rotationY""rotationX""rotationZ""scaleY""scaleX"等等,而關閉動畫方法closeAnim()的作用正好相反,把剛才變化的動畫移回原位。最后我們注冊了點擊事件,判斷一個布爾型的標記位的值,使響應事件分別調用開啟動畫和關閉動畫這兩個方法。

以上介紹的這些就是ObjectAnimator的最基本的用法,同時也是Android開發者進階必須掌握的內容,當然作為一個很有上進心的開發者,要求當然不能是只是會用基本用法這么低,我們再繼續來學習屬性動畫的監聽和ValueAnimator的用法,這就比上面的要高級很多了。


ValueAnimator和屬性動畫的監聽

上面在學習ObjectAnimator的使用方法的同時,已經對ValueAnimator的繼承關系進行了初步的介紹,下面更進一步,我們一起來更深入的學習ValueAnimator,如果要用一句話來概括ValueAnimator的特性,可以這樣概括,在屬性動畫的執行過程中,不斷計算並修改動畫的屬性值。這樣說可能比較晦澀難懂,但接下來就會有一個實例,看完這個實例你就明白為什么會這樣描述ValueAnimator

考慮這樣一個場景,我們希望在6秒內把一個view控件的背景顏色從從紅色漸變到藍色,我們該怎樣去實現它呢?不用想了,前面學過的視圖動畫完成不了這個需求,就是這樣一個簡單的不能再簡單的需求,就能看出視圖動畫的局限性了。對的,那我們還有屬性動畫呢,用屬性動畫就可以輕松實現上面的場景啦,不過在此之前我們還需要了解一下屬性動畫的原理到底是什么?

屬性動畫需要不斷改變對象的某個屬性值,從而達到動畫的效果,那么問題來了,既然是改變對象的屬性值,比如上面所說的view控件的背景顏色這一屬性的值,那么背景顏色這個屬性一定是要不斷地反復的被賦值並在手機屏幕上顯示出來的,注意上面的這句話,我們首先應該畫出的關鍵字是“不斷地被賦值”這幾個字,說到賦值,Android系統最常見的取值、賦值方法自然是settergetter方法啦,所以自然而然的,屬性動畫要求定義的屬性必須有settergetter方法,就算沒有getter方法在某些特殊的情況下是允許的,但是所有情況下setter方法必須要有,如果系統沒有提供那就需要我們自己動手去寫setter方法啦!其次對於上面那句話我們畫出的關鍵字,你是否注意到有“不斷地”這三個字?不斷地賦值那么這些值是怎么來的呢?我可以告訴你有兩種方式來不斷得到這些值:

  • ValueAnimator對象設置動畫監聽,代碼如下所示:valueAnimator.addUpdateListener(),需要傳入一個AnimatorUpdateListener對象,一般我們傳入的是AnimatorUpdateListener的匿名對象,即:valueAnimator.addUpdateListener(new AnimatorUpdateListener(){...}),需要重寫它的onAnimationUpdate()方法,那么上述值的計算邏輯就放在onAnimationUpdate()方法體內;
  • 重寫TypeEvaluatorTypeEvaluator這個詞直譯過來就是類型值算法,也被譯作估值器,我覺得這個叫法很形象,因為他就是用來計算屬性動畫某個時刻的屬性值的具體值的,關於估值器和插值器我會在下一篇博客中詳細介紹;

現在繼續來完成上面提到的這個場景,這也是我認為本篇博客最值得一看的地方:在6秒內把一個view控件的背景顏色從從紅色漸變到藍色。先來看效果圖如下,這里我們實現的是把一個按鈕的背景顏色從藍色漸變到紅色,並且同時做縮放動畫,效果還是很明顯的。

布局文件很簡單,在RelativeLayout中定義了一個Button,我們來看主要代碼,實現這種效果的代碼BuleToRed.java我貼出來,下面會詳細分析實現的細節:

package com.wondertwo.propertyanime;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;

/**
 * BuleToRed實現目標對象背景色的漸變
 * Created by wondertwo on 2016/3/23.
 */
public class BuleToRed extends Activity {

    private Button targetView;
    private int mCurrentRed = -1;
    private int mCurrentGreen = -1;
    private int mCurrentBlue = -1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_blue_to_red);

        targetView = (Button) findViewById(R.id.tv_color_backgroound);

        /**
         * 注冊點擊事件,展示效果
         */
        targetView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                displayResult(targetView, "#0000ff", "#ff0000");
            }
        });
    }

    /**
     * displayResult()展示結果
     */
    private void displayResult(final View target, final String start, final String end) {
        // 創建ValueAnimator對象,實現顏色漸變
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(1f, 100f);

        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                // 獲取當前動畫的進度值,1~100
                float currentValue = (float) animation.getAnimatedValue();
                Log.d("當前動畫值", "current value : " + currentValue);

                // 獲取動畫當前時間流逝的百分比,范圍在0~1之間
                float fraction = animation.getAnimatedFraction();
                // 直接調用evaluateForColor()方法,通過百分比計算出對應的顏色值
                String colorResult = evaluateForColor(fraction, start, end);

                /**
                 * 通過Color.parseColor(colorResult)解析字符串顏色值,傳給ColorDrawable,創建ColorDrawable對象
                 */
                /*LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) target.getLayoutParams();*/
                ColorDrawable colorDrawable = new ColorDrawable(Color.parseColor(colorResult));
                // 把ColorDrawable對象設置為target的背景
                target.setBackground(colorDrawable);
                target.invalidate();
            }
        });
        valueAnimator.setDuration(6 * 1000);


        // 組裝縮放動畫
        ValueAnimator animator_1 = ObjectAnimator.ofFloat(target, "scaleX", 1f, 0.5f);
        ValueAnimator animator_2 = ObjectAnimator.ofFloat(target, "scaleY", 1f, 0.5f);
        ValueAnimator animator_3 = ObjectAnimator.ofFloat(target, "scaleX", 0.5f, 1f);
        ValueAnimator animator_4 = ObjectAnimator.ofFloat(target, "scaleY", 0.5f, 1f);
        AnimatorSet set_1 = new AnimatorSet();
        set_1.play(animator_1).with(animator_2);
        AnimatorSet set_2 = new AnimatorSet();
        set_2.play(animator_3).with(animator_4);
        AnimatorSet set_3 = new AnimatorSet();
        set_3.play(set_1).before(set_2);
        set_3.setDuration(3 * 1000);

        // 組裝顏色動畫和縮放動畫,並啟動動畫
        AnimatorSet set_4 = new AnimatorSet();
        set_4.play(valueAnimator).with(set_3);
        set_4.start();
    }

    /**
     * evaluateForColor()計算顏色值並返回
     */
    private String evaluateForColor(float fraction, String startValue, String endValue) {

        String startColor = startValue;
        String endColor = 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;
            }
        } 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;
    }
}

乍一看起來似乎代碼很長很繁瑣,有170多行,但是別急,都是你見過的知道的東西,所以分析起來會很簡單!在BuleToRed.java類中,首先我們在onCreate()方法中拿到目標對象,也就是我們定義的Button對象,可以看到屬性動畫的啟動入口就是Button的點擊事件中displayResult()方法,再往下看我們發現displayResult(final View target, final String start, final String end)方法接收三個參數,分別是:

  • 要設置顏色漸變的目標對象的實例,這里我們直接傳入了Button按鈕的實例對象;
  • 顏色起始值,我們傳入"#0000ff",即藍色;
  • 顏色結束值,我們傳入"#ff0000",即紅色;

displayResult()方法中,先是創建ValueAnimator對象用於實現顏色漸變的動畫效果,我們為ValueAnimator對象valueAnimator設置了監聽器ValueAnimator.AnimatorUpdateListener(),動畫的執行過程中會不斷回調AnimatorUpdateListener()中的onAnimationUpdate(ValueAnimator animation)方法,所以要實現背景顏色的漸變效果,則控制顏色漸變的邏輯必須要放在onAnimationUpdate()中,緊接着我們在onAnimationUpdate()中通過animation.getAnimatedValue()拿到監聽的數值,代碼如下:

// 獲取當前動畫的進度值,1~100
float currentValue = (float) animation.getAnimatedValue();
Log.d("當前動畫值", "current value : " + currentValue);

並獲取當前時間流逝所占的百分比參數fraction,接着調用evaluateForColor(fraction, start, end)方法,這個方法就是專門負責計算當前對應的顏色值,需要傳入我們剛才計算出來的fraction參數,代碼如下:

// 獲取動畫當前時間流逝的百分比,范圍在0~1之間
float fraction = animation.getAnimatedFraction();
// 直接調用evaluateForColor()方法,通過百分比計算出對應的顏色值
String colorResult = evaluateForColor(fraction, start, end);

很自然的,我們用String colorResult來接受evaluateForColor()方法返回的顏色值,是一個十六進制的字符串,到這里你可能會糾結我們怎么才能把這個字符串解析出來並設置給目標對象呢?代碼如下:

/**
 * 通過Color.parseColor(colorResult)解析字符串顏色值,傳給ColorDrawable,創建ColorDrawable對象
 */
ColorDrawable colorDrawable = new ColorDrawable(Color.parseColor(colorResult));
// 把ColorDrawable對象設置為target的背景
target.setBackground(colorDrawable);
target.invalidate();

給目標對象設置背景target.setBackground(colorDrawable)setBackground()方法接收一個Drawable對象,順水推舟我們很容易就會聯想到ColorDrawableColorDrawableDrawable接口的一個實現類,我們只需要創建一個ColorDrawable對象並把它傳給setBackground()方法就OK,而ColorDrawable的構造方法需要接收一個int類型的顏色值,這個好辦,我們用Color類的靜態工廠方法parseColor()把字符串顏色值colorResult解析成int類型的顏色值傳進去就好,代碼是這樣的:Color.parseColor(colorResult),到這里displayResult()方法就講完了,因為后面的那幾行代碼已經出現過好多次了,就是在顏色漸變的同時給目標對象再加一個縮放的動畫效果。

接下來我還想再補充一下,把evaluateForColor()方法計算顏色值的具體過程在這里分析一下,其實計算顏色值的邏輯也不復雜,首先計算出紅綠藍三種顏色的對應的初始值和結束值,然后根據初始值和結束值之間的差值來計算當前對應的顏色值,getCurrentColor(startGreen, endGreen, colorDiff, redDiff, fraction)就是完成這個計算的邏輯所在,接着把計算得到的三種顏色值的int型數據轉換為十六進制字符串數據,並把它們組裝在一起后返回,而getHexString()方法就負責將int型顏色值數據轉換為十六進制數據。


桌面彈球和Win10開機小圓點旋轉動畫的實例探究

我們先來分析簡單一點的Win10開機小圓點旋轉動畫,用過Win10系統的同學都應該知道,Win10開機系統初始化的時候會顯示一圈環形小圓點旋轉的動畫,相信這個動畫效果我一說你肯定歷歷在目記憶猶新,先來看一下最終的效果圖如下:

作為對ObjectAniumator的用法的高級探究,其實他還是很簡單的,布局文件先定義了4個小圓點ImageView,把每個小圓點ImageView都放在了一個LinearLayout中,這很簡單!說到繪制小圓點,我比較推薦的一種做法是在res/drawable目錄下直接通過xml定義shape資源文件,這樣定義的好處是可以避免使用圖片資源造成不必要的內存占用。這里我把我的小圓點定義代碼貼一下:

<?xml version="1.0" encoding="utf-8"?>
<shape 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">

    <solid android:color="@android:color/holo_red_dark" />

</shape>

實際上我們定義的只是一個橢圓,要顯示出小圓點我們需要指定它的寬高相等,即android:layout_widthandroid:layout_height的值要相等,否則就會顯示成橢圓。然后只需要像引用圖片資源一樣,在drawable目錄下引用它就好,比如:

<LinearLayout
    android:id="@+id/ll_point_circle_4"
    android:layout_width="wrap_content"
    android:layout_height="240dp"
    android:layout_centerInParent="true"
    android:orientation="vertical">
    <ImageView
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:src="@drawable/shape_point" />
</LinearLayout>

下面是完整的布局文件,僅供參考:

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

    <Button
        android:id="@+id/start_ani_2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:text="@string/start_ani" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerVertical="true">

        <LinearLayout
            android:id="@+id/ll_point_circle_1"
            android:layout_width="wrap_content"
            android:layout_height="240dp"
            android:layout_centerInParent="true"
            android:orientation="vertical">
            <ImageView
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:src="@drawable/shape_point"
                />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/ll_point_circle_2"
            android:layout_width="wrap_content"
            android:layout_height="240dp"
            android:layout_centerInParent="true"
            android:orientation="vertical">
            <ImageView
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:src="@drawable/shape_point"
                />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/ll_point_circle_3"
            android:layout_width="wrap_content"
            android:layout_height="240dp"
            android:layout_centerInParent="true"
            android:orientation="vertical">
            <ImageView
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:src="@drawable/shape_point"
                />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/ll_point_circle_4"
            android:layout_width="wrap_content"
            android:layout_height="240dp"
            android:layout_centerInParent="true"
            android:orientation="vertical">
            <ImageView
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:src="@drawable/shape_point"
                />
        </LinearLayout>

    </RelativeLayout>

</RelativeLayout>

接着把CircleProgress屬性動畫類的代碼貼出來,並在CircleProgress屬性動畫類中拿到上面4個小圓點的對象,動畫實現的細節會在代碼后面詳細講解:

package com.wondertwo.propertyanime;

import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.Button;
import android.widget.LinearLayout;

/**
 * ObjectAnimator高級實例探究
 * Created by wondertwo on 2016/3/22.
 */
public class CircleProgress extends Activity {

    private LinearLayout mPoint_1;
    private LinearLayout mPoint_2;
    private LinearLayout mPoint_3;
    private LinearLayout mPoint_4;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_circle_progress);

        mPoint_1 = (LinearLayout) findViewById(R.id.ll_point_circle_1);
        mPoint_2 = (LinearLayout) findViewById(R.id.ll_point_circle_2);
        mPoint_3 = (LinearLayout) findViewById(R.id.ll_point_circle_3);
        mPoint_4 = (LinearLayout) findViewById(R.id.ll_point_circle_4);

        Button startAni = (Button) findViewById(R.id.start_ani_2);
        startAni.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                beginPropertyAni();
            }
        });
    }

    /**
     * 開啟動畫
     */
    private void beginPropertyAni() {
        ObjectAnimator animator_1 = ObjectAnimator.ofFloat(
                mPoint_1,
                "rotation",
                0,
                360);
        animator_1.setDuration(2000);
        animator_1.setInterpolator(new AccelerateDecelerateInterpolator());

        ObjectAnimator animator_2 = ObjectAnimator.ofFloat(
                mPoint_2,
                "rotation",
                0,
                360);
        animator_2.setStartDelay(150);
        animator_2.setDuration(2000 + 150);
        animator_2.setInterpolator(new AccelerateDecelerateInterpolator());

        ObjectAnimator animator_3 = ObjectAnimator.ofFloat(
                mPoint_3,
                "rotation",
                0,
                360);
        animator_3.setStartDelay(2 * 150);
        animator_3.setDuration(2000 + 2 * 150);
        animator_3.setInterpolator(new AccelerateDecelerateInterpolator());

        ObjectAnimator animator_4 = ObjectAnimator.ofFloat(
                mPoint_4,
                "rotation",
                0,
                360);
        animator_4.setStartDelay(3 * 150);
        animator_4.setDuration(2000 + 3 * 150);
        animator_4.setInterpolator(new AccelerateDecelerateInterpolator());

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.play(animator_1).with(animator_2).with(animator_3).with(animator_4);
        animatorSet.start();
    }
}

代碼確實不長只有80多行,但是麻雀雖小五臟俱全,很顯然beginPropertyAni()方法就是啟動動畫的方法,調用ObjectAnimator.ofFloat()靜態工廠方法創建ObjectAnimator對象我就不解釋了,很容易看懂!重點來了,Win10開機小圓點旋轉動畫的難點不在旋轉,如果我們把旋轉的最高點看作是旋轉的起始點,小圓點的旋轉是一個先加速后減速的過程,這恰好符合高中物理的規律,小球內切圓環軌道做圓周運動,不知道我這樣解釋是不是很形象呢?那么控制旋轉的加速度很好辦,只要設置一個AccelerateDecelerateInterpolator()插值器就OK,但是我們發現,這不是一個小球在旋轉,而是有4個同時在旋轉,而且旋轉還不同步,這又該如何解決呢?你只要從第二個小球開始,每個小球設置固定時間間隔的延時啟動,就能完美解決上面的問題。代碼是這樣的:

animator_2.setStartDelay(150);
animator_3.setStartDelay(2 * 150);
animator_4.setStartDelay(3 * 150);

寫到這里已經三萬字了,最后一起來學習一個桌面彈球動畫,這也是這篇博客的收尾工作。老習慣我們還是先展示桌面彈球動畫的酷炫效果吧:

在動畫中可以清晰的看到小球下落過程中的加速運動,碰到桌面(手機屏幕的底部)后的變形壓扁,以及小球彈起的動畫,非常形象生動!先貼代碼后面再做分析:

package com.wondertwo.propertyanime;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.Shader;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.LinearLayout;

import java.util.ArrayList;

/**
 * 小球下落動畫加強版XBallsFallActivity,增加了小球桌底時的壓扁、回彈動畫
 * Created by wondertwo on 2016/3/20.
 */
public class XBallsFallActivity extends Activity {

    static final float BALL_SIZE = 50f;// 小球直徑
    static final float FULL_TIME = 1000;// 下落時間

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

        LinearLayout xContainer = (LinearLayout) findViewById(R.id.xcontainer);

        // 設置要顯示的view組件
        xContainer.addView(new XBallView(this));
    }

    /**
     * 自定義動畫組件XBallView
     */
    public class XBallView extends View implements ValueAnimator.AnimatorUpdateListener {

        public final ArrayList<XShapeHolder> balls = new ArrayList<>();// 創建balls集合來存儲XShapeHolder對象

        public XBallView(Context context) {
            super(context);
            setBackgroundColor(Color.WHITE);
        }

        @Override
        public boolean onTouchEvent(MotionEvent event) {
            // 屏蔽ACTION_UP事件
            if (event.getAction() != MotionEvent.ACTION_DOWN && event.getAction() != MotionEvent.ACTION_MOVE) {
                return false;
            }
            // 在ACTION_DOWN事件發生點生成小球
            XShapeHolder newBall = addBall(event.getX(), event.getY());
            // 計算小球下落動畫開始時Y坐標
            float startY = newBall.getY();
            // 計算小球下落動畫結束時的Y坐標,即屏幕高度減去startY
            float endY = getHeight() - BALL_SIZE;
            // 獲取屏幕高度
            float h = (float) getHeight();
            float eventY = event.getY();
            // 計算動畫持續時間
            int duration = (int) (FULL_TIME * ((h - eventY) / h));

            /**
             * 下面開始定義小球的下落,着地壓扁,反彈等屬性動畫
             */
            // 定義小球下落動畫
            ValueAnimator fallAni = ObjectAnimator.ofFloat(
                    newBall,
                    "y",
                    startY,
                    endY);
            // 設置動畫持續時間
            fallAni.setDuration(duration);
            // 設置加速插值器
            fallAni.setInterpolator(new AccelerateInterpolator());
            // 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
            fallAni.addUpdateListener(this);

            // 定義小球壓扁動畫,控制小球x坐標左移半個球寬度
            ValueAnimator squashshAni1 = ObjectAnimator.ofFloat(
                    newBall,
                    "x",
                    newBall.getX(),
                    newBall.getX() - BALL_SIZE / 2);
            squashshAni1.setDuration(duration / 4);
            squashshAni1.setRepeatCount(1);
            squashshAni1.setRepeatMode(ValueAnimator.REVERSE);
            squashshAni1.setInterpolator(new DecelerateInterpolator());
            // 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
            squashshAni1.addUpdateListener(this);

            // 定義小球壓扁動畫,控制小球寬度加倍
            ValueAnimator squashshAni2 = ObjectAnimator.ofFloat(
                    newBall,
                    "width",
                    newBall.getWidth(),
                    newBall.getWidth() + BALL_SIZE);
            squashshAni2.setDuration(duration / 4);
            squashshAni2.setRepeatCount(1);
            squashshAni2.setRepeatMode(ValueAnimator.REVERSE);
            squashshAni2.setInterpolator(new DecelerateInterpolator());
            // 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
            squashshAni2.addUpdateListener(this);

            // 定義小球拉伸動畫, 控制小球的y坐標下移半個球高度
            ValueAnimator stretchAni1 = ObjectAnimator.ofFloat(
                    newBall,
                    "y",
                    endY,
                    endY + BALL_SIZE / 2);
            stretchAni1.setDuration(duration / 4);
            stretchAni1.setRepeatCount(1);
            stretchAni1.setRepeatMode(ValueAnimator.REVERSE);
            stretchAni1.setInterpolator(new DecelerateInterpolator());
            // 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
            stretchAni1.addUpdateListener(this);

            // 定義小球拉伸動畫, 控制小球的高度減半
            ValueAnimator stretchAni2 = ObjectAnimator.ofFloat(
                    newBall,
                    "height",
                    newBall.getHeight(),
                    newBall.getHeight() - BALL_SIZE / 2);
            stretchAni2.setDuration(duration / 4);
            stretchAni2.setRepeatCount(1);
            stretchAni2.setRepeatMode(ValueAnimator.REVERSE);
            stretchAni2.setInterpolator(new DecelerateInterpolator());
            // 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
            stretchAni2.addUpdateListener(this);

            // 定義小球彈起動畫
            ValueAnimator bounceAni = ObjectAnimator.ofFloat(
                    newBall,
                    "y",
                    endY,
                    startY);
            bounceAni.setDuration(duration);
            bounceAni.setInterpolator(new DecelerateInterpolator());
            // 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
            bounceAni.addUpdateListener(this);

            // 定義AnimatorSet,按順序播放[下落、壓扁&拉伸、彈起]動畫
            AnimatorSet set = new AnimatorSet();
            //在squashshAni1之前播放fallAni
            set.play(fallAni).before(squashshAni1);
            /**
             * 由於小球彈起時壓扁,即寬度加倍,x坐標左移,高度減半,y坐標下移
             * 因此播放squashshAni1的同時還要播放squashshAni2,stretchAni1,stretchAni2
             */
            set.play(squashshAni1).with(squashshAni2);
            set.play(squashshAni1).with(stretchAni1);
            set.play(squashshAni1).with(stretchAni2);
            // 在stretchAni2之后播放bounceAni
            set.play(bounceAni).after(stretchAni2);

            // newBall對象的漸隱動畫,設置alpha屬性值1--->0
            ObjectAnimator fadeAni = ObjectAnimator.ofFloat(
                    newBall,
                    "alpha",
                    1f,
                    0f);
            // 設置動畫持續時間
            fadeAni.setDuration(250);
            // 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
            fadeAni.addUpdateListener(this);

            // 為fadeAni設置監聽
            fadeAni.addListener(new AnimatorListenerAdapter() {
                // 動畫結束
                @Override
                public void onAnimationEnd(Animator animation) {
                    // 動畫結束時將該動畫關聯的ShapeHolder刪除
                    balls.remove(((ObjectAnimator) (animation)).getTarget());
                }
            });

            // 再次定義一個AnimatorSet動畫集合,來組合動畫
            AnimatorSet aniSet = new AnimatorSet();
            // 指定在fadeAni之前播放set動畫集合
            aniSet.play(set).before(fadeAni);

            // 開始播放動畫
            aniSet.start();

            return true;
        }

        @Override
        protected void onDraw(Canvas canvas) {
            for (XShapeHolder xShapeHolder : balls) {
                canvas.save();
                canvas.translate(xShapeHolder.getX(), xShapeHolder.getY());
                xShapeHolder.getShape().draw(canvas);
                canvas.restore();
            }
        }

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // 指定重繪界面
            this.invalidate();
        }

        /**
         * addBall()方法返回XShapeHolder對象,ShapeHolder對象持有小球
         */
        private XShapeHolder addBall(float x, float y) {
            // 創建一個橢圓
            OvalShape circle = new OvalShape();
            // 設置橢圓寬高
            circle.resize(BALL_SIZE, BALL_SIZE);
            // 把橢圓包裝成Drawable對象
            ShapeDrawable drawble = new ShapeDrawable(circle);
            // 創建XShapeHolder對象
            XShapeHolder holder = new XShapeHolder(drawble);
            // 設置holder坐標
            holder.setX(x - BALL_SIZE / 2);
            holder.setY(y - BALL_SIZE / 2);

            // 生成隨機組合的ARGB顏色
            int red = (int) (Math.random() * 255);
            int green = (int) (Math.random() * 255);
            int blue = (int) (Math.random() * 255);
            // 把red,green,blue三個顏色隨機數組合成ARGB顏色
            int color = 0xff000000 + red << 16 | green << 8 | blue;
            // 把red,green,blue三個顏色隨機數除以4得到商值組合成ARGB顏色
            int darkColor = 0xff000000 | red / 4 << 16 | green / 4 << 8 | blue / 4;

            // 創建圓形漸變效果
            RadialGradient gradient = new RadialGradient(
                    37.5f,
                    12.5f,
                    BALL_SIZE,
                    color,
                    darkColor,
                    Shader.TileMode.CLAMP);

            // 獲取drawble關聯的畫筆
            Paint paint = drawble.getPaint();
            paint.setShader(gradient);

            // 為XShapeHolder對象設置畫筆
            holder.setPaint(paint);
            balls.add(holder);
            return holder;
        }
    }
}

這次的代碼挺長有260多行,如果把它拆分開來,你會覺得代碼還是原來的套路,還是很熟悉的有木有?我們首先來看,彈球動畫類XBallsFallActivity中的代碼分為兩塊,一是onCreate()方法,這是每個Activity都要重寫的方法,那我們在onCreate()方法中干了什么呢?只干了一件事就是拿到LinearLayout布局的對象,並調用addBall()方法給它添加XBallView這個view對象,代碼是這樣的:

xContainer.addView(new XBallView(this));

XBallView對象又是什么鬼呢?一個自定義view組件,也就是實現我們小球的view組件,這也是我們這個動畫的難點所在,我們慢慢來分析,代碼定位到XBallView類,第一眼你會發現這個類不僅繼承了View類,而且還實現了ValueAnimator.AnimatorUpdateListener這樣一個接口,再仔細一看你又會發現,這個接口怎么聽起來這么耳熟呢?沒錯,這就是上面我們在上面第二部分[ValueAnimator和屬性動畫的監聽]中講過的AnimatorUpdateListener類!實現了這個接口就意味着可以在XBallView中直接調用addUpdateListener(this)方法對屬性動畫進行監聽,只需要傳入this即可!

那我們再繼續往下看看有沒有我們要找的定義屬性動畫的邏輯呢?果然有!XBallView類中一共定義了7個動畫和兩個AnimatorSet動畫集合,我把這段代碼摘錄出來:

        /**
         * 下面開始定義小球的下落,着地壓扁,反彈等屬性動畫
         */
        // 定義小球下落動畫
        ValueAnimator fallAni = ObjectAnimator.ofFloat(
                newBall,
                "y",
                startY,
                endY);
        // 設置動畫持續時間
        fallAni.setDuration(duration);
        // 設置加速插值器
        fallAni.setInterpolator(new AccelerateInterpolator());
        // 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
        fallAni.addUpdateListener(this);

        // 定義小球壓扁動畫,控制小球x坐標左移半個球寬度
        ValueAnimator squashshAni1 = ObjectAnimator.ofFloat(
                newBall,
                "x",
                newBall.getX(),
                newBall.getX() - BALL_SIZE / 2);
        squashshAni1.setDuration(duration / 4);
        squashshAni1.setRepeatCount(1);
        squashshAni1.setRepeatMode(ValueAnimator.REVERSE);
        squashshAni1.setInterpolator(new DecelerateInterpolator());
        // 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
        squashshAni1.addUpdateListener(this);

        // 定義小球壓扁動畫,控制小球寬度加倍
        ValueAnimator squashshAni2 = ObjectAnimator.ofFloat(
                newBall,
                "width",
                newBall.getWidth(),
                newBall.getWidth() + BALL_SIZE);
        squashshAni2.setDuration(duration / 4);
        squashshAni2.setRepeatCount(1);
        squashshAni2.setRepeatMode(ValueAnimator.REVERSE);
        squashshAni2.setInterpolator(new DecelerateInterpolator());
        // 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
        squashshAni2.addUpdateListener(this);

        // 定義小球拉伸動畫, 控制小球的y坐標下移半個球高度
        ValueAnimator stretchAni1 = ObjectAnimator.ofFloat(
                newBall,
                "y",
                endY,
                endY + BALL_SIZE / 2);
        stretchAni1.setDuration(duration / 4);
        stretchAni1.setRepeatCount(1);
        stretchAni1.setRepeatMode(ValueAnimator.REVERSE);
        stretchAni1.setInterpolator(new DecelerateInterpolator());
        // 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
        stretchAni1.addUpdateListener(this);

        // 定義小球拉伸動畫, 控制小球的高度減半
        ValueAnimator stretchAni2 = ObjectAnimator.ofFloat(
                newBall,
                "height",
                newBall.getHeight(),
                newBall.getHeight() - BALL_SIZE / 2);
        stretchAni2.setDuration(duration / 4);
        stretchAni2.setRepeatCount(1);
        stretchAni2.setRepeatMode(ValueAnimator.REVERSE);
        stretchAni2.setInterpolator(new DecelerateInterpolator());
        // 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
        stretchAni2.addUpdateListener(this);

        // 定義小球彈起動畫
        ValueAnimator bounceAni = ObjectAnimator.ofFloat(
                newBall,
                "y",
                endY,
                startY);
        bounceAni.setDuration(duration);
        bounceAni.setInterpolator(new DecelerateInterpolator());
        // 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
        bounceAni.addUpdateListener(this);

        // 定義AnimatorSet,按順序播放[下落、壓扁&拉伸、彈起]動畫
        AnimatorSet set = new AnimatorSet();
        //在squashshAni1之前播放fallAni
        set.play(fallAni).before(squashshAni1);
        /**
         * 由於小球彈起時壓扁,即寬度加倍,x坐標左移,高度減半,y坐標下移
         * 因此播放squashshAni1的同時還要播放squashshAni2,stretchAni1,stretchAni2
         */
        set.play(squashshAni1).with(squashshAni2);
        set.play(squashshAni1).with(stretchAni1);
        set.play(squashshAni1).with(stretchAni2);
        // 在stretchAni2之后播放bounceAni
        set.play(bounceAni).after(stretchAni2);

        // newBall對象的漸隱動畫,設置alpha屬性值1--->0
        ObjectAnimator fadeAni = ObjectAnimator.ofFloat(
                newBall,
                "alpha",
                1f,
                0f);
        // 設置動畫持續時間
        fadeAni.setDuration(250);
        // 添加addUpdateListener監聽器,當ValueAnimator屬性值改變時會激發事件監聽方法
        fadeAni.addUpdateListener(this);

        // 為fadeAni設置監聽
        fadeAni.addListener(new AnimatorListenerAdapter() {
            // 動畫結束
            @Override
            public void onAnimationEnd(Animator animation) {
                // 動畫結束時將該動畫關聯的ShapeHolder刪除
                balls.remove(((ObjectAnimator) (animation)).getTarget());
            }
        });

        // 再次定義一個AnimatorSet動畫集合,來組合動畫
        AnimatorSet aniSet = new AnimatorSet();
        // 指定在fadeAni之前播放set動畫集合
        aniSet.play(set).before(fadeAni);

        // 開始播放動畫
        aniSet.start();

邏輯很簡單,動畫fallAni控制小球下落,動畫squashshAni1控制小球壓扁時小球x坐標左移半個球寬度,動畫squashshAni2控制小球壓扁時小球寬度加倍,動畫stretchAni1,控制小球拉伸動畫時小球的y坐標下移半個球高度,動畫stretchAni2控制小球水平拉伸時控制小球的高度減半,動畫bounceAni定義小球彈起動畫,接着用一個AnimatorSet動畫集合把這六個動畫先組裝起來,下落動畫fallAni之后是squashshAni1squashshAni2stretchAni1stretchAni2這四個動畫同時播放,這也是小球落地瞬間的完美詮釋,再之后是小球彈起bounceAni。最后還有一個fadeAni漸隱動畫控制小球彈回起始高度后消失,接着再用一個AnimatorSet動畫集合把前面的那個動畫集合和第七個fadeAni漸隱動畫組裝起來,整個桌面彈球動畫就大功告成了!

需要注意的是,在addBall()方法中,返回的是一個XShapeHolder類型的對象,那么XShapeHolder是什么呢?XShapeHolder包裝了ShapeDrawable對象,並且為x,y,width,height,alpha等屬性提供了settergetter方法,代碼如下:

package com.wondertwo.propertyanime;

import android.graphics.Paint;
import android.graphics.RadialGradient;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.Shape;

/**
 *
 * Created by wondertwo on 2016/3/20.
 */
public class XShapeHolder {

    private float x = 0, y = 0;
    private ShapeDrawable shape;
    private int color;
    private RadialGradient gradient;
    private float alpha = 1f;
    private Paint paint;

    public XShapeHolder(ShapeDrawable shape) {
        this.shape = shape;
    }

    public float getWidth() {
        return shape.getShape().getWidth();
    }

    public void setWidth(float width) {
        Shape s = shape.getShape();
        s.resize(width, s.getHeight());
    }

    public float getHeight() {
        return shape.getShape().getHeight();
    }

    public void setHeight(float height) {
        Shape s = shape.getShape();
        s.resize(s.getWidth(), height);
    }

    public float getX() {
        return x;
    }

    public void setX(float x) {
        this.x = x;
    }

    public float getY() {
        return y;
    }

    public void setY(float y) {
        this.y = y;
    }

    public ShapeDrawable getShape() {
        return shape;
    }

    public void setShape(ShapeDrawable shape) {
        this.shape = shape;
    }

    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
    }

    public RadialGradient getGradient() {
        return gradient;
    }

    public void setGradient(RadialGradient gradient) {
        this.gradient = gradient;
    }

    public float getAlpha() {
        return alpha;
    }

    public void setAlpha(float alpha) {
        this.alpha = alpha;
    }

    public Paint getPaint() {
        return paint;
    }

    public void setPaint(Paint paint) {
        this.paint = paint;
    }
}

博客的最后再放上郭霖大神的兩篇博客,供大家參考!

  1. Android屬性動畫完全解析(上),初識屬性動畫的基本用法 http://blog.csdn.net/guolin_blog/article/details/43536355
  2. Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的高級用法 http://blog.csdn.net/guolin_blog/article/details/43816093
  3. Android屬性動畫完全解析(下),Interpolator和ViewPropertyAnimator的用法 http://blog.csdn.net/guolin_blog/article/details/44171115

在最后附上淺析Android動畫系列的三篇文章:

  1. 淺析Android動畫(一),View動畫高級實例探究 http://www.cnblogs.com/wondertwo/p/5295976.html
  2. 淺析Android動畫(二),屬性動畫與高級實例探究 http://www.cnblogs.com/wondertwo/p/5312482.html
  3. 淺析Android動畫(三),自定義Interpolator與TypeEvaluator http://www.cnblogs.com/wondertwo/p/5327586.html

如果覺得不錯,請繼續關注我哦!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM