這期是 HenCoder 自定義繪製的第 1-7 期:屬性動畫(進階篇)
屬性動畫的上手篇在這裡:
HenCoder Android 開發進階:自定義 View 1-6 屬性動畫(上手篇)
如果你沒聽說過 HenCoder,可以先看看這個:
HenCoder:給高階 Android 工程師的進階手冊
簡介
上期的內容,對於大多數簡單的屬性動畫場景已經夠用了。這期的內容主要針對兩個方面:
- 針對特殊型別的屬性來做屬性動畫;
- 針對複雜的屬性關係來做屬性動畫。
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()
來對不限定型別的屬性做動畫了。方式很簡單:
- 為目標屬性寫一個自定義的
TypeEvaluator
- 使用
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();複製程式碼
第二部分,「關於複雜的屬性關係來做動畫」,就這麼三種:
- 使用
PropertyValuesHolder
來對多個屬性同時做動畫; - 使用
AnimatorSet
來同時管理調配多個動畫; 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
下期預告
下期是繪製部分的最後一期:硬體加速相關。內容會比較少,也會比較簡單。
繪製部分終於要完了哎,期待嗎?
覺得贊?
如果你看完覺得有收穫,把文章轉發到你的微博、微信群、朋友圈、公眾號,讓其他需要的人也看到吧。