HenCoder Android 自定義 View 1-6: 屬性動畫(上手篇)

這期是 HenCoder 自定義繪製的第 1-6 期:屬性動畫(上手篇)java

若是你沒據說過 HenCoder,能夠先看看這個:
HenCoder:給高級 Android 工程師的進階手冊git

簡介

前幾期發佈後,常常在回覆裏看到有人問我何時講動畫。原本我是不打算講動畫的,由於動畫其實不算是自定義 View 的內容。但後來考慮了一下,動畫在自定義 View 的開發中也起着很重要的做用,有的時候你對動畫的瞭解不夠,就難以實現一些自定義 View 的效果。github

因而決定:加兩期,講動畫!canvas

不過並非全部的動畫都講,我要講的是屬性動畫。 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 的分支,來講一下屬性動畫的原理以及使用。ide

講解

複雜的東西用文字很難講清楚,因此每次遇到難講的內容我都會選擇上視頻,這期也不例外。post

話說作視頻太費精力和時間了,這期的視頻居然作了兩週。之後必定要控制住本身,少作視頻,否則怕會掉頭髮。動畫

(若是你是手機看的,能夠點這裏去 B 站看視頻)this

下面是講義部分。強烈建議你看完上面的視頻再看下面的內容,否則可能會遭遇理解障礙。spa

ViewPropertyAnimator

使用方式:View.animate() 後跟 translationX() 等方法,動畫會自動執行。

view.animate().translationX(500);複製代碼

具體能夠跟的方法以及方法所對應的 View 中的實際操做的方法以下圖所示:

從圖中能夠看到, View 的每一個方法都對應了 ViewPropertyAnimator 的兩個方法,其中一個是帶有 -By 後綴的,例如,View.setTranslationX() 對應了 ViewPropertyAnimator.translationX()ViewPropertyAnimator.translationXBy() 這兩個方法。其中帶有 -By() 後綴的是增量版本的方法,例如,translationX(100) 表示用動畫把 ViewtranslationX 值漸變爲 100,而 translationXBy(100) 則表示用動畫把 ViewtranslationX 值漸變地增長 100。l

這些方法的效果都簡單易懂,並且視頻裏也有簡單的演示,因此就不放示例圖了。若是你想看,能夠去下面的練習項目。(最好順便也練一下代碼)

ObjectAnimator

使用方式:

  1. 若是是自定義控件,須要添加 setter / getter 方法;
  2. ObjectAnimator.ofXXX() 建立 ObjectAnimator 對象;
  3. 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. 設置監聽器

給動畫設置監聽器,能夠在關鍵時刻獲得反饋,從而及時作出合適的操做,例如在動畫的屬性更新時同步更新其餘數據,或者在動畫結束後回收資源等。

設置監聽器的方法, ViewPropertyAnimatorObjectAnimator 略微不同: 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 毫秒,動畫的完成度更新時),這個方法被調用。

方法的參數是一個 ValueAnimatorValueAnimatorObjectAnimator 的父類,也是 ViewPropertyAnimator 的內部實現,因此這個參數其實就是 ViewPropertyAnimator 內部的那個 ValueAnimator,或者對於 ObjectAnimator 來講就是它本身自己。

ValueAnimator 有不少方法能夠用,它能夠查看當前的動畫完成度、當前的屬性值等等。不過 ValueAnimator 是下一期纔講的內容,因此這期就很少說了。

3.3 ObjectAnimator.addPauseListener()

因爲 ObjectAnimator.pause() 是下期的內容,因此這個方法在這期就不講了。固然,若是你有興趣的話,如今就瞭解一下也能夠。

3.3 ViewPropertyAnimator.withStartAction/EndAction()

這兩個方法是 ViewPropertyAnimator 的獨有方法。它們和 set/addListener() 中回調的 onAnimationStart() / onAnimationEnd() 相比起來的不一樣主要有兩點:

  1. withStartAction() / withEndAction() 是一次性的,在動畫執行結束後就自動棄掉了,就算以後再重用 ViewPropertyAnimator 來作別的動畫,用它們設置的回調也不會再被調用。而 set/addListener() 所設置的 AnimatorListener 是持續有效的,當動畫重複執行時,回調總會被調用。

  2. withEndAction() 設置的回調只有在動畫正常結束時纔會被調用,而在動畫被取消時不會被執行。這點和 AnimatorListener.onAnimationEnd() 的行爲是不一致的。

關於監聽器,就說到這裏。本期內容的講義部分也到此結束。

練習項目

爲了不轉頭就忘,強烈建議你趁熱打鐵,作一下這個練習項目:HenCoderPracticeDraw

下期預告

下期內容是屬性動畫的進階篇,主要講怎樣針對特殊類型的屬性作動畫,以及怎樣作複雜的動畫。雖然對於不少人來講,這期的內容學完就能夠在不少人面前裝逼了,但我仍是要說,下期的內容學完,你還能更上一層樓。

各位還記得幾周以前講三維旋轉時的這個動畫麼:

等下期內容結束後,這個動畫對你將是小意思。(我猜有些人在這期以後就能作出來這個,但下期以後作起來會更輕鬆。)

感謝

這期作了兩週多,腰痠背痛加茶飯不思,感受快上西天了。

感謝個人廚師:我岳父。天天到了飯點下樓蹭飯、蹭完飯嘴一抹凳子一踢就走的感受,還真不錯。
感謝個人按摩師:我老婆。
感謝個人字幕組:我老婆。
感謝我兒子終於上了幼兒園,不會有事沒事就推開個人房門讓我陪他玩了。
最後,我再假模假樣地感謝一下催更和不催更的各位的耐心或不耐心的等待。

以爲贊?

若是你看完以爲有收穫,把文章轉發到你的微博、微信羣、朋友圈、公衆號,讓其餘須要的人也看到吧。

相關文章
相關標籤/搜索