ViewPropertyAnimator屬性動畫

使用 ObjectAnimator
通過使用 3.0 中引入的 ObjectAnimator,你可以通過幾行代碼實現 View 的任意一個屬性動畫。創建一個 Animator,設置任意一個可選的屬性以及一些可選參數(比如 duration 和 repetition 參數),然後調用 start() 方法。例如,想要讓一個叫做 myView 的對象做淡出動畫,你可以這樣做:

ObjectAnimator.ofFloat(myView, "alpha", 0f).start();

這顯然一點都不難,編寫代碼不難,理解起來也不難。你通過想要做動畫的對象的信息、想要做動畫的屬性的名字以及動畫的屬性結束值。非常簡單。

但是在我們看來它還可以被優化。尤其是,既然 View 的這些屬性會非常頻繁的被調用做動畫,那麼我們可以引入一些新的 API 使得讓這些屬性做動畫更加的簡單和易讀(readable)。與此同時,我們還想提高這些屬性做動畫的性能特徵。第二個原因值得被解釋一下,下一段中的內容全都是關於它的。

在 3.0 的動畫系統中,關於 View 的屬性動畫,有三個方面的性能值得提升。其中一個是,我們在一個沒有固定的 “屬性” 概念的語言中做屬性動畫。(One of the elements concerns the mechanism by which we animate properties in a language that has no inherent concept of “properties”.)另一個性能問題是關於同時對多個屬性做動畫的。當做淡出動畫的時候,你只需要對 alpha 屬性做動畫。但是如果這個 View 正在移動,那它的 x 和 y (或者 translationX 和 translationY)屬性也可能同時在做動畫。還有其他的情況使得多個屬性動畫在同時執行。如果我們知道有多個屬性動畫在同時執行的畫,那麼這裏就會有相當大的性能提升空間。

Android 運行時對於“屬性”並沒有概念,所以 ObjectAnimator 通過一種技術來將 表示屬性名字的字符串 轉換成 對目標對象執行 setter 方法。例如,在 View 類中 alpha 字符換會被轉換成對 setAlpha() 方法的引用。這個功能的實現要麼通過反射實現,要麼通過 JNI;這兩者都很可靠,但是都會有一些額外的開銷。但是據我們所知,對於一些對象和屬性,比如 View 的屬性,我們應該可以做到更好。通過了解一些 API 和做動畫的屬性的相關信息,我們可以直接給相應的屬性賦值,從而避免反射和 JNI 帶來的額外開銷。

另外一部分的額外開銷是 Animator 自身。儘管所有的動畫都共享同一個計時機制因此不會因爲計時而產生額外開銷,但是它們是獨立對不同的屬性執行同樣的操作。如果我們提前知道我們正在對多個屬性同時做動畫的話,我們可以把這些動畫結合起來(combine)。在新的動畫系統裏,其中一個解決方式就是使用 PropertyValueHolder。這個類允許你在同一個 Animator 中對多個屬性做動畫,這樣可以省去很多對每個單獨的 Animator 的開銷(per-Animator overhead)。但是種方法會需要更多代碼,這使得一個簡單的操作變得複雜。這裏有一個新的解決途徑,它允許我們以一個更加簡單易讀易寫的方式來將多個動畫結合起來。

最後,View 的每一個屬性都會執行一些操作來確保它和它的父容器可以在合適的時候重繪(ensure proper invalidation)。例如,當對一個 View 做 x 軸上的平移的時候會 invalidate 它之前所在的位置和它當前所在的位置,以此來告訴它的父容器需要重繪哪些部分。同樣的,做 y 軸方向的平移時也會 invalidate 它之前和當前的位置。如果這兩個屬性同時做動畫的話,就會有冗餘的操作,因爲如果我們知道有多個屬性動畫正在進行這些 invalidation 可以結合在一起的。ViewPropertyAnimator 就是用來做這件事的。

介紹:ViewPropertyAnimator
ViewPropertyAnimator 提供了一種可以使多個屬性同時做動畫的簡單方法,而且它在內部只使用一個 Animator。當它計算完這些屬性的值之後,它直接把那些值賦給目標 View 並 invalidate 那個對象,而它完成這些的方式比普通的 ObjectAnimator 更加高效。

廢話少說:讓我們看看代碼。對於一個我們之前見過的 View 淡出動畫,如果使用 ViewPropertyAnimator 的話應該是這樣:

myView.animate().alpha(0);

很好,它非常簡短而且可讀性非常好。而且它非常容易將多個動畫結合起來。例如,我們可以同時移動這個 View 的 x 值和 y 值到 (500, 500) 這個位置:

myView.animate().x(500).y(500);

在這句代碼中有以下幾個需要注意的地方:

1、animate():整個系統從調用 View 的這個叫做 animate() 的新方法開始。這個方法會返回一個 ViewPropertyAnimator 對象,你可以通過調用這個對象的方法來設置需要做動畫的屬性。
2、自動開始:注意我們沒有顯式調用過 start() 方法。在新的 API 中,啓動動畫是隱式的。當你聲明完,動畫就開始了。同時開始。這裏有一個細節,就是這些動畫實際上會在下一次界面刷新的時候啓動,ViewPropertyAnimator 正是通過這個機制來將所有的動畫結合在一起的。如果你繼續聲明動畫,它就會繼續將這些動畫添加到將在下一幀開始的動畫的列表中。而當你聲明完畢並結束對 UI 線程的控制之後,事件隊列機制開始起作用(kicks in)動畫也就開始了。
3、流暢(Fluent):ViewPropertyAnimator 擁有一個流暢的接口(Fluent interface),它允許你將多個方法調用很自然地串在一起並把一個多屬性的動畫寫成一行代碼。所有的調用(比如 x()、y())都會返回一個 ViewPropertyAnimator 實例,所以你可以把其他的方法調用串在一起。

即使是在一個簡單的 alpha 動畫上,這種新的解決途徑也是性能完勝的。 ViewPropertyAnimator 並不使用反射或者 JNI 技術;例如,alpha() 方法是在每一幀直接改變 View 對象的 alpha 屬性(field)值。

ViewPropertyAnimator 的另一個性能上的勝利來自於它可以將多個動畫結合起來。爲此我們來看另一個例子。

當你在屏幕上移動一個 View 的時候,你可能會同時移動它的 x 位置和 y 位置。例如,下面這個動畫移動 myView 的 x 值和 y 值到50和100:

ObjectAnimator animX = ObjectAnimator.ofFloat(myView, "x", 50f);
ObjectAnimator animY = ObjectAnimator.ofFloat(myView, "y", 100f);
AnimatorSet animSetXY = new AnimatorSet();
animSetXY.playTogether(animX, animY);
animSetXY.start();

上面這段代碼創建了兩個動畫,並通過 AnimatorSet 來是他們同時播放。這種方式在處理上存在着額外的開銷,因爲它需要額外創建一個 AnimatorSet 並且在同時執行兩個動畫。這裏有另一種方法,使用 PropertyValuesHolder 來把多個屬性的動畫全部放到一個 Animator 中:

PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 50f);
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 100f);
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvyY).start();

這種解決方案避免了多個 Animator 的額外開銷,在 ViewPropertyAnimator 之前它是一個正確的方法。並且代碼看起來不是太糟。但是使用 ViewPropertyAnimator 的話,它會更加簡單:

myView.animate().x(50f).y(100f);

再次重複一次,這段代碼更加簡單也更加易讀。而且它和上面使用 PropertyValuesHolder 的例子一樣具有單一 Animator 的優點,因爲 ViewPropertyAnimator 在內部只運行一個 Animator 來使所有指定的屬性做動畫。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章