Android 屬性動畫ValueAnimator

成功的事業(yè)離不開堅如磐石的意志。成功路上的坎坷默垄,意志脆弱者是根本無法超越的渐裸∥紫妫——富蘭克林·羅斯福

老式電影膠片就是逐幀動畫的工作原理,很簡單昏鹃,將一個完整的電影拆分成一張張單獨的圖片,然后再將它們連貫起來進(jìn)行播放诀诊。所以我們總是看到一個輪子在不停的轉(zhuǎn)圈洞渤,就是在拖動一張張圖片。動畫分為兩類属瓣,視圖動畫 和 屬性動畫载迄。視圖動圖包括補間動畫和幀動畫,屬性動畫包括ValueAnimator 和 ObjectAnimator抡蛙。為什么在Android 3.0 引入屬性動畫护昧?

補間動畫有一個致命的缺陷,比如現(xiàn)在屏幕的左上角有一個按鈕粗截,我們通過補間動畫將它移動到了屏幕的右下角惋耙,你可以去嘗試點擊一下這個按鈕,點擊事件是絕對不會觸發(fā)熊昌,因為這個按鈕還是停留在屏幕的左上角绽榛,只不過補間動畫將這個按鈕繪制到了屏幕的右下角而已。
下面簡單看一下這個動畫的代碼實現(xiàn)

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

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:text="Start Anim" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#03A9F4"
        android:padding="10dp"
        android:text="Hello World"
        android:textColor="@android:color/white" />

</LinearLayout>

接下來給Buttom 和 TextView 添加單擊事件婿屹,當(dāng)點擊TextView時灭美,彈出Toast提示,當(dāng)點擊Buttom昂利,TextView 移動

public class MainActivity extends AppCompatActivity {

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

        Button btn = findViewById(R.id.btn);
        final TextView tv = findViewById(R.id.tv);

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                TranslateAnimation translateAnimation = new TranslateAnimation(Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 400,
                        Animation.ABSOLUTE, 0, Animation.ABSOLUTE, 200);
                translateAnimation.setFillAfter(true);
                translateAnimation.setDuration(2000);
                tv.startAnimation(translateAnimation);
            }
        });

        tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "go", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

補間動畫還有一個缺陷届腐,它只能夠?qū)崿F(xiàn)移動铁坎、縮放、旋轉(zhuǎn)和淡入淡出這四種動畫操作犁苏,那如果我們希望可以對View的背景色進(jìn)行動態(tài)地改變呢硬萍?它實現(xiàn)不了,屬性動畫因為對控件的屬性進(jìn)行修改傀顾,它可以輕易實現(xiàn)背景顏色的動態(tài)改變襟铭。

ValueAnimator

        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 400);
        valueAnimator.setDuration(1000);
        valueAnimator.start();

ValueAnimator的ofFloat()方法就可以構(gòu)建出一個0到400的動畫,從這段代碼中可以出看短曾,ValueAnimator沒有跟任何控件想關(guān)聯(lián)寒砖,動畫時長是1秒,然后開始動畫嫉拐,這也正好說明ValueAnimator只對值進(jìn)行動畫運算哩都,而不是針對控件的。我們需要監(jiān)聽動畫過程來自己對控件進(jìn)行操作婉徘。

        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 400);
        valueAnimator.setDuration(1000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                float curValue = (float) animation.getAnimatedValue();
                Log.d("tag", "----curValue:" + curValue);
            }
        });

        valueAnimator.start();

我們通過addUpdateListener()方法來添加一個動畫的監(jiān)聽器漠嵌,在監(jiān)聽傳回的結(jié)果中,animator表達(dá)當(dāng)前ValueAnimation實例盖呼,通過animation.getAnimatedValue()函數(shù)得到當(dāng)前值儒鹿,運行結(jié)果:

06-10 20:20:34.855 23008-23008/com.as.propertyanimator D/tag: ----curValue:0.0
06-10 20:20:35.063 23008-23008/com.as.propertyanimator D/tag: ----curValue:0.0
06-10 20:20:35.198 23008-23008/com.as.propertyanimator D/tag: ----curValue:1.822114
06-10 20:20:35.254 23008-23008/com.as.propertyanimator D/tag: ----curValue:7.4239135
06-10 20:20:35.267 23008-23008/com.as.propertyanimator D/tag: ----curValue:11.401903
06-10 20:20:35.280 23008-23008/com.as.propertyanimator D/tag: ----curValue:15.465462
06-10 20:20:35.299 23008-23008/com.as.propertyanimator D/tag: ----curValue:20.118963
06-10 20:20:35.317 23008-23008/com.as.propertyanimator D/tag: ----curValue:25.347496
06-10 20:20:35.335 23008-23008/com.as.propertyanimator D/tag: ----curValue:31.134438

06-10 20:20:36.110 23008-23008/com.as.propertyanimator D/tag: ----curValue:398.00473
06-10 20:20:36.128 23008-23008/com.as.propertyanimator D/tag: ----curValue:399.3332
06-10 20:20:36.146 23008-23008/com.as.propertyanimator D/tag: ----curValue:399.93683
06-10 20:20:36.165 23008-23008/com.as.propertyanimator D/tag: ----curValue:400.0

對指定值區(qū)間進(jìn)行動畫運算,我們對運算過程進(jìn)行監(jiān)聽來自己操作控件几晤,總而言之

  • ValueAnimator 只負(fù)責(zé)對指定值區(qū)間進(jìn)行動畫運算
  • 我們需要對運算過程進(jìn)行監(jiān)聽约炎,然后自己對控件執(zhí)行動畫操作

ValueAnimator 使用實例

下面使用ValueAnimator 來實現(xiàn)補間動畫的單機(jī)區(qū)域問題中的例子,看看是否仍存在單機(jī)區(qū)域問題
布局代碼與上一個例子相同

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

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:text="Start Anim" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#03A9F4"
        android:padding="10dp"
        android:id="@+id/tv"
        android:text="Hello World"
        android:textColor="@android:color/white" />

</LinearLayout>

分別給Buttom 和 TextView 添加單擊事件蟹瘾,當(dāng)點擊TextView時圾浅,彈出Toast提示,當(dāng)點擊Buttom憾朴,TextView 開始移動狸捕。

public class MainActivity extends AppCompatActivity {

    private TextView tv;

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

        Button btn = findViewById(R.id.btn);
        tv = findViewById(R.id.tv);

        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                translate();
            }
        });

        tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "go", Toast.LENGTH_SHORT).show();
            }
        });

    }

    //當(dāng)點擊Button 的時候調(diào)用translate 函數(shù)執(zhí)行動畫操作,在單擊TextView 彈出Toast提示
    private void translate() {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 400);

        valueAnimator.setDuration(2000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int curValue = (int) animation.getAnimatedValue();
                //通過layout 函數(shù)改變TextView 的位置众雷,而layout函數(shù)在改變控件位置時是永久的灸拍,即通過更改left ,top报腔,right株搔,bottom這四個點的坐標(biāo)來更改坐標(biāo)位置,而不僅僅是視覺上畫在哪個位置上
                tv.layout(curValue, curValue, tv.getWidth() + curValue, tv.getHeight() + curValue);
            }
        });
        valueAnimator.start();
    }
...

TextView 的運動軌跡從屏幕左上角(0纯蛾,0)點運行到(400纤房,400)點

在上面的列子中,我們使用了ofFloat 和 ofInt 函數(shù)翻诉,下面看下它的具體聲明

public static ValueAnimator ofInt(int... values)
public static ValueAnimator ofFloat(float... values)

它們的參數(shù)類型都是可變參數(shù)炮姨,所以我們可以傳入任何數(shù)量的值捌刮,傳進(jìn)入的列表就表示動畫的變化方位,比如ofInt(100,400,200)就表示從數(shù)字100 變化到400在變化到數(shù)字200舒岸,所以我們傳進(jìn)去的數(shù)字越多绅作,動畫變化就越復(fù)雜。從參數(shù)類型中可以看出ofInt() 和 ofFloat() 唯一的區(qū)別就是傳入的數(shù)字類型不同蛾派。

在上面例子的基礎(chǔ)上俄认,我們使用ofFloat() 函數(shù)來舉一個列子

private void translateFloat() {
        ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f, 400f,100f,300f);

        valueAnimator.setDuration(4000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Float curValueFloat = (Float) animation.getAnimatedValue();
                int curValue = curValueFloat.intValue();
                tv.layout(curValue, curValue, tv.getWidth() + curValue, tv.getHeight() + curValue);
            }
        });
        valueAnimator.start();
    }

在這個列子中,我們使用 ValueAnimator.ofFloat(0f, 400f,100f,300f) 構(gòu)造了一個比較復(fù)雜的動畫洪乍,值從0變到400眯杏,再回到100,最后變成300壳澳。
所以岂贩,在單機(jī)按鈕之后,TextView 會從(0巷波,0)點移動到(400萎津,400)點,再運動到(100抹镊,100)點锉屈,最后運動到(300,300)點垮耳。



大家可能會疑問部念,為什么要轉(zhuǎn)換成Float 類型,我們先來看看getAnimatorValue()函數(shù)聲明

public Object getAnimatedValue()

它返回的是Object類型氨菇,那我們怎么知道要轉(zhuǎn)換的類型呢?哎妓湘,我們在設(shè)置動畫初始值使用的是ofFloat()函數(shù)查蓉,所以每個值的類型必定是Float類型,我們獲取到的類型也必然是Float類型榜贴。在得到當(dāng)前運動點后豌研,通過layout() 函數(shù)將TextView 移動到指定位置即可。

常用函數(shù)

// 設(shè)置動畫時長
public ValueAnimator setDuration(long duration)

// 獲取當(dāng)前運動點的值
public Object getAnimatedValue()

// 開始動畫
void start()

// 設(shè)置循環(huán)次數(shù)唬党,INFINITE 表示無限循環(huán)鹃共,0表示不循環(huán)
public void setRepeatCount(int value)

/**設(shè)置循環(huán)模式  
*RESTART 表示正序重新開始
* REVERSE 表示倒序重新開始
*/
public void setRepeatMode(@RepeatMode int value)

//取消動畫
void cancel()

下面舉個例子
首先是布局代碼,我們將兩個按鈕放到一列驶拱,將TextView 放到中間

<?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"
    android:orientation="vertical">

    <Button
        android:id="@+id/btnStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:text="Start Anim" />

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:background="#03A9F4"
        android:padding="10dp"
        android:text="Hello World"
        android:textColor="@android:color/white" />

    <Button
        android:id="@+id/btnCancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/btnStart"
        android:layout_marginTop="30dp"
        android:padding="10dp"
        android:text="Cancel Anim" />

</RelativeLayout>

下面看看兩個按鈕的操作

public class MainActivity extends AppCompatActivity {

    private TextView tv;
    private ValueAnimator valueAnimatorRepeat;

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

        Button btnStart = findViewById(R.id.btnStart);
        Button btnCancel = findViewById(R.id.btnCancel);
        tv = findViewById(R.id.tv);

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

        btnCancel.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                valueAnimatorRepeat.cancel();
            }
        });

        tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "go", Toast.LENGTH_SHORT).show();
            }
        });

    }

    private void doRepeatAnimator() {
        valueAnimatorRepeat = ValueAnimator.ofInt(0, 400);
        valueAnimatorRepeat.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int curValue = (int) animation.getAnimatedValue();
                tv.layout(tv.getLeft(), curValue, tv.getRight(), tv.getHeight() + curValue);
            }
        });
        valueAnimatorRepeat.setRepeatMode(ValueAnimator.REVERSE); //倒敘重新開始
        valueAnimatorRepeat.setRepeatCount(ValueAnimator.INFINITE); // 無限循環(huán)
        valueAnimatorRepeat.setDuration(2500);
        valueAnimatorRepeat.start();
    }
}

當(dāng)點擊btnStart時霜浴,調(diào)用doRepeatAnimator()函數(shù),返回ValueAnimator對象蓝纲,并將其賦給valueAnimatorRepeat阴孟,下面來看看這段代碼

    private void doRepeatAnimator() {
        valueAnimatorRepeat = ValueAnimator.ofInt(0, 400);
        valueAnimatorRepeat.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int curValue = (int) animation.getAnimatedValue();
                tv.layout(tv.getLeft(), curValue, tv.getRight(), tv.getHeight() + curValue);
            }
        });
        valueAnimatorRepeat.setRepeatMode(ValueAnimator.REVERSE); //倒敘重新開始
        valueAnimatorRepeat.setRepeatCount(ValueAnimator.INFINITE); // 無限循環(huán)
        valueAnimatorRepeat.setDuration(2500);
        valueAnimatorRepeat.start();
    }

在這里晌纫,我們構(gòu)造一個ValueAnimator,動畫方位時0到400永丝,設(shè)置重復(fù)次數(shù)無限循環(huán)锹漱,重復(fù)模式為倒序。當(dāng)活動結(jié)束的時候慕嚷,必須調(diào)用cancel()函數(shù)取消動畫哥牍,否則動畫將會無限循環(huán),導(dǎo)致View無法釋放喝检,進(jìn)一步導(dǎo)致Activity無法釋放嗅辣,最終引起內(nèi)存泄漏。


自定義插值器

我們通過ofInt(0蛇耀,400)定義了動畫的區(qū)間值 0 到400辩诞,然后通過添加AnimatorUpdateListener來監(jiān)聽動畫的實時變化。那么問題來了纺涤,0到400之間的值是怎么變化的呢译暂?像我們跑步,還有得快撩炊,有的慢外永,這個值是勻速變化的嗎?如果是拧咳,想讓他一直加入該怎么辦呢伯顶?這就是插值器的作用。

插值器就是控制動畫區(qū)間值如何被計算出來骆膝,比如LinearInterpolator插值器表示勻速返回區(qū)間內(nèi)的值祭衩,等等其它插值器。

在自定義插值器之前阅签,先看看系統(tǒng)自帶的插值器是如何實現(xiàn)的掐暮,比如LinearInterpolator

public class LinearInterpolator  implements Interpolator {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input;
    }
}

LinearInterpolator 實現(xiàn)了Interpolator 接口,而Interpolator 接口直接繼承TimeInterpolator政钟,并且沒有添加任何其它的方法路克。

package android.animation;

public interface TimeInterpolator {    
    float getInterpolation(float input);
}

參數(shù)input :它的取值方位0到1,表示當(dāng)前動畫的進(jìn)度养交,0表示動畫開始精算,1表示動畫結(jié)束,0.5表示動畫中間的位置碎连。表示當(dāng)前的動畫參數(shù)是勻速增加的灰羽,動畫進(jìn)度就是動畫在時間上的進(jìn)度,隨著時間推移,動畫自然從0到1逐漸增加谦趣。input參數(shù)相當(dāng)于時間的概念疲吸,我們通過setDuration()指定了動畫時長。
返回值:表示當(dāng)前實際想要顯示的進(jìn)度前鹅,取值可以超過1摘悴,表示超過目標(biāo)值,小于0 表示小于開始值舰绘。

        ValueAnimator valueAnimator = ValueAnimator.ofInt(100, 500);
        valueAnimator.setDuration(1000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int curValue = (int) animation.getAnimatedValue();
                tv.layout(curValue, curValue, tv.getWidth() + curValue, tv.getHeight() + curValue);
            }
        });
        valueAnimator.start();

animation.getAnimatedValue() 得到當(dāng)前值是怎么來的蹂喻?看下面的計算公式,目前可以這么理解

當(dāng)前的值 = 100 + (400 - 100 ) * 顯示進(jìn)度

其中捂寿,100和400就是我們設(shè)置ofFloat (100,400)中的值

input 參數(shù)就表示當(dāng)前動畫進(jìn)度口四,而返回值則表示當(dāng)前動畫的數(shù)值進(jìn)度。

我們自定義插值器

package com.as.propertyanimator;

import android.animation.TimeInterpolator;

public class MyInterpolator implements TimeInterpolator {
    @Override
    public float getInterpolation(float input) {
        return 1 - input;
    }
}

在這個自定義插值器中我們將進(jìn)度反轉(zhuǎn)過來秦陋,當(dāng)傳入0的時候蔓彩,讓它的數(shù)值進(jìn)度在完成的位置,當(dāng)完成的時候驳概,讓它的數(shù)值進(jìn)度在開始位置赤嚼。

private void myInterpolator() {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 300);

        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int curValue = (int) animation.getAnimatedValue();
                tv.layout(tv.getLeft(), curValue, tv.getRight(), tv.getHeight() + curValue);
            }
        });
        valueAnimator.setDuration(1000);
        valueAnimator.setInterpolator(new MyInterpolator());
        valueAnimator.start();
    }

Evaluator(求值器)

上圖講述了從定義動畫的數(shù)值區(qū)間到在AnimatorUpdateListener中得到當(dāng)前動畫所對應(yīng)數(shù)值的整個過程。
這4個步驟的具體含義如下
(1)ofInt(0,400) 表示指定動畫的數(shù)值區(qū)間顺又,從0運動到400
(2)插值器:在動畫開始后更卒,返回當(dāng)前動畫進(jìn)度所對應(yīng)的數(shù)值進(jìn)度,但這個數(shù)值進(jìn)度是以小數(shù)表示的稚照,如0.2蹂空。
(3)Evaluator:我們通過監(jiān)聽器拿到的是當(dāng)前動畫所對應(yīng)的具體數(shù)值,而不是用小數(shù)表示的數(shù)值果录。那么必須有一個地方根據(jù)檔期那數(shù)值進(jìn)度轉(zhuǎn)化為對應(yīng)數(shù)值上枕,這個地方就是Evaluator。Evaluator用于將插值器返回的數(shù)值進(jìn)度轉(zhuǎn)化為對應(yīng)數(shù)值弱恒。
(4)監(jiān)聽器返回:我們通過AnimatorUpdateListener 監(jiān)聽器中使用animation.getAnimatedValue()函數(shù)拿到Evaluator中返回的數(shù)值姿骏。

講了這么多,Evaluator其實就是一個轉(zhuǎn)換器斤彼,能把小數(shù)進(jìn)度轉(zhuǎn)換成對應(yīng)的數(shù)值。

在設(shè)置Evaluator時蘸泻,是通過animator.setEvaluator()函數(shù)來實現(xiàn)的琉苇,比如:

       ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 300);

        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int curValue = (int) animation.getAnimatedValue();
                tv.layout(tv.getLeft(), curValue, tv.getRight(), tv.getHeight() + curValue);
            }
        });
        valueAnimator.setDuration(1000);
        valueAnimator.setEvaluator(new IntEvaluator());
        valueAnimator.setInterpolator(new BounceInterpolator());
        valueAnimator.start();

我們設(shè)置了IntEvaluator,用來計算數(shù)值進(jìn)度所對應(yīng)的數(shù)值悦施。但在此之前并扇,我們在使用ofInt()函數(shù)時,從來沒有定義過使用IntEvaluator來轉(zhuǎn)換值抡诞,能正常運行是因為ofInt()和ofFloat()都是系統(tǒng)直接提供的函數(shù)穷蛹,所以會有默認(rèn)的插值器和Evaluator可供使用土陪。下面看下IntEvaluator內(nèi)部是怎么實現(xiàn)的。

package android.animation;

/**
 * This evaluator can be used to perform type interpolation between <code>int</code> values.
 */
public class IntEvaluator implements TypeEvaluator<Integer> {
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}
  • fraction 參數(shù)就是插值器中返回的值肴熏,表示當(dāng)前動畫的百分比進(jìn)度鬼雀。
  • startValue 和 endValue 分別對應(yīng)ofInt(int start,int end)函數(shù)中start 和 end值
  • 返回值就是當(dāng)前數(shù)值進(jìn)度所對應(yīng)的具體數(shù)值
    假設(shè)當(dāng)我們定義的動畫ofInt(200,500)進(jìn)行到數(shù)值進(jìn)度10%的時候,我們來算下具體值
 return (int)(startInt + fraction * (endValue - startInt));

當(dāng)前值 = 200 + 0.1 * (500 - 200)

簡單實現(xiàn)Evaluator

下面仿照一個IntEvaluator 的實現(xiàn)方法蛙吏,自定義一個MyEvalutor

package com.as.propertyanimator;

import android.animation.TypeEvaluator;

public class MyEvaluator implements TypeEvaluator<Integer> {

    @Override
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        return (int) (200 + startValue + fraction * (endValue - startValue));
    }
}

我們在IntEvaluator的基礎(chǔ)上修改了一下源哩,讓它返回時增加200。當(dāng)我們定義一個ofInt(0,500)時鸦做,它的實際返回值區(qū)間時(200励烦,700)。



很明顯泼诱,TextView 的動畫位置都向下移動了200px坛掠。

自定義插值器實現(xiàn)倒序輸出

package com.as.propertyanimator;

import android.animation.TypeEvaluator;

/**
 * 倒敘輸出
 */
public class ReverseEvaluator implements TypeEvaluator<Integer> {

    @Override
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        return (int) (endValue - fraction * (endValue - startValue));
    }
}

fraction * (endValue - startValue) 表示動畫的實際運動距離,我們用endValue 減去實際運動距離表示距離終點越來越遠(yuǎn)治筒,也就實現(xiàn)了從終點出發(fā)屉栓,到起點的效果

private void myReverseEvaluator() {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 300);

        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int curValue = (int) animation.getAnimatedValue();
                tv.layout(tv.getLeft(), curValue, tv.getRight(), tv.getHeight() + curValue);
            }
        });
        valueAnimator.setDuration(1000);
        valueAnimator.setEvaluator(new ReverseEvaluator());
        valueAnimator.start();
    }

我們來實現(xiàn)顏色過渡轉(zhuǎn)換

private void colorEvaluator() {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0xffffff00, 0xff0000ff);

        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int curValue = (int) animation.getAnimatedValue();
                tv.setBackgroundColor(curValue);
            }
        });
        valueAnimator.setDuration(5000);
        valueAnimator.setEvaluator(new ArgbEvaluator());
        valueAnimator.start();
    }

我們將動畫的定義為(0xffffff00, 0xff0000ff),即從黃色變?yōu)樗{(lán)色矢炼。在監(jiān)聽事件中系瓢,我們根據(jù)當(dāng)前傳回的顏色值,將其設(shè)置為TextView 的背景色句灌。


這里需要注意的是夷陋,必須使用ofInt()函數(shù)來定義顏色的取值范圍,并且顏色必須包含A胰锌,R骗绕,G,B 4個值资昧,我們來簡單看一下ArgbEvaluator的源碼

/*
 * Copyright (C) 2010 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.animation;

import android.annotation.UnsupportedAppUsage;

/**
 * This evaluator can be used to perform type interpolation between integer
 * values that represent ARGB colors.
 */
public class ArgbEvaluator implements TypeEvaluator {
   
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        int startInt = (Integer) startValue;
        float startA = ((startInt >> 24) & 0xff) / 255.0f;
        float startR = ((startInt >> 16) & 0xff) / 255.0f;
        float startG = ((startInt >>  8) & 0xff) / 255.0f;
        float startB = ( startInt        & 0xff) / 255.0f;

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

        // compute the interpolated color in linear space
        float a = startA + fraction * (endA - startA); //當(dāng)前進(jìn)度的透明度
        float r = startR + fraction * (endR - startR); //當(dāng)前進(jìn)度下的紅色值
        float g = startG + fraction * (endG - startG); //當(dāng)前進(jìn)度下的綠色值
        float b = startB + fraction * (endB - startB); //當(dāng)前進(jìn)度下的藍(lán)色值

        return Math.round(a) << 24 | Math.round(r) << 16 | Math.round(g) << 8 | Math.round(b);
    }
}

這段代碼分為三部分酬土,第一部分根據(jù)startValue 起初 A,R格带,G撤缴,B中各個色彩的初始值,第二部分根據(jù)endValue 求出 A叽唱,R屈呕,G,B 各個色彩的結(jié)束值棺亭,第三部分根據(jù)當(dāng)前動畫的百分比進(jìn)度求出對應(yīng)的數(shù)值虎眨,最后通過或運算把結(jié)果拼接到一個整型 4個字節(jié)中。

這段代碼根據(jù)位移和與運算求出顏色中A,R嗽桩,G岳守,B 各個部分對應(yīng)的值,


如果大家對位移和與運算及如何得到指定為不太了解的畫碌冶,可以看我這篇博客算法之美湿痢,位運算。

ValueAnimator 進(jìn)階 ofObject

ofInt()函數(shù)只能傳入Integer類型种樱,ofFloat只能傳入Float 類型蒙袍,如果我們需要操作其它類型的變量該怎么辦呢?其實嫩挤,ValueAnimator還有一個函數(shù)ofObject()害幅,可以傳入任何類型的變量,該函數(shù)的定義如下:

 public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)

它有兩個參數(shù)岂昭,第一個參數(shù)是自定義Evaluator以现;第二個參數(shù)是可變參數(shù),屬于Object類型约啊。既然Object對象是我們自定義的邑遏,那么進(jìn)度值的轉(zhuǎn)換過程也由我們來做,否則系統(tǒng)不可能知道轉(zhuǎn)換出來的具體值是什么恰矩。

下面我們嘗試使用ofObject函數(shù)實現(xiàn)下面的效果记盒,將TextView 中的字母從A變化到Z。

package com.as.propertyanimator;

import android.animation.TypeEvaluator;

/**
 * 動畫求值器從字母A到字母Z
 */
public class CharEvaluator implements TypeEvaluator<Character> {

    @Override
    public Character evaluate(float fraction, Character startValue, Character endValue) {
        int startInt = startValue; //A = 65
        int endInt = endValue;     //Z = 90
        return (char) (startInt + fraction * (endInt - startInt)); //當(dāng)前字符
    }

}

private void objectEvaluator() {
        //我們要實現(xiàn)的動畫效果是從字母A到字母Z
        ValueAnimator valueAnimator = ValueAnimator.ofObject(new CharEvaluator(), 'A', 'Z');

        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //獲取當(dāng)前動畫的值==>獲取到當(dāng)前字母
                char curValue = (char) animation.getAnimatedValue();
                tv.setText(String.valueOf(curValue));
            }
        });
        //動畫勻速==>線性變化
        valueAnimator.setInterpolator(new LinearInterpolator());
        valueAnimator.setDuration(5000);
        valueAnimator.start();
    }

我們自定義了一個CharEvaluator外傅;在初始動畫時纪吮,傳入的是char對象,一個是字母A萎胰,另一個是字母Z碾盟。我們先來了解下ASCIi 碼表中數(shù)字與字符的轉(zhuǎn)換方法,每個字符都有一個對應(yīng)的數(shù)字技竟,字母A到Z對應(yīng)的數(shù)字區(qū)間65到90冰肴,在程序中,我們可以將數(shù)字轉(zhuǎn)換為字符榔组,也可以將字符轉(zhuǎn)化為數(shù)字熙尉。
數(shù)字轉(zhuǎn)字符

char c = (char) 65; //得到的c 就是大寫字母A
char temp = 'A';
int num = (int) temp; // 65

我們來實現(xiàn)一個小球落地的動畫

先用shape 標(biāo)簽實現(xiàn)一個圓形drawable (circle.xml)

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

然后實現(xiàn)布局文件

<?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"
    android:orientation="vertical">

    <Button
        android:id="@+id/btnStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:text="Start Anim" />


    <Button
        android:id="@+id/btnCancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/btnStart"
        android:layout_marginTop="30dp"
        android:padding="10dp"
        android:text="Cancel Anim" />

    <TextView
        android:id="@+id/tv"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_below="@+id/btnCancel"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:background="@drawable/circle"
        android:padding="10dp"
        android:textColor="@android:color/white" />

</RelativeLayout>

將上面的圓形shape 作為ImageView 的源文件顯示出來,接著實現(xiàn)動畫

package com.as.propertyanimator;

import android.animation.TypeEvaluator;
import android.graphics.Point;

/**
 * 蹦蹦求 從空中落到地面上
 */
public class FallingBallEvaluator implements TypeEvaluator<Point> {

    //蹦蹦求返回值
    private Point mPoint = new Point();

    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
        int x = (int) (startValue.x + (fraction * (endValue.x - startValue.x)));
        mPoint.x = x;
        int y = (int) (startValue.y + (fraction * (endValue.y - startValue.y)));
        mPoint.y = y;
        return mPoint;
    }

}


private void ballEvaluator() {
        //定義了球的位置
        ValueAnimator valueAnimator = ValueAnimator.ofObject(new FallingBallEvaluator(), new Point(0, 0), new Point(500, 500));
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //獲取當(dāng)前動畫的值==>獲取到當(dāng)前x 和 y 位置
                Point curValue = (Point) animation.getAnimatedValue();
                //將球移動到指定位置
                tv.layout(curValue.x, curValue.y, curValue.x + tv.getWidth(), curValue.y + tv.getHeight());
            }
        });
        // 彈跳插值器搓扯,模擬了控件自由落地后回彈的效果
        valueAnimator.setInterpolator(new BounceInterpolator());
        valueAnimator.setDuration(3000);
        valueAnimator.start();
    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末骡尽,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子擅编,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爱态,死亡現(xiàn)場離奇詭異谭贪,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)锦担,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門俭识,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人洞渔,你說我怎么就攤上這事套媚。” “怎么了磁椒?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵堤瘤,是天一觀的道長。 經(jīng)常有香客問我浆熔,道長本辐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任医增,我火速辦了婚禮慎皱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘叶骨。我一直安慰自己茫多,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布忽刽。 她就那樣靜靜地躺著天揖,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缔恳。 梳的紋絲不亂的頭發(fā)上宝剖,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天,我揣著相機(jī)與錄音歉甚,去河邊找鬼万细。 笑死,一個胖子當(dāng)著我的面吹牛纸泄,可吹牛的內(nèi)容都是我干的赖钞。 我是一名探鬼主播,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼聘裁,長吁一口氣:“原來是場噩夢啊……” “哼雪营!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起衡便,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤献起,失蹤者是張志新(化名)和其女友劉穎洋访,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體谴餐,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡姻政,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了岂嗓。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片汁展。...
    茶點故事閱讀 38,650評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖厌殉,靈堂內(nèi)的尸體忽然破棺而出食绿,到底是詐尸還是另有隱情,我是刑警寧澤公罕,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布器紧,位于F島的核電站,受9級特大地震影響熏兄,放射性物質(zhì)發(fā)生泄漏品洛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一摩桶、第九天 我趴在偏房一處隱蔽的房頂上張望桥状。 院中可真熱鬧,春花似錦硝清、人聲如沸辅斟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽士飒。三九已至,卻和暖如春蔗崎,著一層夾襖步出監(jiān)牢的瞬間酵幕,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工缓苛, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留芳撒,地道東北人。 一個月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓未桥,卻偏偏與公主長得像笔刹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子冬耿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,527評論 2 349