Android動畫之閒聊片

在Android開發過程當中,或多或少確定會與動畫打交道,今天我就來聊聊Android動畫java

視圖動畫

視圖動畫是僅針對view對象添加動畫效果, 僅支持平移(Translate)縮放(Scale)旋轉(Rotate)透明度(Alpha), 因此你沒法對背景顏色等作動畫效果android

動畫經常使用屬性介紹:bash

- android:duration   動畫時長(毫秒)
- android:startOffset   動畫開始時delay時長(毫秒)
- android:fillAfter   動畫結束時,是否保持在最後的位置(默認false,即會恢復初始狀態)
- android:fillBefore   動畫開始時,在startOffset階段時是否應用動畫屬性的初始值,不然應用view的初始值(默認true),須要配合fillEnabled使用,若是fillEnabled爲false,則應用動畫屬性的初始值; 即若是startOffset爲0,則fillBefore設不設制 效果都同樣
- android:fillEnabled    設置fillBefore屬性是否有效
複製代碼

注意:fillBeforefillEnabledAnimationSet對象設置不起做用app

平移(Translate)

利用xml定義動畫,則動畫文件須要定義在res/anim文件夾下ide

res/anim/view_anim_ translate.xml佈局

<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXDelta="0" // 定義動畫開始時,x軸的位置
    android:toXDelta="300" // 定義動畫結束時,x軸的位置
    android:fromYDelta="0" // 定義動畫開始時,y軸的位置
    android:toYDelta="0" // 定義動畫結束時,y軸的位置
    android:duration="2000"/>
複製代碼

fromXDeltatoXDeltafromYDeltatoYDelta屬性能夠設置三種類型的值:數值、百分數、百分數p動畫

  • 數值: 以view左上角爲原點,x軸或y軸偏移多少px
  • 百分數: 以view左上角爲原點,x軸或y軸偏移View寬度或高度的百分比
  • 百分數p: 以view左上角爲原點,x軸或y軸偏移父控件寬度或高度的百分比

縮放(Scale)

res/anim/view_anim_scale.xmlui

<scale xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromXScale="0.5" // 定義動畫開始時,水平縮放因子
    android:toXScale="2.0" // 定義動畫結束時,水平縮放因子
    android:fromYScale="1.0" // 定義動畫開始時,垂直縮放因子
    android:toYScale="1.0" // 定義動畫結束時,垂直縮放因子
    android:pivotX="0" // 定義原點x軸的位置
    android:pivotY="0" // 定義原點y軸的位置
    android:duration="2000"/>
複製代碼

pivotXpivotY屬性也能夠設置三種類型的值:數值、百分數、百分數pthis

  • 數值: 以view左上角爲原點,x軸或y軸偏移多少px後 爲縮放動畫的原點
  • 百分數: 以view左上角爲原點,x軸或y軸偏移View寬度或高度的百分比後 爲縮放動畫的原點
  • 百分數p: 以view左上角爲原點,x軸或y軸偏移父控件寬度或高度的百分比後 爲縮放動畫的原點

旋轉(Rotate)

res/anim/view_anim_rotate.xmllua

<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="10"
    android:toDegrees="350"
    android:pivotY="50%"
    android:pivotX="50%"
    android:duration="2000"/>
複製代碼

pivotXpivotY屬性也能夠設置三種類型的值:數值、百分數、百分數p,參考上面的說明

透明度(Alpha)

res/anim/view_anim_alpha.xml

<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromAlpha="0.5" // 動畫開始時的透明度
    android:toAlpha="1"  // 動畫結束時的透明度
    android:duration="2000"/>
複製代碼

上面介紹了 怎麼在xml中定義動畫文件,而後就須要在代碼中使用AnimationUtils加載動畫文件

val translateAnimation = AnimationUtils.loadAnimation(this, R.anim.view_anim_alpha)
view.startAnimation(translateAnimation)
複製代碼

上面的四種動畫對應的java動畫類是TranslateAnimationScaleAnimationRotateAnimationAlphaAnimation;因此也能夠直接使用java代碼來實現上面的四種動畫

// 以AlphaAnimation爲例
val alphaAnimation = AlphaAnimation(1f, 0.2f)
alphaAnimation.duration = 2000
view.startAnimation(alphaAnimation)
複製代碼

AnimationSet

AnimationSet是實現將一組動畫一塊兒播放的效果

<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:fillAfter="true">
    <translate
        android:fromXDelta="0"
        android:toXDelta="100%"
        android:fromYDelta="0"
        android:toYDelta="0"
        android:duration="2000"/>

    <scale
        android:fromXScale="1.0"
        android:toXScale="2.0"
        android:fromYScale="1.0"
        android:toYScale="1.0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:duration="2000"/>
</set>
複製代碼

fillAfter、fillBefore 和 fillEnabled

這裏單獨在介紹一下fillAfterfillBeforefillEnabled

先看看fillAfter的效果

val translate1Animation = TranslateAnimation(0f, 300f, 0f, 0f)
translate1Animation.fillAfter = false
translate1Animation.duration = 1000
tv_view1.startAnimation(translate1Animation)

val translate2Animation = TranslateAnimation(0f, 300f, 0f, 0f)
translate2Animation.fillAfter = true
translate2Animation.duration = 1000
tv_view2.startAnimation(translate2Animation)
複製代碼

可見fillAfter的做用是在動畫結束時,是否保持在最後的位置

在看看fillBefore 配合 fillEnabled的效果

val translate1Animation = TranslateAnimation(100f, 300f, 0f, 0f)
translate1Animation.isFillEnabled = true
translate1Animation.fillBefore = false
translate1Animation.fillAfter = true
translate1Animation.startOffset = 1000
translate1Animation.duration = 1000
tv_view1.startAnimation(translate1Animation)

val translate2Animation = TranslateAnimation(100f, 300f, 0f, 0f)
translate2Animation.isFillEnabled = true
translate2Animation.fillBefore = true
translate2Animation.fillAfter = true
translate2Animation.startOffset = 1000
translate2Animation.duration = 1000
tv_view2.startAnimation(translate2Animation)
複製代碼

可見fillBefore表示在startOffset階段時是否應用動畫屬性的初始值

注意:視圖動畫只會在繪製視圖的位置進行修改,而不會修改實際的視圖自己;若是您爲某個按鈕添加了動畫效果,使其能夠在屏幕上移動,該按鈕會正確繪製,但可以點擊按鈕的實際位置並不會更改

屬性動畫

屬性動畫沒有上面視圖動畫的限制,幾乎能夠爲任何內容添加動畫效果;您能夠定義一個隨時間更改任何對象屬性的動畫,不管其是否繪製到屏幕上

屬性動畫沒有fillAfterfillBeforefillEnabled; 動畫結束後保持在最後一幀的位置(相似視圖動畫的fillAftertrue效果同樣),動畫真正開始前(即startDelay結束後) 才應用動畫的初始值(相似視圖動畫的fillEnabledtruefillBeforefalse效果同樣)

屬性動畫沒有startOffset,而是使用startDelay代替

ValueAnimator

ValueAnimator類是屬性動畫的核心類,它繼承自Animator;藉助ValueAnimator類,您能夠爲動畫播放期間某些類型的值添加動畫效果,只需指定一組要添加動畫效果的 intfloat顏色值便可, 可使用ValueAnimator的任一工廠方法來獲取它:ofInt()ofFloat()ofObject()ofArgb();

因爲ValueAnimator類只提供計算添加動畫效果以後的值,因此須要藉助AnimatorUpdateListener實現修改view的屬性,實現動畫效果

// 平移
ValueAnimator.ofFloat(0f, 300f).apply {
    duration = 1000
    addUpdateListener {
        view.translationX = it.animatedValue as Float
        view.translationY = it.animatedValue as Float
    }
    start()
}

// 縮放
ValueAnimator.ofFloat(0.5f, 2f).apply {
    duration = 1000
    addUpdateListener {
        view.pivotX = 0f
        view.pivotY = 0f
        view.scaleX = it.animatedValue as Float
        view.scaleY = it.animatedValue as Float
    }
    start()
}

// 旋轉
ValueAnimator.ofFloat(10f, 350f).apply {
    duration = 1000
    addUpdateListener {
        view.pivotX = 0f
        view.pivotY = 0f
        view.rotation = it.animatedValue as Float
    }
    start()
}

// 透明度
ValueAnimator.ofFloat(0.1f, 1f).apply {
    duration = 1000
    addUpdateListener {
        view.alpha = it.animatedValue as Float
    }
    start()
}

// 背景顏色
ValueAnimator.ofArgb(Color.parseColor("#ff0000"), Color.parseColor("#0000ff")).apply {
    duration = 1000
    addUpdateListener {
        view.setBackgroundColor(it.animatedValue as Int)
    }
    start()
}

複製代碼

可見ValueAnimator是給定一個數值的變化區間和時間段(默認 300 毫秒),計算播放期間的的動畫值,而後由你本身實現修改view的屬性,因此ValueAnimator能夠根據須要實現修改view的任何屬性,達到不一樣的效果

對於屬性動畫的pivotXpivotY 與 視圖動畫有一點不同,只能設置數值,不能設置百分比等;pivotXpivotY默認是相對view的中心位置,一旦設置就是相對view的左上角的座標

上面是使用代碼建立屬性動畫,下面咱們來使用xml定義屬性動畫

對於屬性動畫在xml中定義動畫文件與視圖動畫不同,視圖動畫是定義在res/anim文件夾下, 而屬性動畫定義在res/animator文件夾下

res/animator/view_translate.xml

<?xml version="1.0" encoding="utf-8"?>
// animator 標籤 對應 ValueAnimator類
// objectAnimator 標籤 對應下面要講到的 ObjectAnimator類
<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:valueFrom="0"
    android:valueTo="300"
    android:valueType="floatType"
    android:duration="1000" />
複製代碼

在代碼中使用以下

(AnimatorInflater.loadAnimator(this, R.animator.view_translate) as ValueAnimator).apply {
    addUpdateListener {
        view.translationX = it.animatedValue as Float
        view.translationY = it.animatedValue as Float
    }
    start()
}

複製代碼

ObjectAnimator

ObjectAnimatorValueAnimator的子類,它也有ofInt()ofFloat()ofObject()ofArgb()ofPropertyValuesHolder等工廠方法,可是與ValueAnimator不同的是,新增了target、和 propertyName參數

其中targetpropertyName的關係是 target必須有一個publicsetXXX的方法, 其中XXX就是PropertyName的名字

上面的demo使用ObjectAnimator以下:

// 平移
ObjectAnimator.ofFloat(view, "translationX", 0f, 300f).apply {
    duration = 1000
    start()
}

// 縮放
ObjectAnimator.ofFloat(view, "scaleX", 0.5f, 2f).apply {
    duration = 1000
    view.pivotX = 0f
    view.pivotY = 0f
    start()
}

// 縮放(同時修改2個屬性 方法一)
val path = Path().apply {
    moveTo(0.5f, 0.5f)
    lineTo(2f, 2f)
}
ObjectAnimator.ofFloat(view, "scaleX", "scaleY", path).apply {
    duration = 1000
    view.pivotX = 0f
    view.pivotY = 0f
    start()
}

// 縮放(同時修改2個屬性 方法二)
val scaleXAnim = PropertyValuesHolder.ofFloat("scaleX", 0.5f, 2f)
val scaleYAnim = PropertyValuesHolder.ofFloat("scaleY", 0.5f, 2f)
ObjectAnimator.ofPropertyValuesHolder(view, scaleXAnim, scaleYAnim).apply {
    duration = 1000
    view.pivotX = 0f
    view.pivotY = 0f
    start()
}

// 旋轉
ObjectAnimator.ofFloat(view, "rotation", 10f, 350f).apply {
    duration = 1000
    view.pivotX = 0f
    view.pivotY = 0f
    start()
}

// 透明度
ObjectAnimator.ofFloat(view, "alpha", 0.1f, 1f).apply {
    duration = 1000
    start()
}

// 背景顏色
ObjectAnimator.ofArgb(view, "backgroundColor", Color.parseColor("#ff0000"), Color.parseColor("#0000ff")).apply {
    duration = 1000
    start()
}
複製代碼

xml中定義動畫資源文件以下

res/animator/view_scale.xml

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:propertyName="scaleX"
    android:valueFrom="0.5"
    android:valueTo="2.0"
    android:valueType="floatType" />
複製代碼

代碼中加載動畫文件以下

AnimatorInflater.loadAnimator(this, R.animator.view_scale).apply {
    setTarget(view)
    start()
}
複製代碼

大多數狀況下 咱們都是使用ObjectAnimator,而不是ValueAnimator

AnimatorSet

AnimatorSet 是 針對屬性動畫 用於播放一組動畫效果的類;它支持同時播放(playTogether)順序播放(playSequentially); 同時也能夠按照喜歡使用before(anim)with(anim)after(anim)三個方法自由組合多個屬性動畫

with(anim)設置此動畫play(anim)與anim同時執行

before(anim)設置此動畫play(anim)在anim以前執行

after(anim)設置此動畫play(anim)在anim以後執行

val translationXAnim = ObjectAnimator.ofFloat(tv_view, "translationX", 0f, 300f).apply { duration = 1000 }
val scaleXAnim = ObjectAnimator.ofFloat(tv_view, "scaleX", 0.5f, 2f).apply { duration = 1000 }
val scaleYAnim = ObjectAnimator.ofFloat(tv_view, "scaleY", 0.5f, 2f).apply { duration = 1000 }
AnimatorSet().apply {
    // 若是設置了duration,則會覆蓋每一個animator的duration,不然使用animator本身的duration
    // duration = 4000
    // 同時播放
	// playTogether(translationXAnim, scaleXAnim, scaleYAnim)
    // 按順序播放
    // playSequentially(translationXAnim, scaleXAnim, scaleYAnim)
    // 先平移,而後在x軸和y軸同時縮放
    play(translationXAnim).before(scaleXAnim).before(scaleYAnim)
    start()
}
複製代碼

在xml中定義動畫資源文件以下

res/animator/view_animator_set.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" 
    android:ordering="sequentially">  <!-- 定義按循序播放 -->

    <objectAnimator
        android:duration="1000"
        android:propertyName="translationX"
        android:valueType="floatType"
        android:valueFrom="0"
        android:valueTo="300" />

    <!-- 定義一塊兒播放播放 -->
    <set android:ordering="together">
        <objectAnimator
            android:duration="1000"
            android:propertyName="scaleX"
            android:valueType="floatType"
            android:valueFrom="0.5"
            android:valueTo="2.0"/>

        <objectAnimator
            android:duration="1000"
            android:propertyName="scaleY"
            android:valueType="floatType"
            android:valueFrom="0.5"
            android:valueTo="2.0"/>
    </set>
</set>
複製代碼

使用代碼加載動畫資源文件以下

AnimatorInflater.loadAnimator(this, R.animator.view_animator_set).apply {
    setTarget(view)
    start()
}
複製代碼

LayoutTransition

LayoutTransition 類爲 ViewGroup 內的佈局更改添加動畫效果, 當您向 ViewGroup添加視圖或刪除其中的視圖時,或當您使用 VISIBLEINVISIBLEGONE 調用視圖的 setVisibility() 方法時,這些視圖可能會經歷出現和消失動畫。向 ViewGroup 添加視圖或刪除其中的視圖時,其中剩餘的視圖還可能以動畫形式移動到新位置

您能夠調用 setAnimator() 並使用如下任一 LayoutTransition 常量傳入 Animator對象,從而在 LayoutTransition 對象中定義相應動畫

  • APPEARING: 定義在ViewGroup中添加或顯示view的動畫
  • CHANGE_APPEARING: 定義因爲view的添加或顯示,致使ViewGroup中其餘view發生變化的動畫
  • DISAPPEARING: 定義在ViewGroup中隱藏或移除view的動畫
  • CHANGE_DISAPPEARING: 定義因爲view的隱藏或移除,致使ViewGroup中其餘view發生變化的動畫

先看看下面的demo

第一步先定義layout.xml以下

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onBlock1Show"
            android:text="顯示"/>

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onBlock1Hide"
            android:text="隱藏"/>
    </LinearLayout>
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@+id/block1"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:textSize="30dp"
            android:textColor="#ff0000"
            android:gravity="center"
            android:text="block1"
            android:background="#dddd00"/>
        <TextView
            android:id="@+id/block2"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:textSize="30dp"
            android:textColor="#ff0000"
            android:gravity="center"
            android:text="block2"
            android:background="#00fdfd"/>
    </LinearLayout>
</LinearLayout>
複製代碼

而後在代碼中使用LayoutTransition以下

先看看LayoutTransition.DISAPPEARINGLayoutTransition.APPEARING的效果

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_animator_property)

    // 定義block1隱藏的動畫
    val scaleYOut = PropertyValuesHolder.ofFloat("scaleY", 1f, 0f)
    val scaleXOut = PropertyValuesHolder.ofFloat("scaleY", 1f, 0f)
    val alphaOut = PropertyValuesHolder.ofFloat("alpha", 1f, 0f)
    val rotationOut = PropertyValuesHolder.ofFloat("rotation", 0f, 360f)
    val disappearingAnim = ObjectAnimator.ofPropertyValuesHolder(null as? Any, scaleYOut, scaleXOut, alphaOut, rotationOut)

    // 定義block1顯示的動畫
    val scaleYIn = PropertyValuesHolder.ofFloat("scaleY", 0f, 1f)
    val scaleXIn = PropertyValuesHolder.ofFloat("scaleX", 0f, 1f)
    val alphaIn = PropertyValuesHolder.ofFloat("alpha", 0f, 1f)
    val rotationIn = PropertyValuesHolder.ofFloat("rotation", 360f, 0f)
    val appearingAnim = ObjectAnimator.ofPropertyValuesHolder(null as? Any, scaleYIn, scaleXIn, alphaIn, rotationIn)

    val layoutTransition = LayoutTransition()
    // 應用block1隱藏動畫
    layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, disappearingAnim)
    // 應用block1顯示動畫
    layoutTransition.setAnimator(LayoutTransition.APPEARING, appearingAnim)
    block_container.layoutTransition = layoutTransition
}

fun onBlock1Show(view: View) {
    block1?.parent ?: block_container.addView(block1, 0)
//        block1.visibility = View.VISIBLE
}

fun onBlock1Hide(view: View) {
    block_container.removeView(block1)
//        block1.visibility = View.GONE
}
複製代碼

再看看LayoutTransition.CHANGE_DISAPPEARINGLayoutTransition.CHANGE_APPEARING的效果

...

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_animator_property)

    // 定義block1隱藏的動畫
    ...

    // 定義block1顯示的動畫
    ...

    // 定義隱藏block1時,block2的動畫
    // 必定要添加下面的動畫(LayoutTransition 源碼裏是這麼寫的),不然添加的動畫可能沒效果, 暫時不知道爲何
    val pvhLeft = PropertyValuesHolder.ofInt("left", 0, 1)
    val pvhTop = PropertyValuesHolder.ofInt("top", 0, 1)
    val pvhRight = PropertyValuesHolder.ofInt("right", 0, 1)
    val pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1)
    val pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1)
    val pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1)
    // 自定義縮放動畫
    val scaleXInAnim = PropertyValuesHolder.ofFloat("scaleX", 1f, 0.2f, 1f)
    val scaleYInAnim = PropertyValuesHolder.ofFloat("scaleY", 1f, 0.2f, 1f)
    val changeDisappearingAnim = ObjectAnimator.ofPropertyValuesHolder(null as? Any, pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY, scaleXInAnim, scaleYInAnim)

    // 定義顯示block1時,block2的動畫
    val backgroundAnim = ObjectAnimator.ofArgb(null as? Any, "backgroundColor", Color.parseColor("#ff0000"), Color.parseColor("#0000ff"))
    val changingAppearingAnim = AnimatorSet().apply {
        //(偷個懶,直接克隆changeDisappearingAnim動畫)
        playTogether(changeDisappearingAnim.clone(), backgroundAnim)
    }

	val layoutTransition = LayoutTransition()
    // 應用block1隱藏動畫
    layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, disappearingAnim)
    // 應用block1顯示動畫
    layoutTransition.setAnimator(LayoutTransition.APPEARING, appearingAnim)
    // 應用block2的changeDisappearingAnim動畫
    layoutTransition.setAnimator(LayoutTransition.CHANGE_DISAPPEARING, changeDisappearingAnim)
    // 應用block2的changingAppearingAnim動畫
    layoutTransition.setAnimator(LayoutTransition.CHANGE_APPEARING, changingAppearingAnim)
    block_container.layoutTransition = layoutTransition
}
複製代碼

效果以下

通常狀況不須要設置LayoutTransition.CHANGE_DISAPPEARINGLayoutTransition.CHANGE_APPEARING的動畫,直接使用默認的動畫便可;若是須要自定義動畫,則必定要在默認的屬性動畫上,在加上你本身的動畫,相似上面的代碼

上面是使用代碼定義動畫資源,你也能夠在res/animator文件夾下中定義xml動畫資源,而後在代碼中應用, 這裏我就不舉例了

除了自定義LayoutTransition以外,咱們能夠直接使用animateLayoutChanges屬性快速的添加默認的動畫效果

<LinearLayout
	android:id="@+id/block_container"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:animateLayoutChanges="true">
	...
</LinearLayout>
複製代碼

這樣的話就不須要在代碼裏添加任何代碼,自動爲添加到 ViewGroup 或從中刪除的視圖以及該 ViewGroup 中剩餘的視圖添加動畫效果

StateListAnimator

經過StateListAnimator類,您能夠定義在視圖狀態更改時運行的Animator。此對象充當 Animator對象的封裝容器,只要指定的視圖狀態(例如「按下」或「聚焦」)發生更改,就會調用該動畫

StateListAnimator最低支持Android 5.0

在drawable下定義資源文件以下(也能夠定義在xml文件夾下,官方文檔是定義在xml文件夾下)

res/drawable/animator_state_list

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1.5"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="rotation"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueFrom="0"
                android:valueTo="360"
                android:valueType="floatType"/>
        </set>
    </item>
    <item android:state_pressed="false">
        <set>
            <objectAnimator android:propertyName="scaleX"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="scaleY"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueTo="1"
                android:valueType="floatType"/>
            <objectAnimator android:propertyName="rotation"
                android:duration="@android:integer/config_shortAnimTime"
                android:valueFrom="360"
                android:valueTo="0"
                android:valueType="floatType"/>
        </set>
    </item>
</selector>

複製代碼

而後在使用stateListAnimator屬性設置animator_state_list資源文件便可

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

    <Button
	    android:id="@+id/btn_state_list"
        android:layout_width="200dp"
        android:layout_height="wrap_content"
        android:text="StateListAnimator"
        android:stateListAnimator="@drawable/animator_state_list"/>
</LinearLayout>
複製代碼

也能夠在代碼中設置

val stateListAnimator = AnimatorInflater.loadStateListAnimator(this, R.drawable.animator_state_list)
btn_state_list.stateListAnimator = stateListAnimator
複製代碼

效果以下

AnimatedStateListDrawable

AnimatedStateListDrawable的做用是 在狀態更改間播放一組關鍵幀動畫,達到最終的動畫效果

AnimatedStateListDrawable最低支持Android 5.0

因爲沒有那麼多圖片,因此下面的demo以color爲例(drawable也支持設置color)

首先在在res/drawable文件夾下新建animator_state_list_drawable.xml

<?xml version="1.0" encoding="utf-8"?>
<animated-selector xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- 定義 按下 狀態的 drawable -->
    <item android:id="@+id/pressed" android:state_pressed="true" android:drawable="@color/color_00ff00"/>
    <!-- 定義 鬆開 狀態的 drawable -->
    <item android:id="@+id/no_pressed" android:state_pressed="false" android:drawable="@color/color_ffff00"/>

    <!-- 定義 由按下到鬆開 的動畫轉換過程(相似幀動畫, 一幀一幀的播放) -->
    <transition
        android:fromId="@id/pressed"
        android:toId="@id/no_pressed">
        <animation-list>
            <item android:duration="30" android:drawable="@color/color_00ff00"/>
            <item android:duration="30" android:drawable="@color/color_11ff00"/>
            <item android:duration="30" android:drawable="@color/color_22ff00"/>
            <item android:duration="30" android:drawable="@color/color_33ff00"/>
            <item android:duration="30" android:drawable="@color/color_44ff00"/>
            <item android:duration="30" android:drawable="@color/color_55ff00"/>
            <item android:duration="30" android:drawable="@color/color_66ff00"/>
            <item android:duration="30" android:drawable="@color/color_77ff00"/>
            <item android:duration="30" android:drawable="@color/color_88ff00"/>
            <item android:duration="30" android:drawable="@color/color_99ff00"/>
            <item android:duration="30" android:drawable="@color/color_aaff00"/>
            <item android:duration="30" android:drawable="@color/color_bbff00"/>
            <item android:duration="30" android:drawable="@color/color_ccff00"/>
            <item android:duration="30" android:drawable="@color/color_ddff00"/>
            <item android:duration="30" android:drawable="@color/color_eeff00"/>
            <item android:duration="30" android:drawable="@color/color_ffff00"/>
        </animation-list>
    </transition>

    <!-- 定義 由鬆開到按下 的動畫轉換過程(相似幀動畫, 一幀一幀的播放) -->
    <transition
        android:fromId="@id/no_pressed"
        android:toId="@id/pressed">
        <animation-list>
            <item android:duration="30" android:drawable="@color/color_ffff00"/>
            <item android:duration="30" android:drawable="@color/color_eeff00"/>
            <item android:duration="30" android:drawable="@color/color_ddff00"/>
            <item android:duration="30" android:drawable="@color/color_ccff00"/>
            <item android:duration="30" android:drawable="@color/color_bbff00"/>
            <item android:duration="30" android:drawable="@color/color_aaff00"/>
            <item android:duration="30" android:drawable="@color/color_99ff00"/>
            <item android:duration="30" android:drawable="@color/color_88ff00"/>
            <item android:duration="30" android:drawable="@color/color_77ff00"/>
            <item android:duration="30" android:drawable="@color/color_66ff00"/>
            <item android:duration="30" android:drawable="@color/color_55ff00"/>
            <item android:duration="30" android:drawable="@color/color_44ff00"/>
            <item android:duration="30" android:drawable="@color/color_33ff00"/>
            <item android:duration="30" android:drawable="@color/color_22ff00"/>
            <item android:duration="30" android:drawable="@color/color_11ff00"/>
            <item android:duration="30" android:drawable="@color/color_00ff00"/>
        </animation-list>
    </transition>
</animated-selector>
複製代碼

而後在layout佈局中使用

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center">

    <View
        android:id="@+id/v_state_list_drawable"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:clickable="true"
        android:background="@drawable/animator_state_list_drawable"/>
</LinearLayout>
複製代碼

效果以下

相信你們對於在res/drawable下定義和使用普通的drawable應該都知道,而AnimatedStateListDrawable 只是添加了一組關鍵幀的過渡動畫,讓視圖的狀態變化體驗更友好,不顯得那麼僵硬而已

TimeInterpolator(插值器)和TypeEvaluator(估值器)

TimeInterpolator(插值器)指定了如何根據時間計算動畫中的因子(fraction),它的取值範圍通常是0到1

TimeInterpolator(插值器)會接受來自Animator提供的已播放動畫的時間片斷(input)時間片斷(input)的取值範圍是0到1;而TimeInterpolator(插值器)就是修改這個值達獲得最終的因子(fraction),從而達到LinearInterpolator(線性)AccelerateInterpolator(加速)DecelerateInterpolator(減速)AccelerateDecelerateInterpolator(先加速再減速)等不一樣的動畫效果

LinearInterpolator源碼

override fun getInterpolation(input: Float): Float = input
複製代碼

AccelerateDecelerateInterpolator源碼

override fun getInterpolation(input: Float): Float =
            (Math.cos((input + 1) * Math.PI) / 2.0f).toFloat() + 0.5f
複製代碼

TimeInterpolator(插值器)都是基於數學中一個二維座標系來計算的;若是你要實現一個很是『真實、天然』的動畫,那你必須知道對應的數學公式

TypeEvaluator(估值器)就是根據TimeInterpolator(插值器)計算出來的因子(fraction),而後根據初始值(startValue)結束值(endValue), 計算一個最終的屬性值

系統只提供了IntEvaluatorFloatEvaluatorArgbEvaluator三種類型的估值器,若是要爲 Android 系統沒法識別的類型添加動畫效果,則能夠經過實現TypeEvaluator接口來建立您本身的估值器

FloatEvaluator源碼

// 因爲fraction 取值範圍是0~1
// 當 fraction = 0時,計算出來的屬性值就是 startValue
// 當 fraction = 1時,計算出來的屬性值就是 endValue
// 當 fraction 在 0~1中間時,計算出來的屬性值就是 startValue~endValue的中間值
override fun evaluate(fraction: Float, startValue: Number, endValue: Number): Float {
    val startFloat = startValue.toFloat()
    return startFloat + fraction * (endValue.toFloat() - startFloat)
}
複製代碼

Keyframe關鍵幀

Keyframe 對象由<時間因子, 值>對組成,用於在動畫的特定時間定義特定的狀態, 每一個關鍵幀還能夠用本身的插值器控制動畫在上一關鍵幀時間和此關鍵幀時間之間的時間間隔內的行爲

Keyframe 提供了 ofInt()ofFloat()ofObject()工廠方法建立實例

// 定義剛開始時 0度
val kf0 = Keyframe.ofFloat(0f, 0f)

// 定義時間因子factor=0.5時,value = 360
// 即factor在[0f, 0.5f]變化過程當中,rotation從0變到360
val kf1 = Keyframe.ofFloat(0.5f, 360f)

// 定義時間因子factor=1時,value = 0
// 即factor在[0.5f, 1f]變化過程當中,rotation從360變到0
val kf2 = Keyframe.ofFloat(1f, 0f)

val pvhRotation = PropertyValuesHolder.ofKeyframe("rotation", kf0, kf1, kf2)
ObjectAnimator.ofPropertyValuesHolder(target, pvhRotation).apply {
    duration = 3000
    start()
}
複製代碼

ViewPropertyAnimator

ViewPropertyAnimator是使用單個Animator對象輕鬆爲 View 的多個屬性並行添加動畫效果;

ViewPropertyAnimator它會修改視圖屬性的實際值,但在同時爲多個屬性添加動畫效果時,它更爲高效

ViewPropertyAnimator代碼更加簡潔,也更易讀

下面咱們看看它跟ObjectAnimator的使用區別

多個ObjectAnimator對象

val animX = ObjectAnimator.ofFloat(myView, "x", 50f)
val animY = ObjectAnimator.ofFloat(myView, "y", 100f)
AnimatorSet().apply {
    playTogether(animX, animY)
    start()
}
複製代碼

一個ObjectAnimator對象

val pvhX = PropertyValuesHolder.ofFloat("x", 50f)
val pvhY = PropertyValuesHolder.ofFloat("y", 100f)
ObjectAnimator.ofPropertyValuesHolder(myView, pvhX, pvhY).start()
複製代碼

ViewPropertyAnimator

myView.animate().x(50f).y(100f)
複製代碼

不知不覺,瞎扯了這麼多,好吧 就到這裏了

相關文章
相關標籤/搜索