Android 屬性動畫(Property Animation) 使用詳解

謹(jǐn)以文章記錄學(xué)習(xí)歷程凡蚜,如有錯誤還請指明。

動畫綜述

Google大大對動畫的總述如下:

Animations can add visual cues that notify users about what's going on in your app. They are especially useful when the UI changes state, such as when new content loads or new actions become available. Animations also add a polished look to your app, which gives it a higher quality look and feel.

沒錯,放上原文我只是裝個逼戒祠,~
簡單來說弥臼,動畫就兩個作用:

  • 添加可視提示,通知我們這個APP中正在發(fā)生的事情朝群。比如用戶界面發(fā)生變化時燕耿,有新的內(nèi)容加載或某些操作變?yōu)榭捎谩?/li>
  • 提供高逼格的外觀(裝逼利器

動畫的分類如下:

動畫分類

屬性動畫(Property Animation)

概述

  • 視圖動畫的缺陷:
    • 對象的局限性:僅限于View
    • 只改變了View的視覺效果,而沒有改變View的屬性
    • 動畫效果單一
  • 屬性動畫的特點:
    • 作用對象:任意對象姜胖,甚至沒對象也可以
    • 作用方式:改變對象的屬性
    • 動畫效果:按需自定義誉帅,不再局限于上述4種基本變換
  • 繼承關(guān)系


    繼承關(guān)系
Java類名 XML關(guān)鍵字 說明
ValueAnimator <animator> 放置在res/animator/目錄下 在一個特定的時間里執(zhí)行一個動畫
TimeAnimator 時序監(jiān)聽回調(diào)工具
ObjectAnimator <objectAnimator> 放置在res/animator/目錄下 一個對象的一個屬性動畫
AnimatorSet <set> 放置在res/animator/目錄下 動畫集合

工作原理

指定時間內(nèi),修改屬性(對象中對應(yīng)的字段)的值右莱,以此實現(xiàn)該對象在屬性上的動畫效果蚜锨。
為了更好的理解,我們舉一個栗子:

圖1.linear animation

圖1中我們搞出了一個假象的對象隧出,動畫作用于這個對象(實際可以說是對象的x屬性踏志,也即對象的水平位置),動畫持續(xù)40ms胀瞪,移動距離40px针余。每10ms(默認(rèn)刷新速率)饲鄙,對象水平移動10px。40ms后圆雁,動畫結(jié)束忍级,物體停止在x=40處。這是一個典型的設(shè)置了linearInterpolator(勻速插值器)的動畫伪朽。

為了更好的了解屬性動畫的工作原理轴咱,下面我們來看一看屬性動畫的組件是怎么計算上面例子的動畫的。


圖2.動畫計算過程

其邏輯可以總結(jié)如下:

  1. 為 ValueAnimator 設(shè)置動畫的時長烈涮,以及對應(yīng)屬性的始 & 末值
  2. 設(shè)置屬性在 始 & 末值 間的變化邏輯
  • TimeInterpolator實現(xiàn)類:插值器-描述動畫的變化速率
  • TypeEvaluator實現(xiàn)類:估值器-描述 屬性值 變化的具體數(shù)值
  1. 根據(jù)2中的邏輯更新當(dāng)前值
  2. 獲取3中更新的 值 朴肺,修改 目標(biāo)屬性值
  3. 刷新視圖。
  4. 重復(fù)4-5坚洽,直到 屬性值 == 末值

下面給出動畫工作的關(guān)鍵類

Java類 說明
ValueAnimator 動畫執(zhí)行類戈稿;核心
ObjectAnimator 動畫執(zhí)行類
TimeInterpolator 時間插值(插值器接口),控制動畫變化率
TypeEvaluator 類型估值(估值器接口)讶舰,設(shè)置屬性值計算方式鞍盗,根據(jù)屬性的 始 & 末值 和 插值 一起計算出當(dāng)前時間的屬性值
AnimatorSet 動畫集
AnimatorInflater 加載屬性動畫的XML文件

一些額外的類

Java類 說明
LayoutTransition 布局動畫,為布局的容器設(shè)置動畫
ViewPropertyAnimator 為View的動畫操作提供一種更加便捷的用法
PropertyValuesHolder 保存動畫過程中所需要操作的屬性和對應(yīng)的值
Keyframe 控制每個時間段執(zhí)行的動畫距離
AnimationListener
AnimationUpdateListener
AnimatorListenerAdapter
動畫事件的監(jiān)聽

具體使用

ValueAnimator

  • 屬性動畫的最核心的類
  • 原理:控制 值 的變化跳昼,之后 手動 賦值給對象的屬性般甲,從而實現(xiàn)動畫

對于控制的 的不同,Android 提供給我們?nèi)N構(gòu)造方法來實例ValueAnimator對象

  1. ValueAnimator.ofInt(int... values) -- 整型數(shù)值
  2. ValueAnimator.ofFloat(float... values) -- 浮點型數(shù)值
  3. ValueAnimator.ofObject(TypeEvaluator evaluator, Object... values) -- 自定義對象類型

下面我們一一介紹

ValueAnimator.ofInt()

  • 作用:將初始值 以整型數(shù)值的形式 過渡到結(jié)束值
  • 估值器:內(nèi)置IntEvaluator估值器
  • 具體使用:
    操作 值 的方式分為 XML 方式/ Java 代碼方式

方式1: Java 方式

推薦 Java 方式鹅颊,因為某些時候我們需要動態(tài)獲取屬性的起始值敷存,顯然XML方式是不支持動態(tài)獲取的。

//設(shè)置動畫 始 & 末值
                //ofInt()兩個作用:
                //1. 獲取實例
                //2. 在傳入?yún)?shù)之間平滑過渡
                //如下則0平滑過渡到3
                ValueAnimator animator = ValueAnimator.ofInt(0,3);
                //如下傳入多個參數(shù)挪略,效果則為0->5,5->3,3->10
                //ValueAnimator animator = ValueAnimator.ofInt(0,5,3,10);

                //設(shè)置動畫的基礎(chǔ)屬性
                animator.setDuration(5000);//播放時長
                animator.setStartDelay(300);//延遲播放
                animator.setRepeatCount(0);//重放次數(shù)
                animator.setRepeatMode(ValueAnimator.RESTART);
                //重放模式
                //ValueAnimator.START:正序
                //ValueAnimator.REVERSE:倒序

                //設(shè)置更新監(jiān)聽
                //值 改變一次历帚,該方法就執(zhí)行一次
                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        //獲取改變后的值
                        int currentValue = (int) animation.getAnimatedValue();
                        //輸出改變后的值
                        Log.d("1111", "onAnimationUpdate: " + currentValue);
                        
                        //改變后的值發(fā)賦值給對象的屬性值
                        view.setproperty(currentValue);
                        
                        //刷新視圖
                        view.requestLayout();
                    }
                });
                //啟動動畫
                animator.start();

以上就是一個標(biāo)準(zhǔn)的Java方式的模板

方式2: XML 方式

  1. 在路徑 res/animator/ 路徑下常見 XML 文件,如 set_animator.xml
  2. 在上述文件中設(shè)置動畫參數(shù)
// ValueAnimator采用<animator>  標(biāo)簽
<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueFrom="1"
    android:valueTo="0"
    android:valueType="floatType"
    android:repeatCount="1"
    android:repeatMode="reverse"/>
/>
  1. Java代碼啟動動畫
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);  
// 載入XML動畫

animator.setTarget(view);  
// 設(shè)置動畫對象

animator.start();  
// 啟動動畫

ValueAnimator.ofFloat()

  • 作用:將初始值 以浮點型數(shù)值的形式 過渡到結(jié)束值
  • 估值器:內(nèi)置FloatEvaluator估值器
  • 具體使用:
    和ValueAnimator.ofInt()及其類似杠娱,以下只說明不同之處挽牢,省略部分參考 ofInt()

方式1:Java方式

ValueAnimator anim = ValueAnimator.ofFloat(0, 3);  
//只是改了實例方法,除此之外完全一樣

方式2:XML方式
只在設(shè)置動畫 XML 文件中的屬性時略有不同

// ValueAnimator 采用 <animator>  標(biāo)簽
// ObjectAnimator 采用 <objectAnimator> 標(biāo)簽
<animatorxmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueTo="200"
    android:valueType="floatType"
    android:propertyName="y"
    android:repeatCount="1"
    android:repeatMode="reverse"/>

ValueAnimator.ofObject()

  • 作用:將初始值 以對象的形式 過渡到結(jié)束值
  • 估值器:Android 不提供摊求,需要自定義估值器
  • 具體使用:
    ValueAnimator.ofObject() 屬于 ValueAnimator 的高級用法禽拔,我們前面提到的對任意對象進行動畫操作,就是通過此方法實現(xiàn)的室叉。
    以下先放上示例模板:
// 創(chuàng)建初始動畫的對象  & 結(jié)束動畫的對象
Point point1 = new Point ();  
Point point2 = new Point ();  

// 創(chuàng)建動畫對象 & 設(shè)置參數(shù)
ValueAnimator anim = ValueAnimator.ofObject(new myObjectEvaluator(), point1 , point2 );  
// 參數(shù)說明
// 1. 自定義的估值器對象(TypeEvaluator 類型參數(shù)) - 下面會詳細(xì)介紹
// 2. 初始動畫的對象
// 3. 結(jié)束動畫的對象
anim.setDuration(length);  
anim.start();

上面示例中睹栖,我們看到有兩個Point對象point1 , point2 。假設(shè)我們有一個自定義View茧痕,這個View中有一個Point對象用于管理坐標(biāo)野来,然后我們在onDraw()方法中根據(jù)這個Point對象的坐標(biāo)值進行繪制,也就是說踪旷,我們可以對Point對象進行動畫操作曼氛,不停的根據(jù)Point的坐標(biāo)刷新View重繪制豁辉,以此就可以實現(xiàn) View 的動畫了。
到這里都很好理解舀患,不過我們還注意到傳入了一個 new myObjectEvaluator()(TypeEvaluator實現(xiàn)類) 參數(shù)徽级,這是干什么的呢?不要急聊浅,下面我們就詳細(xì)解答餐抢。

TypeEvaluator 估值器

其實我們已經(jīng)不止一次的提到估值器的概念,如 ValueAnimator.ofFloat() 方法中的 IntEvaluator 低匙, ValueAnimator.ofFloat() 方法中的 FloatEvaluator旷痕,這兩種都是Android預(yù)置供我們使用的,二者通過計算告知動畫系統(tǒng)如何從初始值過渡到結(jié)束值顽冶。
但是二者雖然好用苦蒿,但也有其局限性:只能針對 Int / Float 類型數(shù)值操作。因此某些我們需要對任意對象進行動畫操作的時候渗稍,二者顯然不能滿足我們的需求了,這時候我們需要自定義一個TypeEvaluator來告知系統(tǒng)如何進行過渡团滥。

那么如何自定義呢竿屹?別急,我們可以先看一看系統(tǒng)提供的 IntEvaluator 是如何實現(xiàn)的:

/**
 * This evaluator can be used to perform type interpolation between int values.
 */
public class IntEvaluator implements TypeEvaluator<Integer> {

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * fraction representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: result = x0 + t * (v1 - v0)灸姊,
     * where x0 is startValue, x1 is endValue, and t is fraction.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value; should be of type int or Integer
     * @param endValue   The end value; should be of type int or Integer
     * @return A linear interpolation between the start and end values, given the fraction parameter.
     */
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

源碼很簡單拱燃,注釋也相當(dāng)詳細(xì),方便觀看力惯,我再次解釋一下:
IntEvaluator 實現(xiàn)了 TypeEvaluator 接口碗誉,然后重寫了 evaluate() 方法。該方法的三個參數(shù)意義如下:

  • fraction:表示動畫完成度父晶,據(jù)此計算當(dāng)前動畫的值
  • startValue:動畫初始值
  • endValue:動畫結(jié)束值

那么 evalute() 方法的返回值就不難理解了哮缺,簡單的數(shù)學(xué)公式,返回當(dāng)前動畫的值甲喝。

是不是很簡單尝苇?因此我們自定義 TypeEvaluator 時只需要實現(xiàn) TypeEvaluator 接口,然后重寫 evaluate() 方法埠胖,在此方法中處理好邏輯即可糠溜。
下面我們就動手寫一個自定義 TypeEvaluator

自定義TypeEvaluator

我們還是以上面提過的Point對象管理View坐標(biāo)的為例:

  1. 定義Point
public class Point {
    //記錄坐標(biāo)位置
    private float x;
    private float y;

    //通過構(gòu)造方法設(shè)置坐標(biāo),因此不需要額外的set方法
    public Point(float x, float y) {
        this.x = x;
        this.y = y;
    }

    //get方法直撤,獲取當(dāng)前坐標(biāo)值
    public float getX() {
        return x;
    }
    
    public float getY() {
        return y;
    }
}
  1. 定義 PointEvaluator
//實現(xiàn)TypeEvaluator接口
public class PointEvaluator implements TypeEvaluator {

    //重寫evaluate()方法
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {

        //始末值強轉(zhuǎn)為Point對象
        Point startPoint = (Point) startValue;
        Point endPoint = (Point) endValue;

        //通過fraction計算當(dāng)前動畫的坐標(biāo)值x,y
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());

        //返回以上述x,y組裝的新的Point對象
        Point point = new Point(x,y);
        return point;
    }
}
  1. Point 對象間的平滑過渡
// 創(chuàng)建初始動畫的對象  & 結(jié)束動畫的對象
Point point1 = new Point(0, 0);  
Point point2 = new Point(500, 500);  

// 創(chuàng)建動畫對象 & 設(shè)置參數(shù)
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1 , point2 );  
anim.setDuration(3000);
anim.start();

以上就是自定義TypeEvaluator的全部用法非竿。
下面我們就可以嘗試用上述知識練習(xí)如何對對象進行動畫操作,從而實現(xiàn)自定義View的動畫效果

自定義View的動畫效果

  1. 新建MyAnimView繼承View
public class MyAnimView extends View {

    //常量
    public static final float RADIUS = 50f;

    //當(dāng)前Point谋竖,記錄當(dāng)前動畫的值(x,y坐標(biāo))
    private Point curPoint;

    //畫筆
    private Paint mPaint;

    //Java代碼實例化View時調(diào)用
    public MyAnimView(Context context) {
        super(context);
    }
    //XML文件實例時調(diào)用
    public MyAnimView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        //初始化畫筆
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLUE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //第一次繪制時
        if (curPoint == null){
            //初始化坐標(biāo)為(50f, 50f)
            curPoint = new Point(RADIUS,RADIUS);
            //畫圓
            drawCircle(canvas);
            //開始動畫
            startAnimation();
        }else {//非第一次繪制
            drawCircle(canvas);
        }
    }

    //在當(dāng)前坐標(biāo)處繪制一個半徑為50f的圓
    private void drawCircle(Canvas canvas) {
        float x = curPoint.getX();
        float y = curPoint.getY();
        canvas.drawCircle(x,y,RADIUS,mPaint);
    }

    //開始動畫
    private void startAnimation() {
        //設(shè)置 起始值&結(jié)束值
        Point startPoint = new Point(RADIUS,RADIUS);//起始為左上角(50f,50f)
        Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//終點為右下角(屏幕寬度-50f,屏幕高度-50f)
        final ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
        //設(shè)置插值器
        anim.setInterpolator(new BounceInterpolator());
        //設(shè)置監(jiān)聽
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            //每當(dāng)Point的值有改變的時候红柱,都會調(diào)用onAnimationUpdate()方法
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //更新curPoint承匣,即更新當(dāng)前坐標(biāo)
                curPoint = (Point) animation.getAnimatedValue();
                // 刷新,重現(xiàn)調(diào)用onDraw()方法
                // 由于curPoint的值改變豹芯,那么繪制的位置也會改變悄雅,也就實現(xiàn)了一個從左上到右下的平移動畫
                invalidate();
            }
        });
        anim.setDuration(5000);
        anim.start();
    }
}
  1. 布局文件中引入該自定義view
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.whdalive.learn_propertyanimation.Property.MyAnimView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

效果演示


上圖中我們發(fā)現(xiàn)球并非勻速,而是有一個bounce的效果铁蹈,這是因為我們?yōu)閯赢嬏砑恿艘粋€插值器 anim.setInterpolator(new BounceInterpolator())宽闲。
具體Interpolator的使用,我們在前一篇Android 視圖動畫(View Animation) 使用詳解中已經(jīng)詳細(xì)講解了握牧,此處就不再贅述容诬。

注意
ValueAnimator.ofObject()的本質(zhì)還是操作 , 只是將 多個值 封裝到一個對象里沿腰,同時對 該對象 里的 多個值 一起操作而已

至此览徒,ValueAnimator 的用法就介紹完了,下面我們介紹一個貌似更加常用的類 ObjectAnimator

ObjectAnimator

  • 屬性動畫重要的類
  • 原理:控制 值 的變化颂龙,之后 自動 賦給對象的屬性习蓬,從而實現(xiàn)動畫
  • 與ValueAnimator對比
    • ValueAnimator的子類
    • ValueAnimator只是對 進行平滑的動畫過渡;ObjectAnimator直接對 任意對象的任意屬性 進行動畫操作措嵌,如View的alpha屬性
    • ValueAnimator需要我們?yōu)閷ο髮傩?strong>手動賦值躲叼;ObjectAnimator會為對象屬性自動賦值

具體使用

由于繼承關(guān)系的存在,ObjectAnimator的用法和ValueAnimator及其類似:Java方式設(shè)置 / XML方式設(shè)置(推薦Java方式)

方式1:Java方式

ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String property, float ....values);  
//ObjectAnimator animator = ObjectAnimator.ofInt(Object object, String property, int ....values);  
//ObjectAnimator animator = ObjectAnimator.ofObject(Object object, String property, TypeEvaluator evaluator,Object....values);  

// 以ofFloat為例 參數(shù)說明:
// Object object:需要操作的對象
// String property:需要操作的對象的屬性
// float ....values:動畫初始值 & 結(jié)束值(不固定長度)
// 若是兩個參數(shù)a,b企巢,則動畫效果則是從屬性的a值到b值
// 若是三個參數(shù)a,b,c枫慷,則則動畫效果則是從屬性的a值到b值再到c值
// 以此類推
// 至于如何從初始值 過渡到 結(jié)束值,同樣是由估值器決定浪规,此處ObjectAnimator.ofFloat()是有系統(tǒng)內(nèi)置的浮點型估值器FloatEvaluator或听,同ValueAnimator講解

//動畫基本屬性
anim.setDuration(500); 
anim.setStartDelay(500);
anim.setRepeatCount(0);
anim.setRepeatMode(ValueAnimator.RESTART);

animator.start();  
// 啟動動畫

方式2:XML方式設(shè)置

  1. 路徑res/animator/下創(chuàng)建動畫XML文件,如set_animation.xml
  2. 設(shè)置動畫參數(shù)
ObjectAnimator 采用<objectAnimator >  標(biāo)簽
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"  
    android:valueFrom="1"   // 初始值
    android:valueTo="0"  // 結(jié)束值
    android:valueType="floatType"  // 變化值類型 :floatType & intType
    android:propertyName="alpha" // 對象變化的屬性名稱
/>
  1. Java啟動動畫
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.view_animation);  
// 載入XML動畫
animator.setTarget(view);  
// 設(shè)置動畫對象
animator.start();  
// 啟動動畫

以上為ObjectAnimator的標(biāo)準(zhǔn)使用模板笋婿,我們通過控制傳入 ofFloat()的第二個參數(shù)preperty 或者 XML中的android:propertyName屬性 來產(chǎn)生不同動畫效果誉裆,以下為Android預(yù)置好的一些屬性:即四種基本變換,透明度萌抵、平移找御、縮放、旋轉(zhuǎn)

屬性 作用 數(shù)值類型
alpha 透明度 float
translationX X方向的位移 float
translationY Y方向的位移 float
scaleX X方向的縮放倍數(shù) float
scaleY Y方向的縮放倍數(shù) float
rotation 以屏幕方向為軸的旋轉(zhuǎn)度數(shù) float
rotationX 以X軸為軸的旋轉(zhuǎn)度數(shù) float
rotationY 以Y軸為軸的旋轉(zhuǎn)度數(shù) float

那么問題來了绍填,除了以上的屬性霎桅,我們還可以傳入哪些屬性值呢?
答案是 任意屬性值

為了理解這個問題讨永,我們需要先了解一下ObjectAnimator是如何自動為屬性賦值的滔驶。

自動賦值
  • 以alpha+textview為例,很顯然textview是不具備alpha這一屬性的卿闹,那么ObjectAnimator是如何操作的呢揭糕?
  • 原理:實際上ObjectAnimator內(nèi)部的工作機制是去尋找這個屬性名對應(yīng)的get和set方法萝快,通過二者賦值。
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha",1f, 0f, 1f);
// TextView對象中并沒有alpha這個屬性值
// ObjectAnimator并不是直接對我們傳入的屬性名進行操作
// 而是根據(jù)傳入的屬性值"alpha" 去尋找對象對應(yīng)屬性名對應(yīng)的get和set方法著角,從而通過set() &  get()對屬性進行賦值

// 因為TextView對象中有alpha屬性所對應(yīng)的get & set方法
// 所以傳入的alpha屬性是有效的
// 所以才能對alpha這個屬性進行操作賦值
public void setRotation(float value);  
public float getRotation();  

// 實際上揪漩,這兩個方法是由View對象提供的,其余平移旋轉(zhuǎn)等屬性同理

因此:

  • ObjectAnimator 類針對的是任意對象 & 任意屬性值吏口,并不是單單針對于View對象
  • 如果需要采用ObjectAnimator 類實現(xiàn)動畫效果奄容,那么需要操作的對象就必須有該屬性的set() & get()

特別注意:如果想讓對象的屬性a的動畫生效,屬性a需要同時滿足下面兩個條件:

  1. 對象必須要提供屬性a的set()方法
    a. 如果沒傳遞初始值产徊,那么需要提供get()方法昂勒,因為系統(tǒng)要去拿屬性a的初始值
    b. 若該條件不滿足,程序直接崩潰
  2. 對象提供的 屬性a的set()方法 對 屬性a的改變 必須通過某種方法反映出來
    a. 如帶來ui上的變化
    b. 若這條不滿足舟铜,動畫無效戈盈,但不會崩潰)
自定義對象實現(xiàn)動畫效果

本質(zhì):

  • 為對象設(shè)置需要操作屬性的set() & get()方法
  • 通過實現(xiàn)TypeEvaluator類從而定義屬性變化的邏輯

下面我們通過實例來介紹如何通過自定義屬性實現(xiàn)動畫效果

  • 實現(xiàn)效果(在前面運動小球的基礎(chǔ)上控制顏色變化)

    untitled.gif

  • 具體實現(xiàn)

  1. 為對象類屬性color設(shè)置set() & get() 方法
public class MyAnimView extends View {
    
    ...
    
    //將顏色設(shè)置為String類型,使用#RRGGBB格式表示顏色
    private String color;

    //get()方法
    public String getColor() {
        return color;
    }

    //set()方法
    public void setColor(String color) {
        mPaint.setColor(Color.parseColor(color));
        this.color = color;
        //改變畫筆顏色后立即刷新視圖谆刨,然后onDraw()方法就會調(diào)用
        invalidate();
    }
    ...
}

  1. 布局中加入自定義view
    同前面ValueAnimator的自定義view
  2. 根據(jù)需求實現(xiàn)TypeEvaluator接口
public class ColorEvaluator implements TypeEvaluator {
    // 實現(xiàn)TypeEvaluator接口

    private int mCurrentRed;

    private int mCurrentGreen ;

    private int mCurrentBlue ;

    // 復(fù)寫evaluate()
    // 在evaluate()里寫入對象動畫過渡的邏輯:此處是寫顏色過渡的邏輯
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {

        // 獲取到顏色的初始值和結(jié)束值
        String startColor = (String) startValue;
        String endColor = (String) endValue;

        // 通過字符串截取的方式將初始化顏色分為RGB三個部分塘娶,并將RGB的值轉(zhuǎn)換成十進制數(shù)字
        // 那么每個顏色的取值范圍就是0-255
        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);

        // 將初始化顏色的值定義為當(dāng)前需要操作的顏色值
            mCurrentRed = startRed;
            mCurrentGreen = startGreen;
            mCurrentBlue = startBlue;


        // 計算初始顏色和結(jié)束顏色之間的差值
        // 該差值決定著顏色變化的快慢:初始顏色值和結(jié)束顏色值很相近,那么顏色變化就會比較緩慢;否則,變化則很快
        // 具體如何根據(jù)差值來決定顏色變化快慢的邏輯寫在getCurrentColor()里.
        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);
                    // getCurrentColor()決定如何根據(jù)差值來決定顏色變化的快慢 ->>關(guān)注1
        } else if (mCurrentGreen != endGreen) {
            mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,
                    redDiff, fraction);
        } else if (mCurrentBlue != endBlue) {
            mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,
                    redDiff + greenDiff, fraction);
        }
        // 將計算出的當(dāng)前顏色的值組裝返回
        String currentColor = "#" + getHexString(mCurrentRed)
                + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);

        // 由于我們計算出的顏色是十進制數(shù)字痊夭,所以需要轉(zhuǎn)換成十六進制字符串:調(diào)用getHexString()->>關(guān)注2
        // 最終將RGB顏色拼裝起來,并作為最終的結(jié)果返回
        return currentColor;
    }


    // 關(guān)注1:getCurrentColor()
    // 具體是根據(jù)fraction值來計算當(dāng)前的顏色血柳。

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

    // 關(guān)注2:將10進制顏色值轉(zhuǎn)換成16進制。
    private String getHexString(int value) {
        String hexString = Integer.toHexString(value);
        if (hexString.length() == 1) {
            hexString = "0" + hexString;
        }
        return hexString;
    }

}
  1. 調(diào)用ObjectAnimator.ofObject()方法
//借助AnimatorSet生兆,實現(xiàn)組合動畫
public class MyAnimView extends View {
     ...
    //開始動畫
    private void startAnimation() {
        //設(shè)置 起始值&結(jié)束值
        Point startPoint = new Point(RADIUS,RADIUS);//起始為左上角(50f,50f)
        Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//終點為右下角(屏幕寬度-50f,屏幕高度-50f)
        final ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
        //設(shè)置插值器
        anim.setInterpolator(new BounceInterpolator());
        //設(shè)置監(jiān)聽
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            //每當(dāng)Point的值有改變的時候,都會調(diào)用onAnimationUpdate()方法
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //更新curPoint膝宁,即更新當(dāng)前坐標(biāo)
                curPoint = (Point) animation.getAnimatedValue();
                // 刷新鸦难,重現(xiàn)調(diào)用onDraw()方法
                // 由于curPoint的值改變,那么繪制的位置也會改變员淫,也就實現(xiàn)了一個從左上到右下的平移動畫
                invalidate();
            }
        });
       // anim.setDuration(5000);
        //anim.start();
        

        //顏色過渡的代碼邏輯放在startAnimation()方法中合蔽,本身就在MyAnimView中執(zhí)行,因此傳入 this 參數(shù)即可
        ObjectAnimator anim2 = ObjectAnimator.ofObject(this,"color",new ColorEvaluator(),"#FF0000", "#0000FF");
        //創(chuàng)建一個AnimatorSet介返,讓兩個動畫同時播放拴事,時長5s
        AnimatorSet animationSet = new AnimatorSet();
        animationSet.play(anim).with(anim2);
        animationSet.setDuration(5000);
        //啟動動畫
        animationSet.start();
    }

以上我們就實現(xiàn)了一個平移 & 顏色變化的動畫。
至此關(guān)于ValueAnimator和ObjectAnimeato的使用就講解完了圣蝎,下面我們再講講一些額外的使用方法

1.AnimatorSet 組合動畫

  • 獨立的動畫能夠?qū)崿F(xiàn)的視覺效果畢竟是相當(dāng)有限的刃宵,因此將多個動畫組合到一起播放就顯得尤為重要
  • 使用:
AnimatorSet.play(Animator anim)   :播放當(dāng)前動畫
AnimatorSet.after(long delay)   :將現(xiàn)有動畫延遲x毫秒后執(zhí)行
AnimatorSet.with(Animator anim)   :將現(xiàn)有動畫和傳入的動畫同時執(zhí)行
AnimatorSet.after(Animator anim)   :將現(xiàn)有動畫插入到傳入的動畫之后執(zhí)行
AnimatorSet.before(Animator anim) :  將現(xiàn)有動畫插入到傳入的動畫之前執(zhí)行
  • 實例
    Java方式
ObjectAnimator a1 = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0f);  
ObjectAnimator a2 = ObjectAnimator.ofFloat(view, "translationY", 0f, viewWidth);  
......
AnimatorSet animSet = new AnimatorSet();  
animSet.setDuration(5000);  
animSet.setInterpolator(new LinearInterpolator());   
//animSet.playTogether(a1, a2, ...); //兩個動畫同時執(zhí)行  
animSet.play(a1).after(a2); //先后執(zhí)行
......//其他組合方式
animSet.start();  

XML方式

<set
  android:ordering=["together" | "sequentially"]>

    <objectAnimator
        android:propertyName="string"
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <animator
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <set>
        ...
    </set>
</set>

2. 監(jiān)聽動畫

  • 監(jiān)聽到動畫的各種事件,比如動畫何時開始徘公,何時結(jié)束牲证,然后在開始或者結(jié)束的時候去執(zhí)行一些邏輯處理
  • Animator類提供addListener()方法,說明其子類都可以使用該方法(關(guān)于繼承關(guān)系关面,前面我們提到過了)
  • 使用方法
anim.addListener(new AnimatorListener() {
          @Override
          public void onAnimationStart(Animation animation) {
              //動畫開始時執(zhí)行
          }
      
           @Override
          public void onAnimationRepeat(Animation animation) {
              //動畫重復(fù)時執(zhí)行
          }

         @Override
          public void onAnimationCancel()(Animation animation) {
              //動畫取消時執(zhí)行
          }
    
          @Override
          public void onAnimationEnd(Animation animation) {
              //動畫結(jié)束時執(zhí)行
          }
      });

// 特別注意:每次監(jiān)聽必須4個方法都重寫坦袍。
  • 缺點:
    很多時候我們并不想要監(jiān)聽那么多個事件十厢,可能我只想要監(jiān)聽動畫結(jié)束這一個事件,那么每次都要將四個接口全部實現(xiàn)一遍就顯得非常繁瑣捂齐。
  • 如何解決
    采用動畫適配器(AnimatorListenerAdapter)蛮放,解決 實現(xiàn)接口繁瑣 的問題
anim.addListener(new AnimatorListenerAdapter() {  
// 向addListener()方法中傳入適配器對象AnimatorListenerAdapter()
// 由于AnimatorListenerAdapter中已經(jīng)實現(xiàn)好每個接口
// 所以這里不實現(xiàn)全部方法也不會報錯
    @Override  
    public void onAnimationStart(Animator animation) {  
    // 如想只想監(jiān)聽動畫開始時刻,就只需要單獨重寫該方法就可以
    }  
});

3. ViewPropertyAnimator用法

  • Google為View的動畫操作提供的一種便捷用法
  • 具體使用:
// 使用解析
        View.animate().xxx().xxx();
        // ViewPropertyAnimator的功能建立在animate()上
        // 調(diào)用animate()方法返回值是一個ViewPropertyAnimator對象,之后的調(diào)用的所有方法都是通過該實例完成
        // 調(diào)用該實例的各種方法來實現(xiàn)動畫效果
        // ViewPropertyAnimator所有接口方法都使用連綴語法來設(shè)計奠宜,每個方法的返回值都是它自身的實例
        // 因此調(diào)用完一個方法后可直接連綴調(diào)用另一方法,即可通過一行代碼就完成所有動畫效果
        
// 以下是例子
        mButton = (Button) findViewById(R.id.Button);
        // 創(chuàng)建動畫作用對象:此處以Button為例

        mButton.animate().alpha(0f);
        // 單個動畫設(shè)置:將按鈕變成透明狀態(tài) 
        mButton.animate().alpha(0f).setDuration(5000).setInterpolator(new BounceInterpolator());
        // 單個動畫效果設(shè)置 & 參數(shù)設(shè)置 
        mButton.animate().alpha(0f).x(500).y(500);
        // 組合動畫:將按鈕變成透明狀態(tài)再移動到(500,500)處
        
        // 特別注意:
        // 動畫自動啟動,無需調(diào)用start()方法.因為新的接口中使用了隱式啟動動畫的功能包颁,只要我們將動畫定義完成后,動畫就會自動啟動
        // 該機制對于組合動畫也同樣有效挎塌,只要不斷地連綴新的方法徘六,那么動畫就不會立刻執(zhí)行,等到所有在ViewPropertyAnimator上設(shè)置的方法都執(zhí)行完畢后榴都,動畫就會自動啟動
        // 如果不想使用這一默認(rèn)機制待锈,也可以顯式地調(diào)用start()方法來啟動動畫
  • 注意事項
    • ViewPropertyAnimator實例通過View.animate()方法創(chuàng)建,之后的調(diào)用的所有方法嘴高,設(shè)置的所有屬性都是通過這個實例完成的竿音。
    • ViewPropertyAnimator隱式啟動動畫,即當(dāng)我們將動畫定義完成之后拴驮,動畫就會自動啟動春瞬。當(dāng)然,如果需要手動顯式啟動套啤,我們?nèi)匀豢梢哉{(diào)用start()方法啟動動畫宽气。
    • ViewPropertyAnimator的連綴語法:每個方法返回值都是自身實例,因此方法的調(diào)用可以連綴調(diào)用潜沦,如上面示例代碼中寫的那樣萄涯。

至此,關(guān)于屬性動畫能想到的就全都講解完了唆鸡,至于有所遺漏的涝影,留作日后再補充吧~


總結(jié)


歡迎關(guān)注whd_Alive的簡書

  • 不定期分享Android開發(fā)相關(guān)的技術(shù)干貨伯襟,期待與你的交流,共勉握童。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末逗旁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌片效,老刑警劉巖红伦,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異淀衣,居然都是意外死亡昙读,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門膨桥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蛮浑,“玉大人,你說我怎么就攤上這事只嚣【谥桑” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵册舞,是天一觀的道長蕴掏。 經(jīng)常有香客問我,道長调鲸,這世上最難降的妖魔是什么盛杰? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮藐石,結(jié)果婚禮上即供,老公的妹妹穿的比我還像新娘。我一直安慰自己于微,他們只是感情好逗嫡,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著株依,像睡著了一般祸穷。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上勺三,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音需曾,去河邊找鬼吗坚。 笑死,一個胖子當(dāng)著我的面吹牛呆万,可吹牛的內(nèi)容都是我干的商源。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼谋减,長吁一口氣:“原來是場噩夢啊……” “哼牡彻!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤庄吼,失蹤者是張志新(化名)和其女友劉穎缎除,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體总寻,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡器罐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了渐行。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片轰坊。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖祟印,靈堂內(nèi)的尸體忽然破棺而出肴沫,到底是詐尸還是另有隱情,我是刑警寧澤蕴忆,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布颤芬,位于F島的核電站,受9級特大地震影響孽文,放射性物質(zhì)發(fā)生泄漏驻襟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一芋哭、第九天 我趴在偏房一處隱蔽的房頂上張望沉衣。 院中可真熱鬧,春花似錦减牺、人聲如沸豌习。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肥隆。三九已至,卻和暖如春稚失,著一層夾襖步出監(jiān)牢的瞬間栋艳,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工句各, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吸占,地道東北人。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓凿宾,卻偏偏與公主長得像矾屯,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子初厚,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345