前面學習的內容:
Android自定義View(一) -- 初識
Android自定義View(二) -- Paint詳解
Android自定義View(三) -- drawText()
Android自定義View(四) -- Canvas
Android自定義View(五) -- 繪制順序
今天繼續學習Android自定義View第六篇內容 屬性動畫(上)
本文計劃根據HenCoder系列文章進行學習,所以代碼風格及博文素材可能會摘自其中。
簡介
Android 里動畫是有一些分類的:動畫可以分為兩類:Animation 和 Transition;
其中 Animation 又可以再分為 View Animation 和 Property Animation 兩類:
View Animation 是純粹基于 framework 的繪制轉變,比較簡單,如果你有興趣的話可以上網搜一下它的用法;
Property Animation,屬性動畫,這是在 Android 3.0 開始引入的新的動畫形式,不過說它新只是相對的,它已經有好幾年的歷史了,而且現在的項目中的動畫 99% 都是用的它,極少再用到 View Animation 了。屬性動畫不僅可以使用自帶的 API 來實現最常用的動畫,而且通過自定義 View 的方式來做出定制化的動畫。
除了這兩種 Animation,還有一類動畫是 Transition。 Transition 這個詞的本意是轉換,在 Android 里指的是切換界面時的動畫效果,這個在邏輯上要復雜一點,不過它的重點是在于切換而不是動畫,所以它也不是這次要討論的內容。這次的內容只專注于一點:
Property Animation(屬性動畫)。在這一期我就基于前面幾期講過的自定義繪制,這一個自定義 View 的分支,來說一下屬性動畫的原理以及使用。
講解
學習之前可以先看看視頻,對動畫有個基本認識
ViewPropertyAnimator
使用方式:View.animate() 后跟 translationX() 等方法,動畫會自動執行。
view.animate()
.translationX(300)
.start();
具體可以跟的方法以及方法所對應的 View
中的實際操作的方法如下圖所示:
從圖中可以看到, View
的每個方法都對應了 ViewPropertyAnimator
的兩個方法,其中一個是帶有 -By
后綴的,例如,View.setTranslationX()
對應了 ViewPropertyAnimator.translationX()
和 ViewPropertyAnimator.translationXBy()
這兩個方法。其中帶有 -By()
后綴的是增量版本的方法,例如,translationX(100)
表示用動畫把 View
的 translationX
值漸變為 100
,而 translationXBy(100)
則表示用動畫把 View
的 translationX
值漸變地增加 100
。l
因為電腦暫無法錄制GIF,所以接下來內容摘自原博客Hencoder,可以去原博客學習。
使用方式:
- 如果是自定義控件,需要添加
setter
/getter
方法; - 用
ObjectAnimator.ofXXX()
創建ObjectAnimator
對象; - 用
start()
方法執行動畫。
public class SportsView extends View {
float progress = 0;
......
// 創建 getter 方法
public float getProgress() {
return progress;
}
// 創建 setter 方法
public void setProgress(float progress) {
this.progress = progress;
invalidate();
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
......
canvas.drawArc(arcRectF, 135, progress * 2.7f, false, paint);
......
}
}
......
// 創建 ObjectAnimator 對象
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "progress", 0, 65);
// 執行動畫
animator.start();
通用功能
1. setDuration(int duration) 設置動畫時長
單位是毫秒。
// imageView1: 500 毫秒
imageView1.animate()
.translationX(500)
.setDuration(500);
// imageView2: 2 秒
ObjectAnimator animator = ObjectAnimator.ofFloat(
imageView2, "translationX", 500);
animator.setDuration(2000);
animator.start();
2. setInterpolator(Interpolator interpolator) 設置 Interpolator
視頻里已經說了, Interpolator
其實就是速度設置器。你在參數里填入不同的 Interpolator
,動畫就會以不同的速度模型來執行。
// imageView1: 線性 Interpolator,勻速
imageView1.animate()
.translationX(500)
.setInterpolator(new LinearInterpolator());
// imageView: 帶施法前搖和回彈的 Interpolator
ObjectAnimator animator = ObjectAnimator.ofFloat(
imageView2, "translationX", 500);
animator.setInterpolator(new AnticipateOvershootInterpolator());
animator.start();
簡單介紹一下每一個 Interpolator
。
AccelerateDecelerateInterpolator
先加速再減速。這是默認的 Interpolator
,也就是說如果你不設置的話,那么動畫將會使用這個 Interpolator
。
視頻里已經說過了,這個是一種最符合現實中物體運動的 Interpolator
,它的動畫效果看起來就像是物體從速度為 0 開始逐漸加速,然后再逐漸減速直到 0 的運動。它的速度 / 時間曲線以及動畫完成度 / 時間曲線都是一條正弦 / 余弦曲線(這句話看完就忘掉就行,沒用)。具體的效果如下:
好像不太看得出來加速減速過程?你就將就著看吧,畢竟 gif 不是視頻,要啥自行車啊。
用途:就像上面說的,它是一種最符合物理世界的模型,所以如果你要做的是最簡單的狀態變化(位移、放縮、旋轉等等),那么一般不用設置 Interpolator
,就用這個默認的最好。
LinearInterpolator
勻速。
勻速就不用解釋了吧?直接上效果:
AccelerateInterpolator
持續加速。
在整個動畫過程中,一直在加速,直到動畫結束的一瞬間,直接停止。
別看見它加速驟停就覺得這是個神經病模型哦,它很有用的。它主要用在離場效果中,比如某個物體從界面中飛離,就可以用這種效果。它給人的感覺就會是「這貨從零起步,加速飛走了」。到了最后動畫驟停的時候,物體已經飛出用戶視野,看不到了,所以他們是并不會察覺到這個驟停的。
DecelerateInterpolator
持續減速直到 0。
動畫開始的時候是最高速度,然后在動畫過程中逐漸減速,直到動畫結束的時候恰好減速到 0。
它的效果和上面這個 AccelerateInterpolator
相反,適用場景也和它相反:它主要用于入場效果,比如某個物體從界面的外部飛入界面后停在某處。它給人的感覺會是「咦飛進來個東西,讓我仔細看看,哦原來是 XXX」。
AnticipateInterpolator
先回拉一下再進行正常動畫軌跡。效果看起來有點像投擲物體或跳躍等動作前的蓄力。
如果是圖中這樣的平移動畫,那么就是位置上的回拉;如果是放大動畫,那么就是先縮小一下再放大;其他類型的動畫同理。
這個 Interpolator
就有點耍花樣了。沒有通用的適用場景,根據具體需求和設計師的偏好而定。
OvershootInterpolator
動畫會超過目標值一些,然后再彈回來。效果看起來有點像你一屁股坐在沙發上后又被彈起來一點的感覺。
和 AnticipateInterpolator
一樣,這是個耍花樣的 Interpolator
,沒有通用的適用場景。
AnticipateOvershootInterpolator
上面這兩個的結合版:開始前回拉,最后超過一些然后回彈。
依然耍花樣,不多解釋。
BounceInterpolator
在目標值處彈跳。有點像玻璃球掉在地板上的效果。
耍花樣 +1。
CycleInterpolator
這個也是一個正弦 / 余弦曲線,不過它和 AccelerateDecelerateInterpolator
的區別是,它可以自定義曲線的周期,所以動畫可以不到終點就結束,也可以到達終點后回彈,回彈的次數由曲線的周期決定,曲線的周期由 CycleInterpolator()
構造方法的參數決定。
參數為 0.5f:
參數為 2f:
PathInterpolator
自定義動畫完成度 / 時間完成度曲線。
用這個 Interpolator
你可以定制出任何你想要的速度模型。定制的方式是使用一個 Path
對象來繪制出你要的動畫完成度 / 時間完成度曲線。例如:
Path interpolatorPath = new Path();
...
// 勻速
interpolatorPath.lineTo(1, 1);
Path interpolatorPath = new Path();
...
// 先以「動畫完成度 : 時間完成度 = 1 : 1」的速度勻速運行 25%
interpolatorPath.lineTo(0.25f, 0.25f);
// 然后瞬間跳躍到 150% 的動畫完成度
interpolatorPath.moveTo(0.25f, 1.5f);
// 再勻速倒車,返回到目標點
interpolatorPath.lineTo(1, 1);
你根據需求,繪制出自己需要的 Path
,就能定制出你要的速度模型。
不過要注意,這條 Path
描述的其實是一個 y = f(x) (0 ≤ x ≤ 1)
(y 為動畫完成度,x 為時間完成度)的曲線,所以同一段時間完成度上不能有兩段不同的動畫完成度(這個好理解吧?因為內容不能出現分身術呀),而且每一個時間完成度的點上都必須要有對應的動畫完成度(因為內容不能在某段時間段內消失呀)。所以,下面這樣的 Path
是非法的,會導致程序 FC:
出現重復的動畫完成度,即動畫內容出現「分身」——程序 FC
有一段時間完成度沒有對應的動畫完成度,即動畫出現「中斷」——程序 FC
除了上面的這些,Android 5.0 (API 21)引入了三個新的 Interpolator
模型,并把它們加入了 support v4 包中。這三個新的 Interpolator
每個都和之前的某個已有的 Interpolator
規則相似,只有略微的區別。
FastOutLinearInInterpolator
加速運動。
這個 Interpolator
的作用你不能看它的名字,一會兒 fast 一會兒 linear 的,完全看不懂。其實它和 AccelerateInterpolator
一樣,都是一個持續加速的運動路線。只不過 FastOutLinearInInterpolator
的曲線公式是用的貝塞爾曲線,而 AccelerateInterpolator
用的是指數曲線。具體來說,它倆最主要的區別是 FastOutLinearInInterpolator
的初始階段加速度比 AccelerateInterpolator
要快一些。
FastOutLinearInInterpolator
:
AccelerateInterpolator
:
能看出它倆的區別嗎?
能看出來就怪了。這倆的速度模型幾乎就是一樣的,不信我把它們的動畫完成度 / 時間完成度曲線放在一起給你看:
看到了嗎?兩條線幾乎是一致的,只是紅線比綠線更早地到達了較高的斜率,這說明在初始階段,FastOutLinearInInterpolator
的加速度比 AccelerateInterpolator
更高。
那么這意味著什么呢?
意味個毛。實際上,這點區別,在實際應用中用戶根本察覺不出來。而且,AccelerateInterpolator
還可以在構造方法中調節變速系數,分分鐘調節到和 FastOutLinearInInterpolator
(幾乎)一模一樣。所以你在使用加速模型的時候,這兩個選哪個都一樣,沒區別的。
那么既然都一樣,我做這么多對比,講這么些干什么呢?
因為我得讓你了解。它倆雖然「用起來沒區別」,但這是基于我對它足夠了解所做出的判斷,可我如果直接甩給你一句「它倆沒區別,想用誰用誰,少廢話別問那么多」,你心里肯定會有一大堆疑問,在開發時用到它們的時候也會畏畏縮縮心里打鼓的,對吧?
FastOutSlowInInterpolator
先加速再減速。
同樣也是先加速再減速的還有前面說過的 AccelerateDecelerateInterpolator
,不過它們的效果是明顯不一樣的。FastOutSlowInInterpolator
用的是貝塞爾曲線,AccelerateDecelerateInterpolator
用的是正弦 / 余弦曲線。具體來講, FastOutSlowInInterpolator
的前期加速度要快得多。
FastOutSlowInInterpolator
:
AccelerateDecelerateInterpolator
:
不論是從動圖還是從曲線都可以看出,這二者比起來,FastOutSlowInInterpolator
的前期加速更猛一些,后期的減速過程的也減得更迅速。用更直觀一點的表達就是,AccelerateDecelerateInterpolator
像是物體的自我移動,而 FastOutSlowInInterpolator
則看起來像有一股強大的外力「推」著它加速,在接近目標值之后又「拽」著它減速。總之,FastOutSlowInterpolator
看起來有一點「著急」的感覺。
二者曲線對比圖:
LinearOutSlowInInterpolator
持續減速。
它和 DecelerateInterpolator
比起來,同為減速曲線,主要區別在于 LinearOutSlowInInterpolator
的初始速度更高。對于人眼的實際感覺,區別其實也不大,不過還是能看出來一些的。
LinearOutSlowInInterpolator
:
DecelerateInterpolator
:
二者曲線對比:
對于所有 Interpolator
的介紹就到這里。這些 Interpolator
,有的較為常用且有通用的使用場景,有的需要你自己來根據情況而定。把它們了解清楚了,對于制作出觀感舒服的動畫很有好處。
3. 設置監聽器
給動畫設置監聽器,可以在關鍵時刻得到反饋,從而及時做出合適的操作,例如在動畫的屬性更新時同步更新其他數據,或者在動畫結束后回收資源等。
設置監聽器的方法, ViewPropertyAnimator
和 ObjectAnimator
略微不一樣: ViewPropertyAnimator
用的是 setListener()
和 setUpdateListener()
方法,可以設置一個監聽器,要移除監聽器時通過 set[Update]Listener(null)
填 null 值來移除;而 ObjectAnimator
則是用 addListener()
和 addUpdateListener()
來添加一個或多個監聽器,移除監聽器則是通過 remove[Update]Listener()
來指定移除對象。
另外,由于 ObjectAnimator
支持使用 pause()
方法暫停,所以它還多了一個 addPauseListener()
/ removePauseListener()
的支持;而 ViewPropertyAnimator
則獨有 withStartAction()
和 withEndAction()
方法,可以設置一次性的動畫開始或結束的監聽。
3.1 ViewPropertyAnimator.setListener() / ObjectAnimator.addListener()
這兩個方法的名稱不一樣,可以設置的監聽器數量也不一樣,但它們的參數類型都是 AnimatorListener
,所以本質上其實都是一樣的。 AnimatorListener
共有 4 個回調方法:
3.1.1 onAnimationStart(Animator animation)
當動畫開始執行時,這個方法被調用。
3.1.2 onAnimationEnd(Animator animation)
當動畫結束時,這個方法被調用。
3.1.3 onAnimationCancel(Animator animation)
當動畫被通過 cancel()
方法取消時,這個方法被調用。
需要說明一下的是,就算動畫被取消,onAnimationEnd()
也會被調用。所以當動畫被取消時,如果設置了 AnimatorListener
,那么 onAnimationCancel()
和 onAnimationEnd()
都會被調用。onAnimationCancel()
會先于 onAnimationEnd()
被調用。
3.1.4 onAnimationRepeat(Animator animation)
當動畫通過 setRepeatMode()
/ setRepeatCount()
或 repeat()
方法重復執行時,這個方法被調用。
由于 ViewPropertyAnimator
不支持重復,所以這個方法對 ViewPropertyAnimator
相當于無效。
3.2 ViewPropertyAnimator.setUpdateListener() / ObjectAnimator.addUpdateListener()
和上面 3.1 的兩個方法一樣,這兩個方法雖然名稱和可設置的監聽器數量不一樣,但本質其實都一樣的,它們的參數都是 AnimatorUpdateListener
。它只有一個回調方法:onAnimationUpdate(ValueAnimator animation)
。
3.2.1 onAnimationUpdate(ValueAnimator animation)
當動畫的屬性更新時(不嚴謹的說,即每過 10 毫秒,動畫的完成度更新時),這個方法被調用。
方法的參數是一個 ValueAnimator
,ValueAnimator
是 ObjectAnimator
的父類,也是 ViewPropertyAnimator
的內部實現,所以這個參數其實就是 ViewPropertyAnimator
內部的那個 ValueAnimator
,或者對于 ObjectAnimator
來說就是它自己本身。
ValueAnimator
有很多方法可以用,它可以查看當前的動畫完成度、當前的屬性值等等。不過 ValueAnimator
是下一期才講的內容,所以這期就不多說了。
3.3 ObjectAnimator.addPauseListener()
由于 ObjectAnimator.pause()
是下期的內容,所以這個方法在這期就不講了。當然,如果你有興趣的話,現在就了解一下也可以。
3.3 ViewPropertyAnimator.withStartAction/EndAction()
這兩個方法是 ViewPropertyAnimator
的獨有方法。它們和 set/addListener()
中回調的 onAnimationStart()
/ onAnimationEnd()
相比起來的不同主要有兩點:
withStartAction()
/withEndAction()
是一次性的,在動畫執行結束后就自動棄掉了,就算之后再重用ViewPropertyAnimator
來做別的動畫,用它們設置的回調也不會再被調用。而set/addListener()
所設置的AnimatorListener
是持續有效的,當動畫重復執行時,回調總會被調用。withEndAction()
設置的回調只有在動畫正常結束時才會被調用,而在動畫被取消時不會被執行。這點和AnimatorListener.onAnimationEnd()
的行為是不一致的。
關于監聽器,就說到這里。本期內容的講義部分也到此結束。