【HenCoder Android 開發進階】自定義 View 1-7:屬性動畫(進階篇)

扔物線發表於2017-09-11

這期是 HenCoder 自定義繪製的第 1-7 期:屬性動畫(進階篇)

屬性動畫的上手篇在這裡:
HenCoder Android 開發進階:自定義 View 1-6 屬性動畫(上手篇)

如果你沒聽說過 HenCoder,可以先看看這個:
HenCoder:給高階 Android 工程師的進階手冊

簡介

上期的內容,對於大多數簡單的屬性動畫場景已經夠用了。這期的內容主要針對兩個方面:

  1. 針對特殊型別的屬性來做屬性動畫;
  2. 針對複雜的屬性關係來做屬性動畫。

TypeEvaluator

關於 ObjectAnimator,上期講到可以用 ofInt() 來做整數的屬性動畫和用 ofFloat() 來做小數的屬性動畫。這兩種屬性型別是屬性動畫最常用的兩種,不過在實際的開發中,可以做屬相動畫的型別還是有其他的一些型別。當需要對其他型別來做屬性動畫的時候,就需要用到 TypeEvaluator 了。

關於 TypeEvaluator 是什麼和怎麼用,先看一下下面的視訊吧:

如果你是手機開啟的,可以點這裡去 B 站看。

ArgbEvaluator

如視訊中的例子,TypeEvaluator 最經典的用法是使用 ArgbEvaluator 來做顏色漸變的動畫。

ObjectAnimator animator = ObjectAnimator.ofInt(view, "color", 0xffff0000, 0xff00ff00);
animator.setEvaluator(new ArgbEvaluator());
animator.start();複製程式碼

另外,在 Android 5.0 (API 21) 加入了新的方法 ofArgb(),所以如果你的 minSdk 大於或者等於 21(哈哈哈哈哈哈哈哈),你可以直接用下面這種方式:

ObjectAnimator animator = ObjectAnimator.ofArgb(view, "color", 0xffff0000, 0xff00ff00);
animator.start();複製程式碼

自定義 Evaluator

如果你對 ArgbEvaluator 的效果不滿意,或者你由於別的什麼原因希望寫一個自定義的 TypeEvaluator,你可以這樣寫:

// 自定義 HslEvaluator
private class HsvEvaluator implements TypeEvaluator<Integer> {
   float[] startHsv = new float[3];
   float[] endHsv = new float[3];
   float[] outHsv = new float[3];

   @Override
   public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
       // 把 ARGB 轉換成 HSV
       Color.colorToHSV(startValue, startHsv);
       Color.colorToHSV(endValue, endHsv);

       // 計算當前動畫完成度(fraction)所對應的顏色值
       if (endHsv[0] - startHsv[0] > 180) {
           endHsv[0] -= 360;
       } else if (endHsv[0] - startHsv[0] < -180) {
           endHsv[0] += 360;
       }
       outHsv[0] = startHsv[0] + (endHsv[0] - startHsv[0]) * fraction;
       if (outHsv[0] > 360) {
           outHsv[0] -= 360;
       } else if (outHsv[0] < 0) {
           outHsv[0] += 360;
       }
       outHsv[1] = startHsv[1] + (endHsv[1] - startHsv[1]) * fraction;
       outHsv[2] = startHsv[2] + (endHsv[2] - startHsv[2]) * fraction;

       // 計算當前動畫完成度(fraction)所對應的透明度
       int alpha = startValue >> 24 + (int) ((endValue >> 24 - startValue >> 24) * fraction);

       // 把 HSV 轉換回 ARGB 返回
       return Color.HSVToColor(alpha, outHsv);
   }
}

ObjectAnimator animator = ObjectAnimator.ofInt(view, "color", 0xff00ff00);
// 使用自定義的 HslEvaluator
animator.setEvaluator(new HsvEvaluator());
animator.start();複製程式碼

ofObject()

藉助於 TypeEvaluator,屬性動畫就可以通過 ofObject() 來對不限定型別的屬性做動畫了。方式很簡單:

  1. 為目標屬性寫一個自定義的 TypeEvaluator
  2. 使用 ofObject() 來建立 Animator,並把自定義的 TypeEvaluator 作為引數填入
private class PointFEvaluator implements TypeEvaluator<PointF> {
   PointF newPoint = new PointF();

   @Override
   public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
       float x = startValue.x + (fraction * (endValue.x - startValue.x));
       float y = startValue.y + (fraction * (endValue.y - startValue.y));

       newPoint.set(x, y);

       return newPoint;
   }
}

ObjectAnimator animator = ObjectAnimator.ofObject(view, "position",
        new PointFEvaluator(), new PointF(0, 0), new PointF(1, 1));
animator.start();複製程式碼

另外在 API 21 中,已經自帶了 PointFEvaluator 這個類,所以如果你的 minSdk 大於或者等於 21(哈哈哈哈哈哈哈哈),上面這個類你就不用寫了,直接用就行了。

ofMultiInt() ofMultiFloat()

在 API 引入的新的方法還有 ofMultiInt()ofMultiFloat() 等,用法也很簡單,不過實用性就低了一些。你有興趣的話可以去做一下了解,這裡不在多做介紹。

以上這些就是對 TypeEvaluator 的介紹。它的作用是讓你可以對同樣的屬性有不同的解析方式,對本來無法解析的屬性也可以打造出你需要的解析方式。有了 TypeEvaluator,你的屬性動畫就有了更大的靈活性,從而有了無限的可能。

TypeEvaluator 是本期的第一部分內容:針對特殊的屬性來做屬性動畫,它可以讓你「做到本來做不到的動畫」。接下來是本期的第二部分內容:針對複雜的屬性關係來做動畫,它可以讓你「能做到的動畫做起來更簡單」。

PropertyValuesHolder 同一個動畫中改變多個屬性

很多時候,你在同一個動畫中會需要改變多個屬性,例如在改變透明度的同時改變尺寸。如果使用 ViewPropertyAnimator,你可以直接用連寫的方式來在一個動畫中同時改變多個屬性:

view.animate()
        .scaleX(1)
        .scaleY(1)
        .alpha(1);複製程式碼

而對於 ObjectAnimator,是不能這麼用的。不過你可以使用 PropertyValuesHolder 來同時在一個動畫中改變多個屬性。

PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("scaleX", 1);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("scaleY", 1);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("alpha", 1);

ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder1, holder2, holder3)
animator.start();複製程式碼

PropertyValuesHolder 的意思從名字可以看出來,它是一個屬性值的批量存放地。所以你如果有多個屬性需要修改,可以把它們放在不同的 PropertyValuesHolder 中,然後使用 ofPropertyValuesHolder() 統一放進 Animator。這樣你就不用為每個屬性單獨建立一個 Animator 分別執行了。

AnimatorSet 多個動畫配合執行

有的時候,你不止需要在一個動畫中改變多個屬性,還會需要多個動畫配合工作,比如,在內容的大小從 0 放大到 100% 大小後開始移動。這種情況使用 PropertyValuesHolder 是不行的,因為這些屬性如果放在同一個動畫中,需要共享動畫的開始時間、結束時間、Interpolator 等等一系列的設定,這樣就不能有先後次序地執行動畫了。

這就需要用到 AnimatorSet 了。

ObjectAnimator animator1 = ObjectAnimator.ofFloat(...);
animator1.setInterpolator(new LinearInterpolator());
ObjectAnimator animator2 = ObjectAnimator.ofInt(...);
animator2.setInterpolator(new DecelerateInterpolator());

AnimatorSet animatorSet = new AnimatorSet();
// 兩個動畫依次執行
animatorSet.playSequentially(animator1, animator2);
animatorSet.start();複製程式碼

使用 playSequentially(),就可以讓兩個動畫依次播放,而不用為它們設定監聽器來手動為他們監管協作。

AnimatorSet 還可以這麼用:

// 兩個動畫同時執行
animatorSet.playTogether(animator1, animator2);
animatorSet.start();複製程式碼

以及這麼用:

// 使用 AnimatorSet.play(animatorA).with/before/after(animatorB)
// 的方式來精確配置各個 Animator 之間的關係
animatorSet.play(animator1).with(animator2);
animatorSet.play(animator1).before(animator2);
animatorSet.play(animator1).after(animator2);
animatorSet.start();複製程式碼

有了 AnimatorSet ,你就可以對多個 Animator 進行統一規劃和管理,讓它們按照要求的順序來工作。它的使用比較簡單,具體的用法我寫在講義裡,你可以看一下。

PropertyValuesHolders.ofKeyframe() 把同一個屬性拆分

除了合併多個屬性和調配多個動畫,你還可以在 PropertyValuesHolder 的基礎上更進一步,通過設定 Keyframe (關鍵幀),把同一個動畫屬性拆分成多個階段。例如,你可以讓一個進度增加到 100% 後再「反彈」回來。

// 在 0% 處開始
Keyframe keyframe1 = Keyframe.ofFloat(0, 0);
// 時間經過 50% 的時候,動畫完成度 100%
Keyframe keyframe2 = Keyframe.ofFloat(0.5f, 100);
// 時間見過 100% 的時候,動畫完成度倒退到 80%,即反彈 20%
Keyframe keyframe3 = Keyframe.ofFloat(1, 80);
PropertyValuesHolder holder = PropertyValuesHolder.ofKeyframe("progress", keyframe1, keyframe2, keyframe3);

ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, holder);
animator.start();複製程式碼

第二部分,「關於複雜的屬性關係來做動畫」,就這麼三種:

  1. 使用 PropertyValuesHolder 來對多個屬性同時做動畫;
  2. 使用 AnimatorSet 來同時管理調配多個動畫;
  3. PropertyValuesHolder 的進階使用:使用 PropertyValuesHolder.ofKeyframe() 來把一個屬性拆分成多段,執行更加精細的屬性動畫。

ValueAnimator 最基本的輪子

額外簡單說一下 ValuesAnimator。很多時候,你用不到它,只是在你使用一些第三方庫的控制元件,而你想要做動畫的屬性卻沒有 setter / getter 方法的時候,會需要用到它。

除了 ViewPropertyAnimator 和 ObjectAnimator,還有第三個選擇是 ValueAnimator。ValueAnimator 並不常用,因為它的功能太基礎了。ValueAnimator 是 ObjectAnimator 的父類,實際上,ValueAnimator 就是一個不能指定目標物件版本的 ObjectAnimator。ObjectAnimator 是自動呼叫目標物件的 setter 方法來更新目標屬性的值,以及很多的時候還會以此來改變目標物件的 UI,而 ValueAnimator 只是通過漸變的方式來改變一個獨立的資料,這個資料不是屬於某個物件的,至於在資料更新後要做什麼事,全都由你來定,你可以依然是去呼叫某個物件的 setter 方法(別這麼為難自己),也可以做其他的事,不管要做什麼,都是要你自己來寫的,ValueAnimator 不會幫你做。功能最少、最不方便,但有時也是束縛最少、最靈活。比如有的時候,你要給一個第三方控制元件做動畫,你需要更新的那個屬性沒有 setter 方法,只能直接修改,這樣的話 ObjectAnimator 就不靈了啊。怎麼辦?這個時候你就可以用 ValueAnimator,在它的 onUpdate() 裡面更新這個屬性的值,並且手動呼叫 invalidate()。

所以你看,ViewPropertyAnimator、ObjectAnimator、ValueAnimator 這三種 Animator,它們其實是一種遞進的關係:從左到右依次變得更加難用,也更加靈活。但我要說明一下,它們的效能是一樣的,因為 ViewPropertyAnimator 和 ObjectAnimator 的內部實現其實都是 ValueAnimator,ObjectAnimator 更是本來就是 ValueAnimator 的子類,它們三個的效能並沒有差別。它們的差別只是使用的便捷性以及功能的靈活性。所以在實際使用時候的選擇,只要遵循一個原則就行:儘量用簡單的。能用 View.animate() 實現就不用 ObjectAnimator,能用 ObjectAnimator 就不用 ValueAnimator。

練習專案

為了避免轉頭就忘,強烈建議你趁熱打鐵,做一下這個練習專案:HenCoderPracticeDraw7

下期預告

下期是繪製部分的最後一期:硬體加速相關。內容會比較少,也會比較簡單。

繪製部分終於要完了哎,期待嗎?

覺得贊?

如果你看完覺得有收穫,把文章轉發到你的微博、微信群、朋友圈、公眾號,讓其他需要的人也看到吧。

相關文章