Android 動畫之屬性動畫

簡介

Android 開發中,老是須要一些動畫來優化用戶的交互體驗,提升用戶滿意度。所以,Google 爲咱們提供了一些用於處理動畫效果的動畫框架。Android 的動畫框架分爲兩類:面試

  • 傳統動畫(Animation):經過系統不斷調用onDraw方法重繪界面,來達到動畫的效果。
  • 屬性動畫(Animator):經過操縱一個屬性的get/set方法,真實地改變目標的某些屬性。

傳統動畫框架的侷限性

既然有了傳統動畫框架,Google 爲何還要創造一個屬性動畫框架呢?性能優化

咱們下面舉個例子來講明一下傳統動畫的侷限性。架構

在佈局中加入一個 ImageView 和一個 Button,點擊 ImageView 後彈出一個 Toast,點擊 Button 後使 ImageView 展示一個向右平移的動畫效果。框架

下面是使用傳統動畫實現的代碼:異步

TranslateAnimation animation = new TranslateAnimation(0,200,0,0);    // 平移動畫x軸移動200,y軸不動
animation.setDuration(1000);    // 動畫時長
animation.setFillAfter(true);   // 使動畫結束後停留在結束的位置
mIvPicture.startAnimation(animation);
複製代碼

運行後,ImageView 確實進行了咱們預期的平移的效果。但是當咱們嘗試點擊 ImageView 當前的位置時,卻沒有 Toast 彈出。咱們再嘗試去點擊 ImageView 開始動畫前的位置,卻成功彈出了 Toast。ide

這就是傳統動畫很大的侷限性:函數

  • 它僅僅是重繪了控件,改變了其顯示的位置。但真正事件響應的位置,卻並無發生改變。所以傳統動畫不適合作具備交互的動畫效果。僅僅能作一些顯示的動畫效果
  • 傳統動畫是不斷經過 onDraw() 方法重繪界面,必然會十分耗費GPU資源。
  • 傳統動畫所支持的動畫類型少,僅有旋轉、縮放、位移、透明度這四種動畫效果。雖然經過組合能夠實現豐富的效果,但相比直接經過改變屬性來實現的屬性動畫來講,仍是有很大的侷限性的。

所以,Google 爲咱們提供了一套全新的屬性動畫框架,來讓咱們實現更豐富的動畫效果。佈局

ObjectAnimator

ObjectAnimator 是屬性動畫中,最簡單也最經常使用的一個對象。性能

實現 Animation 框架的功能

平移優化

前文提到的使 ImageVIew 向右平移 200 像素的動畫效果,使用屬性動畫只須要很簡單的幾句代碼便可實現:

ObjectAnimator.ofFloat(mIvPicture,"translationX",0F,200F)
        .setDuration(1000)
        .start();
複製代碼

咱們來分析一下這一句代碼。咱們調用了ofFloat代碼,並傳入三個參數。

​ 第一個參數是動畫須要操縱的目標,在這裏是咱們的 ImageView。

​ 第二個參數是所須要操縱的目標所具有的屬性名稱。

​ 第三個參數是這個動畫變化的取值範圍。

最後設置一下它的動畫的屬性,即可以 start 了。

此次咱們再次點擊 ImageView 目前的位置,成功地彈出了 Toast。這證明了屬性動畫是經過改變物體的屬性來達到動畫效果的理論。

當咱們須要改變 y 座標時,只須要把 "translationX" 變爲 "translationY" 便可。

其實 ,只要Google對一個對象的某個屬性提供了get和set方法,咱們就可使用這個屬性來實現動畫效果

其實咱們還能用 X Y 兩個屬性實現以前的動畫效果,那麼對象屬性中 X 的 Y 與 translationX translationY 有什麼區別呢?

  • translationX translationY指的是物體的偏移量,而X Y則表示它最終到達的絕對位置。

旋轉

旋轉屬性使用的是 "rotation" 屬性,後面的變換範圍的單位是角度。

好比想讓 ImageView 旋轉90度,只須要

ObjectAnimator.ofFloat(mIvPicture,"rotation",0F,90F)
        .setDuration(1000)
        .start();
複製代碼

其餘

其實屬性動畫能操縱的屬性,只要具備 set、get 方法,均可以進行操縱。如 scaleX、scaleY 等等...

插值器

Android 爲咱們內置了插值器,使咱們的動畫更爲天然。好比可讓咱們的平移動畫像物體的重力加速度由快到慢的 Accelerate 等等

Android中內置了七種插值器,分別是

  • Accelerate
  • Decelerate
  • Accelerate/Decelerate
  • Anticipate
  • Overshoot
  • Anticipate/Overshoot
  • Bounce

要應用插值器,能夠調用 ObjectAnimator 的 setInterpolator 方法, new 出對應的插值器做爲參數(xxxInterpolator)。好比下面這段代碼:

animator.setInterpolator(new AccelerateInterpolator());
複製代碼

經過插值器,咱們可讓動畫的效果更佳天然。

多種屬性動畫同時做用

當咱們把幾種動畫按順序寫下時,運行程序,會發現效果是三種屬性動畫的疊加。由此能夠發現,屬性動畫在調用 start 方法後,其實是一個異步的過程。所以咱們就能夠看到三個屬性動畫同時做用的效果。經過這樣的方法,其實就能夠實現多種屬性動畫同時做用的效果:

ObjectAnimator.ofFloat(mIvPicture,"translationX",0F,200F).setDuration(1000).start();
ObjectAnimator.ofFloat(mIvPicture,"rotationX",0F,360F).setDuration(1000).start();
ObjectAnimator.ofFloat(mIvPicture,"translationY",0F,200F).setDuration(1000).start();
複製代碼

其實 Google 爲咱們提供了更好的方法,來實現這樣的效果。

咱們可使用 PropertyValuesHolder 來實現。其構造函數僅僅比 ObjectAnimator 少了一個做用對象參數。以後經過ObjectAnimator 的 ofPropertyValuesHolder 方法,傳入做用對象以及要同時做用的 PropertyValuesHolder 便可執行。能夠看到下面的代碼示例:

PropertyValuesHolder p1 = PropertyValuesHolder.ofFloat("translationX",0F,200F);
PropertyValuesHolder p2 = PropertyValuesHolder.ofFloat("rotationX",0F,360F);
PropertyValuesHolder p3 = PropertyValuesHolder.ofFloat("translationY",0F,200F);
ObjectAnimator.ofPropertyValuesHolder(mIvPicture,p1,p2,p3).setDuration(1000).start();
複製代碼

運行後能夠發現,與以前的效果是相同的。

那既然兩種方法效果同樣,這樣相比以前有什麼好處麼?

  • 其實 Google 在 PropertyValuesHolder 內部進行了一些優化,使得咱們使用多個屬性動畫時更加有效率,節省系統資源。

AnimatorSet 屬性集合

playTogether 方法

咱們其實還能夠經過 AnimatorSet,來實現一樣的效果。這裏咱們調用了 set 的 playTogether 方法,使得這些方法同時執行:

AnimatorSet set = new AnimatorSet();
ObjectAnimator animator1 = ObjectAnimator.ofFloat(mIvPicture, "translationX", 0F, 200F);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(mIvPicture, "rotationX", 0F, 360F);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(mIvPicture, "translationY", 0F, 200F);
set.playTogether(animator1,animator2,animator3);
set.setDuration(1000);
set.start();
複製代碼

playSequentially方法

除了 playTogether 方法外,AnimatorSet 還提供了 playSequentially 方法,它可使得動畫按順序執行。具體順序取決於調用時的參數順序。

AnimatorSet set = new AnimatorSet();
ObjectAnimator animator1 = ObjectAnimator.ofFloat(mIvPicture, "translationX", 0F, 200F);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(mIvPicture, "rotationX", 0F, 360F);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(mIvPicture, "translationY", 0F, 200F);
set.playSequentially(animator1,animator2,animator3);
set.setDuration(1000);
set.start();
複製代碼

play 與 with、after 方法

咱們除了能夠用上述方法來讓動畫按順序執行外,也能夠經過 AnimatorSet 的 play、with、after、before 等方法相組合來控制動畫播放關係。

例如以下的代碼就能夠實現先平移,再旋轉的效果

set.play(animator1).with(animator3);
set.play(animator2).after(animator1);
複製代碼

動畫監聽事件

經過下面的代碼,咱們能夠實現按鈕按下後漸隱的效果。

mBtnPress.setOnClickListener(new OnClickListener() {
    @Override
    public void onClick(View v) {
        ObjectAnimator animator = ObjectAnimator.ofFloat(mBtnPress,"alpha",1F,0F);
        animator.setDuration(1000);
        animator.start();
    }
});
複製代碼

但若是咱們想要在動畫播放完成後再執行一些操做的話,又該如何實現呢?

  • 咱們可使用 ObjectAnimator 的 addListener方法,傳入一個AnimatorListener,爲動畫設置監聽事件。

AnimatorListener

一個AnimatorListener,須要實現四個方法,分別是:

  • onAnimationStart
  • onAnimationEnd
  • onAnimationCancel
  • onAnimationRepeat

它們的回調時機咱們根據字面意思即可以理解。大部分時候,咱們須要實現的是onAnimationEnd方法。

animator.addListener(new AnimatorListener() {
    @Override
    public void onAnimationStart(Animator animation) {
    }
    @Override
    public void onAnimationEnd(Animator animation) {
        Toast.makeText(MainActivity.this,"Animation End",Toast.LENGTH_SHORT).show();
    }
    @Override
    public void onAnimationCancel(Animator animation) {
    }
    @Override
    public void onAnimationRepeat(Animator animation) {
    }
});
複製代碼

AnimatorListenerAdapter

若是每次監聽都須要實現這麼多方法,未免太麻煩了一點。所以 Android 爲咱們提供了另外一種方法來添加動畫的監聽事件:在添加 AnimatorListener 的時候,傳入 AnimatorListenerAdapter 便可。這樣咱們就只須要實現本身須要的方法便可。

animator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        Toast.makeText(MainActivity.this,"Animation End",Toast.LENGTH_SHORT).show();
    }
});
複製代碼

ValueAnimator

簡介

ValueAnimator 自己不做用於任何一個屬性,也不提供任何一種動畫。它就是一個數值發生器,能夠產生想要的各類數值。Android 系統爲它提供了不少計算數值的方法,如 int、float 等等。咱們也能夠本身實現計算數值的方法。其實,在屬性動畫中,如何產生每一步的動畫效果,都是經過 ValueAnimator 計算出來的。

好比咱們要實現一個從 0-100 的位移動畫。隨着動畫時間的持續,它產生的值也會從 0-100 遞增。經過這個 ValueAnimator 產生的值,再進行屬性的設置便可。

那麼 ValueAnimator 到底是如何產生這些值的呢?

  • 首先 ValueAnimator會根據會根據動畫已進行的時間與它持續的總時間的比值,產生一個0-1的時間因子。有了這樣的時間因子,通過相應的變換,就能夠根據初始值和最終值來生成中間的相應值。同時,經過插值器的使用,咱們還能夠進一步控制每個時間因子產生值的變化速率。若是咱們使用的是線性插值器,那麼它生成值的時候就會呈一個線性變化。若是咱們使用一個加速度插值器,那麼它生成值時便會呈一個二次曲線,增加率愈來愈快。

因爲 ValueAnimator 不做用於任何一個屬性,也不提供任何一種動畫。所以並無 ObjectAnimator 使用得普遍。

實際上,ObjectAnimator 就是基於 ValueAnimator 進行的一次封裝。咱們能夠查看 ObjectAnimator 的源碼,會發現它繼承自 ValueAnimator,是它的一個子類。正是 ValueAnimator 產生的變化值,才使得 ObjectAnimator 能夠將它應用於各個屬性。

使用方法

咱們能夠經過 ValueAnimator 的 ofXXX 產生一個 XXX 類型的值(如ofInt),而後爲 ValueAnimator 添加一個更新的回調事件。在回調事件中,經過參數 animation 的 getAnimationValue() 方法,來獲取對應的 value。有了這個值,咱們就能夠實現咱們全部想要的動畫效果。

好比此處就經過 ValueAnimator 實現了一個計時器的動畫效果。

ValueAnimator animator = ValueAnimator.ofInt(0,100);
animator.setDuration(5000);
animator.addUpdateListener(new AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        Integer value = (Integer) animation.getAnimatedValue();
        mButton.setText(""+value);
    }
});
animator.start();
複製代碼

自定義數值生成器

前面提到,ValueAnimator 能夠建立自定義的數值生成器,作法就是調用 ValueAnimator 的 ofObject 方法,建立一個 TypeEvaluator 做爲參數。以後咱們能夠經過重寫 TypeEvaluator 的 evaluate 方法,來按照本身的規則返回具體的值。

ValueAnimator animator = ValueAnimator.ofObject(new TypeEvaluator() {
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {
        //計算
        return null;    //返回值
    }
});
複製代碼

咱們來看一下 evaluate 方法的幾個參數

  • float fraction:前面提到的時間因子
  • Object startValue:起始值
  • Object endValue:結束值

經過這三個值,咱們就能夠通過計算產生全部咱們想要的值。

其實,經過 TypeEvaluator,咱們不光能產生普通的數據,還能結合泛型,咱們還能定義更加複雜的數據:

咱們能夠在建立 TypeEvaluator 時指定具體類型,來達到更豐富的效果。好比這裏就用到了一個名爲 PointF 的數據類型:

ValueAnimator animator = ValueAnimator.ofObject(new TypeEvaluator<PointF>() {
    @Override
    public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
        //計算
        return null;    //返回值
    }
});
複製代碼

最後送福利了,如今關注我而且加入羣聊能夠獲取包含源碼解析,自定義View,動畫實現,架構分享等。 內容難度適中,篇幅精煉,天天只需花上十幾分鍾閱讀便可。 你們能夠跟我一塊兒探討,歡迎加羣探討,有flutter—性能優化—移動架構—資深UI工程師 —NDK相關專業人員和視頻教學資料,還有更多面試題等你來拿~ 羣號:661841852

QQ截圖20190510143109.jpg
點擊連接加入羣聊【Android開發行業交流】: jq.qq.com/?_wv=1027&a…
相關文章
相關標籤/搜索