Android 屬性動(dòng)畫

一. 簡(jiǎn)介

屬性動(dòng)畫(Property Animation)是Android 3.0(API 11)之后提供的動(dòng)畫框架,在之前Android 提供了 幀動(dòng)畫(Frame Animation) 和 補(bǔ)間動(dòng)畫(Tween Animation),幀動(dòng)畫就是把一個(gè)動(dòng)畫分成多張圖片,然后把這些圖片連貫起來(lái)播放,原理和動(dòng)畫片類似;補(bǔ)間動(dòng)畫是針對(duì)View 進(jìn)行的平移、旋轉(zhuǎn)、縮放、透明度變化 等動(dòng)畫效果;屬性動(dòng)畫則是通過(guò)改變屬性值來(lái)實(shí)現(xiàn)動(dòng)畫效果,不再局限于View。

屬性動(dòng)畫和補(bǔ)間動(dòng)畫的對(duì)比:

  • 作用對(duì)象
    補(bǔ)間動(dòng)畫的作用對(duì)象僅為View,而屬性動(dòng)畫則可作用于非View 對(duì)象。有些情況下,可能需要對(duì)View 的某個(gè)對(duì)象進(jìn)行操作,而不是整個(gè)View 對(duì)象,比如動(dòng)態(tài)改變View 的顏色,此時(shí)補(bǔ)間動(dòng)畫便無(wú)法實(shí)現(xiàn)。

  • 對(duì)View 的影響
    補(bǔ)間動(dòng)畫只改變View 的視覺(jué)效果,并不改變View 的屬性,比如將一個(gè)Button 從屏幕左上角 移動(dòng)至 右下角,使用補(bǔ)間動(dòng)畫的話,移動(dòng)完成之后 Button 的點(diǎn)擊區(qū)域還是左上角,因?yàn)樗奈恢脤傩圆](méi)有改變。而屬性動(dòng)畫,顧名思義,改變的就是屬性。

  • 動(dòng)畫效果
    補(bǔ)間動(dòng)畫效果比較單一,僅支持平移、旋轉(zhuǎn)、縮放、透明度變化,而屬性動(dòng)畫比較靈活,可以實(shí)現(xiàn)多種復(fù)雜的動(dòng)畫效果。

在現(xiàn)在的開(kāi)發(fā)過(guò)程中,由于屬性動(dòng)畫已經(jīng)可以實(shí)現(xiàn)幾乎所有補(bǔ)間動(dòng)畫能實(shí)現(xiàn)的功能,所以我們最常用的還是屬性動(dòng)畫,本篇將簡(jiǎn)介屬性動(dòng)畫的基本使用。

二. 工作原理及相關(guān)類

1. 屬性動(dòng)畫的工作過(guò)程
工作過(guò)程.png

從上面的工作過(guò)程可以看到,屬性動(dòng)畫框架中最常用也是最重要的幾個(gè)類,就是ValueAnimator、ObjectAnimator、Interpolator 的實(shí)現(xiàn)類 和 TypeEvaluator 的實(shí)現(xiàn)類。

2. 相關(guān)類簡(jiǎn)介

Animators:
1)ValueAnimator,屬性動(dòng)畫的主要計(jì)時(shí)引擎,它還計(jì)算要設(shè)置動(dòng)畫的屬性的值。它具有計(jì)算動(dòng)畫值的所有核心功能,包含每個(gè)動(dòng)畫的持續(xù)時(shí)間,有關(guān)動(dòng)畫是否重復(fù)進(jìn)行,更新屬性值的事件偵聽(tīng)器以及設(shè)置自定義類型的估值器功能。屬性動(dòng)畫有兩個(gè)部分:計(jì)算動(dòng)畫過(guò)程中的屬性值,給對(duì)象和屬性上設(shè)置這些值。ValueAnimator 僅負(fù)責(zé)計(jì)算值,因此必須監(jiān)聽(tīng)ValueAnimator 計(jì)算的值的更新,并使用按照自己的邏輯修改要設(shè)置動(dòng)畫的對(duì)象。

主要方法:

方法簽名 用途
public static ValueAnimator ofInt (int... values) ofInt() 作用有兩個(gè):
1. 創(chuàng)建動(dòng)畫實(shí)例
2. 將傳入的多個(gè)Int參數(shù)進(jìn)行平滑過(guò)渡: 如果傳入0和1,表示將值從0平滑過(guò)渡到1,如果傳入了3個(gè)Int參數(shù) a,b,c ,則是先從a平滑過(guò)渡到b,再?gòu)腷平滑過(guò)渡到C,以此類推。
ValueAnimator.ofInt() 內(nèi)置了整型估值器,直接采用默認(rèn)的,不需要設(shè)置,即默認(rèn)設(shè)置了如何從初始值 過(guò)渡到 結(jié)束值。
public static ValueAnimator ofFloat (float... values) 類似ofInt
public static ValueAnimator ofArgb (int... values) api 21 時(shí)加入,傳入的參數(shù)是顏色的int 值,提供顏色值的平滑變換,和ofInt 的區(qū)別在于使用的估值器不同
public static ValueAnimator ofObject (TypeEvaluator evaluator, Object... values) 可傳入對(duì)象作為參數(shù)創(chuàng)建Animator 實(shí)例,由于可以傳任意對(duì)象,所以需要自己提供估值器
public ValueAnimator setDuration (long duration) 設(shè)置動(dòng)畫持續(xù)時(shí)長(zhǎng),單位毫秒
public void setRepeatCount (int value) 設(shè)置動(dòng)畫重復(fù)次數(shù)
public void setRepeatMode (int value) 設(shè)置動(dòng)畫重復(fù)模式,有兩種取值:RESTART 或 REVERSE,顧名思義就是重新開(kāi)始 和 倒著執(zhí)行
public void setStartDelay (long startDelay) 設(shè)置延遲多長(zhǎng)時(shí)間開(kāi)始動(dòng)畫,單位毫秒
public boolean isRunning () 判斷動(dòng)畫是否正在運(yùn)行
public void addUpdateListener (ValueAnimator.AnimatorUpdateListener listener) 添加值變化時(shí)的監(jiān)聽(tīng)事件,若要使用ValueAnimator 實(shí)現(xiàn)動(dòng)畫就必須要添加一個(gè)Listener,在數(shù)據(jù)變化之后改變對(duì)象屬性來(lái)實(shí)現(xiàn)動(dòng)畫
public void start () 開(kāi)始動(dòng)畫,如果沒(méi)有設(shè)置延時(shí)開(kāi)始,動(dòng)畫會(huì)立即開(kāi)始,設(shè)置了則會(huì)延時(shí)一段時(shí)間后開(kāi)始
public void cancel () / public void end () 取消動(dòng)畫 / 結(jié)束動(dòng)畫,兩者的區(qū)別就是調(diào)用cancel 會(huì)停止在當(dāng)前狀態(tài),end 會(huì)將屬性值設(shè)置為結(jié)束值
public void addListener (Animator.AnimatorListener listener) 是父類Animator 的方法,添加Listener,監(jiān)聽(tīng)動(dòng)畫開(kāi)始、結(jié)束、取消、重復(fù)等事件
public void pause () / public void resume () 暫停 / 重新開(kāi)始動(dòng)畫,調(diào)用這兩個(gè)方法的線程必須和動(dòng)畫開(kāi)始的線程相同,另外,如果對(duì)一個(gè)沒(méi)有暫停的動(dòng)畫調(diào)用resume 方法將會(huì)被忽略

以上,只是比較常用的方法,還有許多諸如getter 等方法,這里不一一列舉,詳情可參官方文檔。

2)ObjectAnimator,是ValueAnimator的子類,允許我們將目標(biāo)對(duì)象和對(duì)象屬性設(shè)置為動(dòng)畫,它在計(jì)算動(dòng)畫的新值時(shí)會(huì)相應(yīng)地更新屬性,我們?cè)诖蠖鄶?shù)情況下可以使用ObjectAnimator,因?yàn)樗沟迷谀繕?biāo)對(duì)象上設(shè)置動(dòng)畫值的過(guò)程變得更加容易。但是,ObjectAnimator還有一些限制,例如要求在目標(biāo)對(duì)象上存在特定的acessor方法。

主要方法和ValueAnimator 類似,只是創(chuàng)建對(duì)象的ofXXX 方法有些不同,常用如下

方法簽名 用途
public static ObjectAnimator ofArgb (Object target, String propertyName, int... values) 構(gòu)造并返回一個(gè)在顏色值之間設(shè)置動(dòng)畫的ObjectAnimator,values 傳單個(gè)值意味著該值是動(dòng)畫的結(jié)束值,在這種情況下,起始值將從正在執(zhí)行動(dòng)畫的屬性的值和第一次調(diào)用start() 時(shí)的目標(biāo)對(duì)象產(chǎn)生。兩個(gè)值的話意味著起始值和結(jié)束值。兩個(gè)以上的值意味著起始值,沿途的動(dòng)畫值和結(jié)束值
public static ObjectAnimator ofFloat (Object target, String xPropertyName, String yPropertyName, Path path) 使用兩個(gè)屬性沿Path設(shè)置動(dòng)畫,“路徑”動(dòng)畫以二維方式移動(dòng),將坐標(biāo)(x,y)設(shè)置為動(dòng)畫,以跟隨線條。坐標(biāo)是浮點(diǎn)數(shù),它們被分別設(shè)置到由xPropertyName和yPropertyName指定的屬性。
public static ObjectAnimator ofFloat (Object target, String propertyName, float... values) 為指定對(duì)象target 的指定屬性propertyName 設(shè)置值
public static ObjectAnimator ofInt (T target, Property<T, Integer> xProperty, Property<T, Integer> yProperty, Path path) 和上面提到的ofFloat 類似,只不過(guò)參數(shù)值的類型是int 值

需要注意的是,在給對(duì)象的指定屬性設(shè)置值時(shí),要求對(duì)象內(nèi)必須有屬性對(duì)應(yīng)的getter 和setter。更多方法可以查文檔。

3)AnimatorSet

提供一種將動(dòng)畫分組在一起的機(jī)制,以便它們相互運(yùn)行,可以將動(dòng)畫設(shè)置為一起播放,按順序播放,或在指定的延遲后播放。主要方法如下

方法簽名 用途
public AnimatorSet.Builder play (Animator anim) 此方法創(chuàng)建一個(gè)Builder對(duì)象,用于設(shè)置動(dòng)畫之間的播放約束。這個(gè)初始的play() 方法告訴Builder動(dòng)畫,它是對(duì)Builder的后續(xù)命令的依賴。例如,調(diào)用play(a1).with(a2) 將AnimatorSet 設(shè)置為同時(shí)播放a1和a2,play(a1).before(a2) 設(shè)置AnimatorSet首先播放a1,然后播放a2,而 play(a1).after(a2) 將AnimatorSet 設(shè)置為先播放a2,然后播放a1。請(qǐng)注意,play() 是告訴Builder 創(chuàng)建依賴關(guān)系的動(dòng)畫的唯一方法,因此對(duì)Builder 中各種函數(shù)的連續(xù)調(diào)用都將引用play() 中提供的初始參數(shù)作為其他動(dòng)畫的依賴關(guān)系。例如,當(dāng)a1結(jié)束時(shí),調(diào)用play(a1).before(a2).before(a3) 將同時(shí)播放a2和a3; 它沒(méi)有在a2 和a3 之間建立依賴關(guān)系。
public void playSequentially (List<Animator> items) 順序執(zhí)行動(dòng)畫列表
public void playTogether (Collection<Animator> items) 同時(shí)播放集合中的動(dòng)畫
public void setCurrentPlayTime (long playTime) 設(shè)置時(shí)間進(jìn)度到指定的時(shí)間點(diǎn),值應(yīng)該在0 和 動(dòng)畫集結(jié)束的總時(shí)長(zhǎng) 之間
public void reverse () 反轉(zhuǎn)執(zhí)行動(dòng)畫,如果使用setCurrentPlayTime 跳到了某時(shí)間點(diǎn),將在該時(shí)間點(diǎn)反轉(zhuǎn),否則將從結(jié)束值開(kāi)始反轉(zhuǎn),這個(gè)方法值適用當(dāng)前動(dòng)畫,未來(lái)的動(dòng)畫將不受影響
public AnimatorSet setDuration (long duration) 設(shè)置動(dòng)畫集中每一個(gè)動(dòng)畫的執(zhí)行時(shí)長(zhǎng),默認(rèn)情況每個(gè)動(dòng)畫執(zhí)行自己的時(shí)長(zhǎng),調(diào)用了這個(gè)方法后,每個(gè)動(dòng)畫的執(zhí)行時(shí)長(zhǎng)將使用設(shè)置值

更多方法可見(jiàn)這里。

Interpolator:

時(shí)間插值器定義如何計(jì)算動(dòng)畫中的特定值作為時(shí)間的函數(shù)。例如,可以指定動(dòng)畫在整個(gè)動(dòng)畫中線性發(fā)生,這意味著動(dòng)畫在整個(gè)時(shí)間內(nèi)均勻移動(dòng),或者還可以指定動(dòng)畫以使用非線性時(shí)間,例如,在開(kāi)始時(shí)加速并在結(jié)束時(shí)減速。Android 提供了一些插值器,如果所提供的插值器都不適合需求,可以實(shí)現(xiàn)TimeInterpolator接口并創(chuàng)建自定義插值器。

Android 默認(rèn)提供了9 種插值器

描述
LinearInterpolator 值隨時(shí)間的變化為線性
image.png
AccelerateInterpolator 加速變化
image.png
DecelerateInterpolator 減速變化
image.png
AccelerateDecelerateInterpolator 先加速后減速變化
image.png
AnticipateInterpolator 先反向變化,再正向快速變化
image.png
OvershootInterpolator 快速變化到超出結(jié)束值一段,再緩慢反向變化回結(jié)束值
image.png
BounceInterpolator 不斷回彈地變化
image.png
CycleInterpolator 正弦函數(shù)變化
image.png

Evaluator:

Evaluator(估值器)告訴屬性動(dòng)畫系統(tǒng)如何計(jì)算給定屬性的值,它們獲取Animator類提供的時(shí)序數(shù)據(jù)(時(shí)序數(shù)據(jù)的值就是通過(guò)Interpolator 計(jì)算出的),動(dòng)畫的開(kāi)始和結(jié)束值,并根據(jù)此數(shù)據(jù)計(jì)算屬性的動(dòng)畫值。

Android 提供了三種估值器:

描述
IntEvaluator Int 值屬性的默認(rèn)估值器
FloatEvaluator Float 值屬性的默認(rèn)估值器
ArgbEvaluator 屬性值表示顏色時(shí),使用該估值器

如果我們需要做動(dòng)畫的屬性的類型不是int 或 float時(shí),就需要實(shí)現(xiàn)TypeEvaluator 接口自定義估值器。

三. 應(yīng)用

1. ValueAnimator

在使用ValueAnimator 實(shí)現(xiàn)動(dòng)畫時(shí),首先要?jiǎng)?chuàng)建一個(gè)Animator 對(duì)象,上面的API 簡(jiǎn)介提到了一系列的of 方法,可以創(chuàng)建針對(duì)不同屬性類型的Animator 對(duì)象

    val intAnim = ValueAnimator.ofInt(0, 1000).apply {
            duration = 5000
            startDelay = 1000
            interpolator = BounceInterpolator()
            addUpdateListener {         // 在監(jiān)聽(tīng)值更新的Listener 里給要做動(dòng)畫的對(duì)象(本例是一個(gè)TextView)的屬性賦值
                animText.x = (it.animatedValue as Int).toFloat()
                animText.rotationY = (it.animatedValue as Int).toFloat()
            }
            addListener(object : Animator.AnimatorListener {            // 監(jiān)聽(tīng)動(dòng)畫的執(zhí)行事件
                override fun onAnimationStart(animation: Animator?) {
                    println("-------------start")
                }

                override fun onAnimationEnd(animation: Animator?) {
                    println("-------------end")
                }

                override fun onAnimationCancel(animation: Animator?) {
                    println("-------------cancel")
                }

                override fun onAnimationRepeat(animation: Animator?) {
                    println("-------------repeat")
                }
            })
        }

除了用Java/Kotlin 代碼創(chuàng)建外,還可以在XML 中創(chuàng)建動(dòng)畫的相關(guān)設(shè)置:

<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:valueFrom="0"
    android:valueTo="1000"
    android:valueType="intType"
    android:duration="5000"
    android:startOffset="1000"
    android:repeatCount="1"
    android:repeatMode="reverse">

</animator>

加載XML 并創(chuàng)建對(duì)象的代碼如下:

val xmlAnim = AnimatorInflater.loadAnimator(this, R.animator.value_anim)

注意在使用XML 創(chuàng)建Animator 對(duì)象之后,也需要添加Listener 來(lái)動(dòng)態(tài)改變對(duì)象的屬性值。

創(chuàng)建了Animator 對(duì)象之后,若要開(kāi)始動(dòng)畫,只需要調(diào)用start 方法即可。

2. ObjectAnimator

ObjectAnimator 是ValueAnimator 的子類,對(duì)其進(jìn)行了一定程度的封裝,我們不需要再添加監(jiān)聽(tīng)值變化的Listener 來(lái)手動(dòng)為屬性賦值,我們只需在創(chuàng)建ObjectAnimator 對(duì)象時(shí),傳入要改變屬性的對(duì)象及其屬性名即可(但要保證該屬性有對(duì)應(yīng)的setter 方法),如下:

val colorAnim = ObjectAnimator.ofArgb(textView, "textColor", Color.parseColor("#dc5f26"), Color.parseColor("#2684DC")).apply {
    duration = 5000
}

同樣,ObjectAnimator 的相關(guān)設(shè)置也可以通過(guò)XML 來(lái)寫,

<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:valueFrom="0"
    android:valueTo="800"
    android:valueType="floatType"
    android:duration="5000"
    android:startOffset="1000"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:repeatCount="1"
    android:repeatMode="restart"
    android:propertyXName="x"
    android:propertyYName="y"
    android:pathData="M10,10Q200,100,400,600Q600,100,800,0Z">

</objectAnimator>

同樣需要加載對(duì)象

val xmlAnim = AnimatorInflater.loadAnimator(this, R.animator.obj_anim)
xmlAnim.setTarget(textView)                 // 設(shè)置動(dòng)畫作用的對(duì)象

開(kāi)始動(dòng)畫只需調(diào)用start 方法即可。

我們知道,屬性動(dòng)畫的動(dòng)畫效果就是通過(guò)不斷改變對(duì)象的屬性值來(lái)實(shí)現(xiàn)的,所以要比補(bǔ)間動(dòng)畫更靈活,那么補(bǔ)間動(dòng)畫能實(shí)現(xiàn)的View 的四種動(dòng)畫效果,放在屬性動(dòng)畫里應(yīng)該改變哪些值呢,如下表

屬性 作用 數(shù)值類型
alpha 控制View的透明度 float
translationX 控制X方向的位移 float
translationY 控制Y方向的位移 float
translationZ 控制Z方向的位移 float
scaleX 控制X方向的縮放倍數(shù) float
scaleY 控制Y方向的縮放倍數(shù) float
rotation 控制以屏幕方向(可以理解為Z 軸)為軸的旋轉(zhuǎn)度數(shù) float
rotationX 控制以X軸為軸的旋轉(zhuǎn)度數(shù) float
rotationY 控制以Y軸為軸的旋轉(zhuǎn)度數(shù) float

這些都是View 的屬性,通過(guò)對(duì)他們的改變就可以實(shí)現(xiàn)一些基本的動(dòng)畫效果。

3. AnimatorSet

AnimatorSet 可以將多個(gè)動(dòng)畫組合起來(lái)進(jìn)行播放,并且可以給這些動(dòng)畫設(shè)置先后順序等

        val path = Path().apply {
            moveTo(10f, 10f)
            quadTo(200f, 100f, 400f, 600f)
            quadTo(600f, 100f,800f, 0f)
        }
        val pathAnim = ObjectAnimator.ofFloat(textView, View.X, View.Y, path).apply {       // 創(chuàng)建一個(gè)讓View 按照指定Path 運(yùn)動(dòng)的動(dòng)畫
            duration = 3000
            interpolator = AccelerateDecelerateInterpolator()
        }

                // 顏色改變的動(dòng)畫
        val colorAnim = ObjectAnimator.ofArgb(textView, "textColor", Color.parseColor("#dc5f26"), Color.parseColor("#2684DC")).apply {
            duration = 5000
        }

                // 改變橫坐標(biāo)的動(dòng)畫
        val intAnim = ObjectAnimator.ofFloat(textView, "x", 0f).apply {
            duration = 3000
        }

        val animSet = AnimatorSet()
        animSet.play(pathAnim)          // 調(diào)用play 來(lái)播放
        animSet.play(colorAnim).with(intAnim).after(pathAnim).after(2000)           // with 表示同時(shí)播放,after 表示在xxx 之后播放,before 表示在xxx 之前播放

                animSet.start()                         // 開(kāi)始動(dòng)畫
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容