前面學(xué)習(xí)的內(nèi)容:
Android自定義View(一) -- 初識(shí)
Android自定義View(二) -- Paint詳解
Android自定義View(三) -- drawText()
Android自定義View(四) -- Canvas
Android自定義View(五) -- 繪制順序
今天繼續(xù)學(xué)習(xí)Android自定義View第六篇內(nèi)容 屬性動(dòng)畫(上)
本文計(jì)劃根據(jù)HenCoder系列文章進(jìn)行學(xué)習(xí),所以代碼風(fēng)格及博文素材可能會(huì)摘自其中湖蜕。
簡介
Android 里動(dòng)畫是有一些分類的:動(dòng)畫可以分為兩類:Animation 和 Transition费就;
其中 Animation 又可以再分為 View Animation 和 Property Animation 兩類:
View Animation 是純粹基于 framework 的繪制轉(zhuǎn)變斤寂,比較簡單前标,如果你有興趣的話可以上網(wǎng)搜一下它的用法品抽;
Property Animation墓贿,屬性動(dòng)畫诅蝶,這是在 Android 3.0 開始引入的新的動(dòng)畫形式,不過說它新只是相對(duì)的募壕,它已經(jīng)有好幾年的歷史了调炬,而且現(xiàn)在的項(xiàng)目中的動(dòng)畫 99% 都是用的它,極少再用到 View Animation 了舱馅。屬性動(dòng)畫不僅可以使用自帶的 API 來實(shí)現(xiàn)最常用的動(dòng)畫缰泡,而且通過自定義 View 的方式來做出定制化的動(dòng)畫。
除了這兩種 Animation代嗤,還有一類動(dòng)畫是 Transition棘钞。 Transition 這個(gè)詞的本意是轉(zhuǎn)換,在 Android 里指的是切換界面時(shí)的動(dòng)畫效果干毅,這個(gè)在邏輯上要復(fù)雜一點(diǎn)宜猜,不過它的重點(diǎn)是在于切換而不是動(dòng)畫,所以它也不是這次要討論的內(nèi)容硝逢。這次的內(nèi)容只專注于一點(diǎn):
Property Animation(屬性動(dòng)畫)姨拥。在這一期我就基于前面幾期講過的自定義繪制绅喉,這一個(gè)自定義 View 的分支,來說一下屬性動(dòng)畫的原理以及使用叫乌。
講解
學(xué)習(xí)之前可以先看看視頻柴罐,對(duì)動(dòng)畫有個(gè)基本認(rèn)識(shí)
ViewPropertyAnimator
使用方式:View.animate() 后跟 translationX() 等方法,動(dòng)畫會(huì)自動(dòng)執(zhí)行憨奸。
view.animate()
.translationX(300)
.start();
具體可以跟的方法以及方法所對(duì)應(yīng)的 View
中的實(shí)際操作的方法如下圖所示:
從圖中可以看到革屠, View
的每個(gè)方法都對(duì)應(yīng)了 ViewPropertyAnimator
的兩個(gè)方法,其中一個(gè)是帶有 -By
后綴的排宰,例如似芝,View.setTranslationX()
對(duì)應(yīng)了 ViewPropertyAnimator.translationX()
和 ViewPropertyAnimator.translationXBy()
這兩個(gè)方法。其中帶有 -By()
后綴的是增量版本的方法板甘,例如国觉,translationX(100)
表示用動(dòng)畫把 View
的 translationX
值漸變?yōu)?100
,而 translationXBy(100)
則表示用動(dòng)畫把 View
的 translationX
值漸變地增加 100
虾啦。l
因?yàn)殡娔X暫無法錄制GIF麻诀,所以接下來內(nèi)容摘自原博客Hencoder,可以去原博客學(xué)習(xí)。
使用方式:
- 如果是自定義控件傲醉,需要添加
setter
/getter
方法蝇闭; - 用
ObjectAnimator.ofXXX()
創(chuàng)建ObjectAnimator
對(duì)象; - 用
start()
方法執(zhí)行動(dòng)畫硬毕。
public class SportsView extends View {
float progress = 0;
......
// 創(chuàng)建 getter 方法
public float getProgress() {
return progress;
}
// 創(chuàng)建 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);
......
}
}
......
// 創(chuàng)建 ObjectAnimator 對(duì)象
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "progress", 0, 65);
// 執(zhí)行動(dòng)畫
animator.start();
通用功能
1. setDuration(int duration) 設(shè)置動(dòng)畫時(shí)長
單位是毫秒呻引。
// 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) 設(shè)置 Interpolator
視頻里已經(jīng)說了, Interpolator
其實(shí)就是速度設(shè)置器吐咳。你在參數(shù)里填入不同的 Interpolator
逻悠,動(dòng)畫就會(huì)以不同的速度模型來執(zhí)行。
// imageView1: 線性 Interpolator韭脊,勻速
imageView1.animate()
.translationX(500)
.setInterpolator(new LinearInterpolator());
// imageView: 帶施法前搖和回彈的 Interpolator
ObjectAnimator animator = ObjectAnimator.ofFloat(
imageView2, "translationX", 500);
animator.setInterpolator(new AnticipateOvershootInterpolator());
animator.start();
簡單介紹一下每一個(gè) Interpolator
童谒。
AccelerateDecelerateInterpolator
先加速再減速。這是默認(rèn)的 Interpolator
沪羔,也就是說如果你不設(shè)置的話饥伊,那么動(dòng)畫將會(huì)使用這個(gè) Interpolator
。
視頻里已經(jīng)說過了蔫饰,這個(gè)是一種最符合現(xiàn)實(shí)中物體運(yùn)動(dòng)的 Interpolator
琅豆,它的動(dòng)畫效果看起來就像是物體從速度為 0 開始逐漸加速,然后再逐漸減速直到 0 的運(yùn)動(dòng)篓吁。它的速度 / 時(shí)間曲線以及動(dòng)畫完成度 / 時(shí)間曲線都是一條正弦 / 余弦曲線(這句話看完就忘掉就行茫因,沒用)。具體的效果如下:
好像不太看得出來加速減速過程杖剪?你就將就著看吧冻押,畢竟 gif 不是視頻驰贷,要啥自行車啊。
用途:就像上面說的翼雀,它是一種最符合物理世界的模型,所以如果你要做的是最簡單的狀態(tài)變化(位移孩擂、放縮狼渊、旋轉(zhuǎn)等等),那么一般不用設(shè)置 Interpolator
类垦,就用這個(gè)默認(rèn)的最好狈邑。
LinearInterpolator
勻速。
勻速就不用解釋了吧蚤认?直接上效果:
AccelerateInterpolator
持續(xù)加速米苹。
在整個(gè)動(dòng)畫過程中,一直在加速砰琢,直到動(dòng)畫結(jié)束的一瞬間蘸嘶,直接停止。
別看見它加速驟停就覺得這是個(gè)神經(jīng)病模型哦陪汽,它很有用的训唱。它主要用在離場效果中,比如某個(gè)物體從界面中飛離挚冤,就可以用這種效果况增。它給人的感覺就會(huì)是「這貨從零起步,加速飛走了」训挡。到了最后動(dòng)畫驟停的時(shí)候澳骤,物體已經(jīng)飛出用戶視野,看不到了澜薄,所以他們是并不會(huì)察覺到這個(gè)驟停的为肮。
DecelerateInterpolator
持續(xù)減速直到 0。
動(dòng)畫開始的時(shí)候是最高速度肤京,然后在動(dòng)畫過程中逐漸減速弥锄,直到動(dòng)畫結(jié)束的時(shí)候恰好減速到 0。
它的效果和上面這個(gè) AccelerateInterpolator
相反蟆沫,適用場景也和它相反:它主要用于入場效果籽暇,比如某個(gè)物體從界面的外部飛入界面后停在某處。它給人的感覺會(huì)是「咦飛進(jìn)來個(gè)東西饭庞,讓我仔細(xì)看看戒悠,哦原來是 XXX」。
AnticipateInterpolator
先回拉一下再進(jìn)行正常動(dòng)畫軌跡舟山。效果看起來有點(diǎn)像投擲物體或跳躍等動(dòng)作前的蓄力绸狐。
如果是圖中這樣的平移動(dòng)畫卤恳,那么就是位置上的回拉;如果是放大動(dòng)畫寒矿,那么就是先縮小一下再放大突琳;其他類型的動(dòng)畫同理。
這個(gè) Interpolator
就有點(diǎn)朔啵花樣了拆融。沒有通用的適用場景,根據(jù)具體需求和設(shè)計(jì)師的偏好而定啊终。
OvershootInterpolator
動(dòng)畫會(huì)超過目標(biāo)值一些镜豹,然后再彈回來。效果看起來有點(diǎn)像你一屁股坐在沙發(fā)上后又被彈起來一點(diǎn)的感覺蓝牲。
和 AnticipateInterpolator
一樣趟脂,這是個(gè)耍花樣的 Interpolator
例衍,沒有通用的適用場景昔期。
AnticipateOvershootInterpolator
上面這兩個(gè)的結(jié)合版:開始前回拉,最后超過一些然后回彈佛玄。
依然苏蚓欤花樣,不多解釋翎嫡。
BounceInterpolator
在目標(biāo)值處彈跳欠动。有點(diǎn)像玻璃球掉在地板上的效果。
嘶笊辏花樣 +1具伍。
CycleInterpolator
這個(gè)也是一個(gè)正弦 / 余弦曲線,不過它和 AccelerateDecelerateInterpolator
的區(qū)別是圈驼,它可以自定義曲線的周期人芽,所以動(dòng)畫可以不到終點(diǎn)就結(jié)束,也可以到達(dá)終點(diǎn)后回彈绩脆,回彈的次數(shù)由曲線的周期決定萤厅,曲線的周期由 CycleInterpolator()
構(gòu)造方法的參數(shù)決定。
參數(shù)為 0.5f:
參數(shù)為 2f:
PathInterpolator
自定義動(dòng)畫完成度 / 時(shí)間完成度曲線靴迫。
用這個(gè) Interpolator
你可以定制出任何你想要的速度模型惕味。定制的方式是使用一個(gè) Path
對(duì)象來繪制出你要的動(dòng)畫完成度 / 時(shí)間完成度曲線。例如:
Path interpolatorPath = new Path();
...
// 勻速
interpolatorPath.lineTo(1, 1);
Path interpolatorPath = new Path();
...
// 先以「動(dòng)畫完成度 : 時(shí)間完成度 = 1 : 1」的速度勻速運(yùn)行 25%
interpolatorPath.lineTo(0.25f, 0.25f);
// 然后瞬間跳躍到 150% 的動(dòng)畫完成度
interpolatorPath.moveTo(0.25f, 1.5f);
// 再勻速倒車玉锌,返回到目標(biāo)點(diǎn)
interpolatorPath.lineTo(1, 1);
你根據(jù)需求名挥,繪制出自己需要的 Path
,就能定制出你要的速度模型主守。
不過要注意禀倔,這條 Path
描述的其實(shí)是一個(gè) y = f(x) (0 ≤ x ≤ 1)
(y 為動(dòng)畫完成度榄融,x 為時(shí)間完成度)的曲線,所以同一段時(shí)間完成度上不能有兩段不同的動(dòng)畫完成度(這個(gè)好理解吧救湖?因?yàn)閮?nèi)容不能出現(xiàn)分身術(shù)呀)愧杯,而且每一個(gè)時(shí)間完成度的點(diǎn)上都必須要有對(duì)應(yīng)的動(dòng)畫完成度(因?yàn)閮?nèi)容不能在某段時(shí)間段內(nèi)消失呀)。所以鞋既,下面這樣的 Path
是非法的力九,會(huì)導(dǎo)致程序 FC:
出現(xiàn)重復(fù)的動(dòng)畫完成度,即動(dòng)畫內(nèi)容出現(xiàn)「分身」——程序 FC
有一段時(shí)間完成度沒有對(duì)應(yīng)的動(dòng)畫完成度涛救,即動(dòng)畫出現(xiàn)「中斷」——程序 FC
除了上面的這些畏邢,Android 5.0 (API 21)引入了三個(gè)新的 Interpolator
模型业扒,并把它們加入了 support v4 包中检吆。這三個(gè)新的 Interpolator
每個(gè)都和之前的某個(gè)已有的 Interpolator
規(guī)則相似,只有略微的區(qū)別程储。
FastOutLinearInInterpolator
加速運(yùn)動(dòng)蹭沛。
這個(gè) Interpolator
的作用你不能看它的名字,一會(huì)兒 fast 一會(huì)兒 linear 的章鲤,完全看不懂摊灭。其實(shí)它和 AccelerateInterpolator
一樣,都是一個(gè)持續(xù)加速的運(yùn)動(dòng)路線败徊。只不過 FastOutLinearInInterpolator
的曲線公式是用的貝塞爾曲線帚呼,而 AccelerateInterpolator
用的是指數(shù)曲線。具體來說皱蹦,它倆最主要的區(qū)別是 FastOutLinearInInterpolator
的初始階段加速度比 AccelerateInterpolator
要快一些煤杀。
FastOutLinearInInterpolator
:
AccelerateInterpolator
:
能看出它倆的區(qū)別嗎?
能看出來就怪了沪哺。這倆的速度模型幾乎就是一樣的沈自,不信我把它們的動(dòng)畫完成度 / 時(shí)間完成度曲線放在一起給你看:
看到了嗎?兩條線幾乎是一致的辜妓,只是紅線比綠線更早地到達(dá)了較高的斜率枯途,這說明在初始階段,FastOutLinearInInterpolator
的加速度比 AccelerateInterpolator
更高籍滴。
那么這意味著什么呢酪夷?
意味個(gè)毛。實(shí)際上孽惰,這點(diǎn)區(qū)別捶索,在實(shí)際應(yīng)用中用戶根本察覺不出來。而且灰瞻,AccelerateInterpolator
還可以在構(gòu)造方法中調(diào)節(jié)變速系數(shù)腥例,分分鐘調(diào)節(jié)到和 FastOutLinearInInterpolator
(幾乎)一模一樣辅甥。所以你在使用加速模型的時(shí)候,這兩個(gè)選哪個(gè)都一樣燎竖,沒區(qū)別的璃弄。
那么既然都一樣,我做這么多對(duì)比构回,講這么些干什么呢夏块?
因?yàn)槲业米屇懔私狻K鼈z雖然「用起來沒區(qū)別」纤掸,但這是基于我對(duì)它足夠了解所做出的判斷脐供,可我如果直接甩給你一句「它倆沒區(qū)別,想用誰用誰借跪,少廢話別問那么多」政己,你心里肯定會(huì)有一大堆疑問,在開發(fā)時(shí)用到它們的時(shí)候也會(huì)畏畏縮縮心里打鼓的掏愁,對(duì)吧歇由?
FastOutSlowInInterpolator
先加速再減速。
同樣也是先加速再減速的還有前面說過的 AccelerateDecelerateInterpolator
果港,不過它們的效果是明顯不一樣的沦泌。FastOutSlowInInterpolator
用的是貝塞爾曲線,AccelerateDecelerateInterpolator
用的是正弦 / 余弦曲線辛掠。具體來講谢谦, FastOutSlowInInterpolator
的前期加速度要快得多。
FastOutSlowInInterpolator
:
AccelerateDecelerateInterpolator
:
不論是從動(dòng)圖還是從曲線都可以看出萝衩,這二者比起來回挽,FastOutSlowInInterpolator
的前期加速更猛一些,后期的減速過程的也減得更迅速欠气。用更直觀一點(diǎn)的表達(dá)就是厅各,AccelerateDecelerateInterpolator
像是物體的自我移動(dòng),而 FastOutSlowInInterpolator
則看起來像有一股強(qiáng)大的外力「推」著它加速预柒,在接近目標(biāo)值之后又「拽」著它減速队塘。總之宜鸯,FastOutSlowInterpolator
看起來有一點(diǎn)「著急」的感覺憔古。
二者曲線對(duì)比圖:
LinearOutSlowInInterpolator
持續(xù)減速。
它和 DecelerateInterpolator
比起來淋袖,同為減速曲線鸿市,主要區(qū)別在于 LinearOutSlowInInterpolator
的初始速度更高。對(duì)于人眼的實(shí)際感覺,區(qū)別其實(shí)也不大焰情,不過還是能看出來一些的陌凳。
LinearOutSlowInInterpolator
:
DecelerateInterpolator
:
二者曲線對(duì)比:
對(duì)于所有 Interpolator
的介紹就到這里。這些 Interpolator
内舟,有的較為常用且有通用的使用場景合敦,有的需要你自己來根據(jù)情況而定。把它們了解清楚了验游,對(duì)于制作出觀感舒服的動(dòng)畫很有好處充岛。
3. 設(shè)置監(jiān)聽器
給動(dòng)畫設(shè)置監(jiān)聽器,可以在關(guān)鍵時(shí)刻得到反饋耕蝉,從而及時(shí)做出合適的操作崔梗,例如在動(dòng)畫的屬性更新時(shí)同步更新其他數(shù)據(jù),或者在動(dòng)畫結(jié)束后回收資源等垒在。
設(shè)置監(jiān)聽器的方法蒜魄, ViewPropertyAnimator
和 ObjectAnimator
略微不一樣: ViewPropertyAnimator
用的是 setListener()
和 setUpdateListener()
方法,可以設(shè)置一個(gè)監(jiān)聽器爪膊,要移除監(jiān)聽器時(shí)通過 set[Update]Listener(null)
填 null 值來移除权悟;而 ObjectAnimator
則是用 addListener()
和 addUpdateListener()
來添加一個(gè)或多個(gè)監(jiān)聽器砸王,移除監(jiān)聽器則是通過 remove[Update]Listener()
來指定移除對(duì)象推盛。
另外,由于 ObjectAnimator
支持使用 pause()
方法暫停谦铃,所以它還多了一個(gè) addPauseListener()
/ removePauseListener()
的支持耘成;而 ViewPropertyAnimator
則獨(dú)有 withStartAction()
和 withEndAction()
方法,可以設(shè)置一次性的動(dòng)畫開始或結(jié)束的監(jiān)聽驹闰。
3.1 ViewPropertyAnimator.setListener() / ObjectAnimator.addListener()
這兩個(gè)方法的名稱不一樣瘪菌,可以設(shè)置的監(jiān)聽器數(shù)量也不一樣,但它們的參數(shù)類型都是 AnimatorListener
嘹朗,所以本質(zhì)上其實(shí)都是一樣的师妙。 AnimatorListener
共有 4 個(gè)回調(diào)方法:
3.1.1 onAnimationStart(Animator animation)
當(dāng)動(dòng)畫開始執(zhí)行時(shí),這個(gè)方法被調(diào)用屹培。
3.1.2 onAnimationEnd(Animator animation)
當(dāng)動(dòng)畫結(jié)束時(shí)默穴,這個(gè)方法被調(diào)用。
3.1.3 onAnimationCancel(Animator animation)
當(dāng)動(dòng)畫被通過 cancel()
方法取消時(shí)褪秀,這個(gè)方法被調(diào)用蓄诽。
需要說明一下的是,就算動(dòng)畫被取消媒吗,onAnimationEnd()
也會(huì)被調(diào)用仑氛。所以當(dāng)動(dòng)畫被取消時(shí),如果設(shè)置了 AnimatorListener
,那么 onAnimationCancel()
和 onAnimationEnd()
都會(huì)被調(diào)用锯岖。onAnimationCancel()
會(huì)先于 onAnimationEnd()
被調(diào)用介袜。
3.1.4 onAnimationRepeat(Animator animation)
當(dāng)動(dòng)畫通過 setRepeatMode()
/ setRepeatCount()
或 repeat()
方法重復(fù)執(zhí)行時(shí),這個(gè)方法被調(diào)用出吹。
由于 ViewPropertyAnimator
不支持重復(fù)米酬,所以這個(gè)方法對(duì) ViewPropertyAnimator
相當(dāng)于無效。
3.2 ViewPropertyAnimator.setUpdateListener() / ObjectAnimator.addUpdateListener()
和上面 3.1 的兩個(gè)方法一樣趋箩,這兩個(gè)方法雖然名稱和可設(shè)置的監(jiān)聽器數(shù)量不一樣赃额,但本質(zhì)其實(shí)都一樣的,它們的參數(shù)都是 AnimatorUpdateListener
叫确。它只有一個(gè)回調(diào)方法:onAnimationUpdate(ValueAnimator animation)
跳芳。
3.2.1 onAnimationUpdate(ValueAnimator animation)
當(dāng)動(dòng)畫的屬性更新時(shí)(不嚴(yán)謹(jǐn)?shù)恼f,即每過 10 毫秒竹勉,動(dòng)畫的完成度更新時(shí))飞盆,這個(gè)方法被調(diào)用。
方法的參數(shù)是一個(gè) ValueAnimator
次乓,ValueAnimator
是 ObjectAnimator
的父類吓歇,也是 ViewPropertyAnimator
的內(nèi)部實(shí)現(xiàn),所以這個(gè)參數(shù)其實(shí)就是 ViewPropertyAnimator
內(nèi)部的那個(gè) ValueAnimator
票腰,或者對(duì)于 ObjectAnimator
來說就是它自己本身城看。
ValueAnimator
有很多方法可以用,它可以查看當(dāng)前的動(dòng)畫完成度杏慰、當(dāng)前的屬性值等等测柠。不過 ValueAnimator
是下一期才講的內(nèi)容,所以這期就不多說了缘滥。
3.3 ObjectAnimator.addPauseListener()
由于 ObjectAnimator.pause()
是下期的內(nèi)容轰胁,所以這個(gè)方法在這期就不講了。當(dāng)然朝扼,如果你有興趣的話赃阀,現(xiàn)在就了解一下也可以。
3.3 ViewPropertyAnimator.withStartAction/EndAction()
這兩個(gè)方法是 ViewPropertyAnimator
的獨(dú)有方法擎颖。它們和 set/addListener()
中回調(diào)的 onAnimationStart()
/ onAnimationEnd()
相比起來的不同主要有兩點(diǎn):
withStartAction()
/withEndAction()
是一次性的榛斯,在動(dòng)畫執(zhí)行結(jié)束后就自動(dòng)棄掉了,就算之后再重用ViewPropertyAnimator
來做別的動(dòng)畫肠仪,用它們?cè)O(shè)置的回調(diào)也不會(huì)再被調(diào)用肖抱。而set/addListener()
所設(shè)置的AnimatorListener
是持續(xù)有效的,當(dāng)動(dòng)畫重復(fù)執(zhí)行時(shí)异旧,回調(diào)總會(huì)被調(diào)用意述。withEndAction()
設(shè)置的回調(diào)只有在動(dòng)畫正常結(jié)束時(shí)才會(huì)被調(diào)用,而在動(dòng)畫被取消時(shí)不會(huì)被執(zhí)行。這點(diǎn)和AnimatorListener.onAnimationEnd()
的行為是不一致的荤崇。
關(guān)于監(jiān)聽器拌屏,就說到這里。本期內(nèi)容的講義部分也到此結(jié)束术荤。