趕緊給你的 layout 變化加上動(dòng)畫吧!

什么是給 layout 變化加上動(dòng)畫?

其實(shí)很好理解,就是在 layout,也就是我們說的布局內(nèi)容發(fā)生變化的時(shí)候,給它添加上一個(gè)過渡的動(dòng)畫,使其看起來更顯得自然一些。
其實(shí) Android 自帶的 framework 已經(jīng)為我們提供了這么一個(gè)能力,它可以在兩個(gè) layout 變化的時(shí)候加上動(dòng)畫,使 layout 的變化更加自然。這么好的功能為什么不用呢?而且實(shí)際上,它的使用也并不復(fù)雜,只需短短的幾行代碼,就可以提升用戶的操作體驗(yàn),何樂而不為。

about transition framework

  • Group-level animations: Apply one or more animation effects to all of the views in a view hierarchy.
  • Built-in animations: Use predefined animations for common effects such as fade out or movement.
  • Resource file support: Load view hierarchies and built-in animations from layout resource files.
  • Lifecycle callbacks: Receive callbacks that provide control over the animation and hierarchy change process.

根據(jù)官網(wǎng)文檔所示,transition framework 有幾個(gè)重要的特性:

  1. 組級別動(dòng)畫:支持運(yùn)用一個(gè)或多個(gè)動(dòng)畫到視圖層級上。
  2. 內(nèi)建動(dòng)畫:framework 已經(jīng)為我們提供了很多的內(nèi)置動(dòng)畫,比如你可以使用淡入淡入、位移動(dòng)畫等。
  3. 支持資源文件:構(gòu)建場景 scene 時(shí),支持資源文件直接導(dǎo)入,當(dāng)然,同時(shí)它也是支持 view 導(dǎo)入的。
  4. 生命周期回調(diào):可以監(jiān)聽到動(dòng)畫執(zhí)行的聲明周期,提供 start、resume、pause 等。

那么,transition framework 是怎么如何實(shí)現(xiàn) layout 變化動(dòng)畫效果的?layout、scene、transition 他們之間的關(guān)系又是什么樣的?我們看一張官方貼圖。


來源于官方文檔.png

首先,我們將起始 layout 以及結(jié)束 layout 轉(zhuǎn)化成相對應(yīng)的場景。
然后,創(chuàng)建想要執(zhí)行的 transition。
最后,通過 TransitionManager 將場景和 transition 綁定在一起執(zhí)行。
這樣三步,就可以實(shí)現(xiàn)從場景一到場景二的動(dòng)畫轉(zhuǎn)變了。

如何給 layout 變化加上動(dòng)畫

scene.gif

今天就以這個(gè)為例子說一下如何給 layout 變化加上平滑的動(dòng)畫。
從圖中可以看到,點(diǎn)擊機(jī)器人的時(shí)候,layout 改變了,機(jī)器人變大,并且名字也移動(dòng)到右邊,同時(shí),詳情介紹也出現(xiàn)了。返回的時(shí)候,layout 中的元素也平滑的回到之前的狀態(tài)。這就是用 scene 和 transition 實(shí)現(xiàn)的。

1. 創(chuàng)建場景 scene

  • 創(chuàng)建 layouts

layout_first.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/layout_container"
        android:layout_width="wrap_content" android:layout_height="wrap_content">

    <ImageView
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            android:src="@mipmap/ic_launcher"
            android:id="@+id/image_icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    <TextView
            app:layout_constraintLeft_toRightOf="@id/image_icon"
            app:layout_constraintTop_toTopOf="@id/image_icon"
            app:layout_constraintBottom_toBottomOf="@id/image_icon"
            android:layout_marginLeft="16dp"
            android:textSize="20sp"
            android:id="@+id/text_name"
            android:text="頭像"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

</android.support.constraint.ConstraintLayout>

layout_second.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/layout_container"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <ImageView
            android:layout_marginTop="20dp"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            android:minHeight="200dp"
            android:minWidth="200dp"
            android:src="@mipmap/ic_launcher"
            android:id="@+id/image_icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    <TextView
            app:layout_constraintLeft_toLeftOf="@id/image_icon"
            app:layout_constraintRight_toRightOf="@id/image_icon"
            app:layout_constraintTop_toBottomOf="@id/image_icon"
            android:layout_marginTop="16dp"
            android:textSize="28sp"
            android:id="@+id/text_name"
            android:text="頭像"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    <TextView
            android:id="@+id/text_detail"
            android:layout_marginTop="20dp"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintTop_toBottomOf="@id/text_name"
            android:layout_width="wrap_content"
            android:textSize="18sp"
            android:padding="16dp"
            android:text="這是詳情這是詳情這是詳情這是詳情
            這是詳情這是詳情這是詳情這是詳情這是詳情這是詳情
            這是詳情這是詳情這是詳情這是詳情這是詳情這是詳情
            這是詳情這是詳情這是詳情這是詳情這是詳情這是詳情
            這是詳情這是詳情這是詳情"
            android:layout_height="wrap_content"/>


</android.support.constraint.ConstraintLayout>

注意:這里兩個(gè)場景 layout 的相應(yīng)控件的 id 需要設(shè)置成一樣的。

activity.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".LayoutWithSActivity">

    <FrameLayout
            android:id="@+id/layout_container"
            app:layout_constraintTop_toTopOf="parent"
            android:layout_width="match_parent"
            app:layout_constraintBottom_toBottomOf="parent"
            android:layout_height="0dp">
        
    </FrameLayout>
    
</android.support.constraint.ConstraintLayout>
  • 根據(jù) layout 生成相應(yīng)的 scene

生成 scene 的方式有兩種,可以通過 view 生成一個(gè)場景 scene,也可以直接加載布局文件生成 scene。

通過布局文件直接生成 scene

val sceneFirst = Scene.getSceneForLayout(layout_container, R.layout.scene_first, this)
val sceneSecond = Scene.getSceneForLayout(layout_container, R.layout.scene_second, this)

通過 view 直接生成 scene

val firstView = layoutInflater.inflate(R.layout.scene_first, null)
val secondView = layoutInflater.inflate(R.layout.scene_second, null)

val sceneFirst = Scene(layout_container, firstView)
val sceneSecond = Scene(layout_container, secondView)

第一種方式方便,直接加載布局文件就可以了,但是不易控制里面的元素。拿本例來說,因?yàn)橐刂莆淖值母淖?,所以采取第二種通過 view 的方式來生成。

2. 創(chuàng)建 transition

創(chuàng)建 transition 有兩種方式,可以在 xml 中定義,也可以只直接在代碼中定義。

  • 在 xml 中定義 transition

在 res/transition 下定義 transition.xml

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android">
    <changeBounds/>
    <fade/>
    <slide android:slideEdge="right"/>
</transitionSet>

transitionSet 是一個(gè)組合,可以同時(shí)定義多個(gè) transition
然后再代碼中加載:

val inflateTransition = TransitionInflater.from(this).inflateTransition(R.transition.layout_transform)
  • 在代碼中直接生成
al transition = ChangeBounds()

內(nèi)置的 transition 大概有這么多個(gè):


內(nèi)置 transition

AutoTransition 是默認(rèn)的transition,F(xiàn)ade out,move and resize,and fade in views。
ChangeBounds 改變邊界,栗子中我們用的就是這種。
其他的大家自己可以去查看下源碼。

2. 應(yīng)用 transition

應(yīng)用 transition 非常的簡單

TransitionManager.go(scene, transition)

在栗子中,點(diǎn)擊機(jī)器人就會(huì)轉(zhuǎn)變到場景 2,并且文字也發(fā)生變化,那么我們可以這么寫

firstView.setOnClickListener {
            secondView.findViewById<TextView>(R.id.text_name).text = "從前置過來"
            TransitionManager.go(sceneSecond, transition)
        }

One More Thing

事實(shí)上,還有一種,不需要借助 scene 來實(shí)現(xiàn)的變化,那就是通過TransitionManager.beginDelayedTransition()。在執(zhí)行TransitionManager.beginDelayedTransition()之后,系統(tǒng)會(huì)保存一個(gè)當(dāng)前視圖樹狀態(tài)的場景,之后當(dāng)我們改變了View的屬性之后(比如重新設(shè)置了View位置、縮放、clipe等等)。在下一次繪制時(shí),系統(tǒng)會(huì)自動(dòng)對比之前保存的視圖樹,然后執(zhí)行相應(yīng)動(dòng)畫。

實(shí)現(xiàn)的代碼很簡單

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

        val inflateTransition = TransitionInflater.from(this).inflateTransition(R.transition.layout_transform)

        btn_add.setOnClickListener {
            val text = TextView(this).apply {
                text = "I am new"
            }

            TransitionManager.beginDelayedTransition(layout_items_container, Fade())
            layout_items_container.addView(text)
        }

        btn_remove.setOnClickListener {
            TransitionManager.beginDelayedTransition(layout_items_container, inflateTransition)
            if (layout_items_container.childCount == 0) return@setOnClickListener
            layout_items_container.removeViewAt(0)
        }
    }

寫在最后

文中若有表述不恰當(dāng)?shù)牡胤剑埡侠碇赋?,共勉?/p>

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

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

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