Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> Android開發 >> 關於android開發 >> android 動畫詳解(二)

android 動畫詳解(二)

編輯:關於android開發

android 動畫詳解(二)


  下面就開始學習屬性動畫的基本用法,我們來看屬性動畫的繼承關系,如下如所示:

  

  data-cke-saved-src=

 

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

  要設置動畫的目標對象;

  動畫的屬性類型;

  一個或多個屬性值;當只指定一個屬性值,系統默認此值為結束值;當指定兩個屬性值,系統默認分別為起始值和結束值;當指定三個或三個以上時,系統默認線性插值;

  ValueAnimator是整個屬性動畫機制當中最核心的一個類,前面我們已經提到了,屬性動畫的運行機制是通過不斷地對值進行操作來實現的,而初始值和結束值之間的動畫過渡就是由ValueAnimator這個類來負責計算的,ValueAnimator對過渡動畫值的計算依靠一個時間因子fraction,而這個時間因子fraction是系統由setDuration()方法設置的動畫執行時間通過計算得來的,所以ValueAnimator還負責管理動畫的持續時間、播放次數、播放模式、以及對動畫設置監聽器等,確實是一個非常重要的類。

  相關API

  Property Animation故名思議就是通過動畫的方式改變對象的屬性了,我們首先需要了解幾個屬性:

  Duration動畫的持續時間,默認300ms。

  Time interpolation:時間差值,乍一看不知道是什麼,但是我說LinearInterpolator、AccelerateDecelerateInterpolator,大家一定知道是干嘛的了,定義動畫的變化率。

  Repeat count and behavior:重復次數、以及重復模式;可以定義重復多少次;重復時從頭開始,還是反向。

  Animator sets: 動畫集合,你可以定義一組動畫,一起執行或者順序執行。

  Frame refresh delay:幀刷新延遲,對於你的動畫,多久刷新一次幀;默認為10ms,但最終依賴系統的當前狀態;基本不用管。

  相關的類

  ObjectAnimator 動畫的執行類,後面詳細介紹

  ValueAnimator 動畫的執行類,後面詳細介紹

  AnimatorSet 用於控制一組動畫的執行:線性,一起,每個動畫的先後執行等。

  AnimatorInflater 用戶加載屬性動畫的xml文件

  TypeEvaluator 類型估值,主要用於設置動畫操作屬性的值。

  TimeInterpolator 時間插值,上面已經介紹。

  總的來說,屬性動畫就是,動畫的執行類來設置動畫操作的對象的屬性、持續時間,開始和結束的屬性值,時間差值等,然後系統會根據設置的參數動態的變化對象的屬性。

  ValueAnimator

  ValueAnimator是整個屬性動畫機制當中最核心的一個類,屬性動畫的運行機制是通過不斷地對值進行操作來實現的,而初始值和結束值之間的動畫過渡就是由ValueAnimator這個類來負責計算的。它的內部使用一種時間循環的機制來計算值與值之間的動畫過渡,我們只需要將初始值和結束值提供給ValueAnimator,並且告訴它動畫所需運行的時長,那麼ValueAnimator就會自動幫我們完成從初始值平滑地過渡到結束值這樣的效果。除此之外,ValueAnimator還負責管理動畫的播放次數、播放模式、以及對動畫設置監聽器等,確實是一個非常重要的類。

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

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

很簡單吧,調用ValueAnimator的ofFloat()方法就可以構建出一個ValueAnimator的實例,ofFloat()方法當中允許傳入多個float類型的參數,這裡傳入0和1就表示將值從0平滑過渡到1,然後調用ValueAnimator的setDuration()方法來設置動畫運行的時長,最後調用start()方法啟動動畫。
用法就是這麼簡單,現在如果你運行一下上面的代碼,動畫就會執行了。可是這只是一個將值從0過渡到1的動畫,又看不到任何界面效果,我們怎樣才能知道這個動畫是不是已經真正運行了呢?這就需要借助監聽器來實現了,如下所示:

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

ObjectAnimator

相比於ValueAnimator,ObjectAnimator可能才是我們最常接觸到的類,因為ValueAnimator只不過是對值進行了一個平滑的動畫過渡,但我們實際使用到這種功能的場景好像並不多。而ObjectAnimator則就不同了,它是可以直接對任意對象的任意屬性進行動畫操作的;
下面舉幾個例子(csdn上面的例子)

一個動畫能夠讓View既可以縮小、又能夠淡出(3個屬性scaleX,scaleY,alpha),只使用ObjectAnimator咋弄?

public void rotateyAnimRun(final View view)  
{  
    ObjectAnimator anim = ObjectAnimator//  
            .ofFloat(view, "zhy", 1.0F,  0.0F)//  
            .setDuration(500);//  
    anim.start();  
    anim.addUpdateListener(new AnimatorUpdateListener()  
    {  
        @Override  
        public void onAnimationUpdate(ValueAnimator animation)  
        {  
            float cVal = (Float) animation.getAnimatedValue();  
            view.setAlpha(cVal);  
            view.setScaleX(cVal);  
            view.setScaleY(cVal);  
        }  
    });  
} 

其實還有更簡單的方式,實現一個動畫更改多個效果:使用propertyValuesHolder

public void propertyValuesHolder(View view)  
    {  
        PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("alpha", 1f,  
                0f, 1f);  
        PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("scaleX", 1f,  
                0, 1f);  
        PropertyValuesHolder pvhZ = PropertyValuesHolder.ofFloat("scaleY", 1f,  
                0, 1f);  
        ObjectAnimator.ofPropertyValuesHolder(view, pvhX, pvhY,pvhZ).setDuration(1000).start();  
    }  

這裡寫圖片描述


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

這裡寫圖片描述vcS/x7DOqta5o6xPYmplY3RBbmltYXRvcrXE08O3qLu5y+PKx8/gtbG88rWlsMmjrLWrysfO0s/g0MW/z7aou+HT0LK7ydnF89PRz9bU2tDEwO+2vNPQzazR+dK7uPbSyc7Ko6y+zcrHb2ZGbG9hdCgpt723qLXEtdq2/rj2ss7K/bW9tde/ydLUtKvExNCp1rXE2KO/xL/HsM7Sw8fKudPDuf3By2FscGhhoaJyb3RhdGlvbqGidHJhbnNsYXRpb25Yus1zY2FsZVnV4ry4uPbWtaOst9ax8L/J0tTN6rPJta3I67Wts/ahotD916qhosuuxr3SxravoaK0udaxy/W3xdXivLjW1ravu62jrMTHw7S7udPQxMTQqda1yse/ydLUyrnTw7XExNijv8bkyrXV4rj2zsrM4rXEtPCwuLfHs6PQ/rr1o6y+zcrHztLDx7/J0tS0q8jryM7S4rXE1rW1vW9mRmxvYXQoKbe9t6i1xLXatv649rLOyv21sdbQoaPIztLitcTWtaO/z+DQxdXiutyz9rr1tPO80rXE0uLBz7DJo6y1q8rCyrW+zcrHyOe0y6Gj0vLOqk9iamVjdEFuaW1hdG9y1NrJ6LzGtcTKsbryvs3Du9PQ1eu21NPaVmlld8C0vfjQ0MnovMajrLb4ysfV67bU09rIztLittTP87XEo6zL/Mv5uLrU8LXEuaTX977Nyseyu7bPtdjP8sSzuPa21M/z1tC1xMSzuPbK9NDUvfjQ0Liz1rWjrMi7uvO21M/zuPm+3cr00NTWtbXEuMSx5NTZwLS+9raoyOe6ztW5z9az9sC0oaM8YnIgLz4NCsTHw7SxyMjny7XO0sPHtffTw8/Cw+bV4tH50ru2zrT6wuujujwvcD4NCjxwcmUgY2xhc3M9"brush:java;"> ObjectAnimator.ofFloat(textview, "alpha", 1f, 0f);

其實這段代碼的意思就是ObjectAnimator會幫我們不斷地改變textview對象中alpha屬性的值,從1f變化到0f。然後textview對象需要根據alpha屬性值的改變來不斷刷新界面的顯示,從而讓用戶可以看出淡入淡出的動畫效果。
那麼textview對象中是不是有alpha屬性這個值呢?沒有,不僅textview沒有這個屬性,連它所有的父類也是沒有這個屬性的!這就奇怪了,textview當中並沒有alpha這個屬性,ObjectAnimator是如何進行操作的呢?其實ObjectAnimator內部的工作機制並不是直接對我們傳入的屬性名進行操作的,而是會去尋找這個屬性名對應的get和set方法,因此alpha屬性所對應的get和set方法應該就是:

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

那麼textview對象中是否有這兩個方法呢?確實有,並且這兩個方法是由View對象提供的,也就是說不僅TextView可以使用這個屬性來進行淡入淡出動畫操作,任何繼承自View的對象都可以的。
既然alpha是這個樣子,相信大家一定已經明白了,前面我們所用的所有屬性都是這個工作原理,那麼View當中一定也存在著setRotation()、getRotation()、setTranslationX()、getTranslationX()、setScaleY()、getScaleY()這些方法,不信的話你可以到View當中去找一下。

桌面彈球和Win10開機小圓點旋轉動畫的實例探究
這裡寫圖片描述

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




    

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


    

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


接著把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 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之後是squashshAni1、squashshAni2、stretchAni1、stretchAni2這四個動畫同時播放,這也是小球落地瞬間的完美诠釋,再之後是小球彈起bounceAni。最後還有一個fadeAni漸隱動畫控制小球彈回起始高度後消失,接著再用一個AnimatorSet動畫集合把前面的那個動畫集合和第七個fadeAni漸隱動畫組裝起來,整個桌面彈球動畫就大功告成了!

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

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. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved