Android過渡動畫Scene and Transition(一):使用Transition框架實現(xiàn)場景過渡動畫

Android場景過渡——Scene and Transition(一)

場景過渡動畫

場景過渡動畫是指以動畫的形式實現(xiàn)View場景切換技肩,比如:Acitivty跳轉動畫,F(xiàn)ragment切換動畫浮声,或者兩個View/ViewGroup之間的替換動畫虚婿。相對于View Animation或Property Animator,場景過渡動畫更加具有特殊性泳挥,可以看作是基于特定業(yè)務情景(場景切換)對Property Animator的高度封裝然痊。不同于Animator,場景過度動畫具有特定的關注點屉符,即如何實現(xiàn)具有視覺連續(xù)性的場景切換剧浸。

場景過渡動畫的作用

在Android中,一個Activity的UI通常會響應用戶的輸入或其他事件而作出改變矗钟。例如辛蚊,一個包含搜索篩選條件表單的Activity在用戶提交篩選條件時隱藏表單,并在表單位置顯示搜索結果真仲。++為了在這些情況下提供視覺連續(xù)性袋马,我們就可以在不同的場景之間實現(xiàn)動畫。這些動畫為用戶提供其交互動作的反饋秸应,并引導他們了解應用程序如何工作虑凛。++ 換句話說碑宴,UI動畫的作用不僅僅是為了視覺體驗,動畫強調UI變化并提供視覺引導的特點桑谍,能夠幫助用戶更加了解應用的功能延柠。

Android Transitions Framework

從API 19(4.4.2)開始,Android提供原生Transitions Framework锣披。利用此框架贞间,我們能夠輕松實現(xiàn)兩個場景之間的場景過渡動畫。該框架在運行時通過隨時間改變View的屬性值來實現(xiàn)動畫雹仿。該框架包含一些常見效果的內建動畫增热,并且允許我們創(chuàng)建自定義動畫以及Transition生命周期回調。在API 21(5.0)提供了更加豐富的內建Transition效果胧辽。并且峻仇,提供API 14(4.0)及以上的andorid支持庫。

該框架具有以下功能:

  • 組級動畫邑商,允許將一個或多個動畫效果運用于一個View Hierarchy(以下翻譯為視圖層級結構)的所有Views摄咆;
  • 過渡型動畫,根據開始和結束的View的屬性值變化執(zhí)行動畫人断;
  • 內建動畫吭从,該框架包含多種常見內建動畫效果,例如漸變恶迈,移動影锈;
  • 支持資源文件創(chuàng)建,允許通過資源文件加載View和transition animation蝉绷;
  • 生命周期回調鸭廷,提供回調方法以更好控制動畫和View Hierarchy的變化過程。
image
image

如上圖所示熔吗,transition framework(藍色部分)與view hierarchy以及animations并行工作辆床。starting scene和ending scene分別保存starting layout 和ending layout的狀態(tài),包括它所有的views及views的屬性桅狠。transition保存了一個或多個屬性動畫效果讼载。要執(zhí)行一個transition來實現(xiàn)starting layout過渡到ending layout,需要使用transitionManager中跌,并指定ending scene和需要使用的transition咨堤。

場景過渡動畫主要有兩個關鍵概念:Scene(場景)Transition(過渡)漩符。

創(chuàng)建一個場景

一個Scene保存了一個視圖層級結構一喘,包括它所有的views以及views的狀態(tài)。Transition框架可以實現(xiàn)在starting scene和ending scene之間執(zhí)行動畫。大多數情況下凸克,我們不需要創(chuàng)建starting scene议蟆,因為starting scene通常由當前UI狀態(tài)決定,我們只需要創(chuàng)建ending scene萎战。Transition框架允許我們通過XML文件或Java代碼創(chuàng)建一個Scene咐容。兩種方式都需要使用Scene類。

通過XML創(chuàng)建Scene

XML創(chuàng)建方法主要用于靜態(tài)視圖蚂维,生成的場景表示創(chuàng)建Scene實例時視圖層級結構的狀態(tài) 戳粒。如果視圖層級結構發(fā)生改變,則必須重新創(chuàng)建Scene虫啥。這種方式只能以整個layout文件的視圖層級結構創(chuàng)建Scene蔚约,不能僅僅使用某一部分來創(chuàng)建。

public static Scene getSceneForLayout(ViewGroup sceneRoot, int layoutId, Context context)

從Kotlin代碼中創(chuàng)建

@TargetApi(21)
public Scene(ViewGroup sceneRoot, View layout)

public Scene(ViewGroup sceneRoot, ViewGroup layout)

核心代碼:

  1. sceneRoot:res/layout/activity_main.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"
    android:id="@+id/sceneRoot"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.iyao.transition.MainActivity">
    <include
        android:id="@+id/layouySceneA"
        layout="@layout/scene_a"/>
</android.support.constraint.ConstraintLayout>
  1. sceneA:res/layout/scene_a.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="@dimen/space_16"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <View
        android:id="@+id/view_1"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:background="@drawable/shape_oval_red"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>
  1. sceneB:res/layout/scene_b.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="@dimen/space_16"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <View
        android:id="@+id/view_1"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:background="@drawable/shape_square_orange"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintBottom_toBottomOf="parent"/>
</android.support.constraint.ConstraintLayout>
  1. com/iyao/transition/MainActivity.kt
 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //以代碼的方式創(chuàng)建sceneA孝鹊,只使用xml的一部分
        sceneA = Scene(layoutSceneRoot, layoutSceneA as ViewGroup)
        //以XML的方式創(chuàng)建sceneB,只能以整個XML文件創(chuàng)建Scene對象
        sceneB = Scene.getSceneForLayout(layoutSceneRoot, R.layout.scene_b, this)
    }

創(chuàng)建入場/出場Action

sceneA = Scene(layoutSceneRoot, layoutSceneA as ViewGroup).apply{
            setEnterAction { 
            //入場時調用
            }
            setExitAction { 
            //出場時調用
            }
        }

注意展蒂,不要使用scene action在starting scene和ending scene的views之間傳遞數據又活,action并非場景過渡的生命周期回調方法,兩個action均在transition動畫執(zhí)行之前被調用锰悼。

10-27 14:39:50.164 29218-29218/com.iyao.transition I/action: sceneB exit
10-27 14:39:50.165 29218-29218/com.iyao.transition I/action: sceneA enter
10-27 14:39:50.176 29218-29218/com.iyao.transition I/transitionListener: sceneA transition start
10-27 14:39:51.204 29218-29218/com.iyao.transition I/transitionListener: sceneA transition end

創(chuàng)建并啟動transition

Transition框架中代表過渡動畫的類是Transition柳骄。要想實現(xiàn)一個過渡動畫,需要一個transition實例箕般,一個ending scene實例耐薯。并通過TransitionManager執(zhí)行過渡動畫。
Transition框架中內建了一些常見動畫效果的Transition丝里,例如:Fade(API19)曲初,ChangeBounds(API19),Slide(API21)等杯聚。所有的transitions都繼承自Transition臼婆。Transition同樣支持XML創(chuàng)建和code創(chuàng)建兩種方式。

從XML創(chuàng)建一個transition

res/transition/fade_transition.xml

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

Kotlin代碼中獲取transition實例

val fade = TransitionInflater.from(this)
        .inflateTransition(R.transition.fade_transition)

通過code創(chuàng)建transition

private val fade = Fade().apply {
    //設置transition動畫時長
    duration = 1000
}

執(zhí)行transition

Transition框架通過TransitionManager執(zhí)行過度動畫缓醋,需要傳入一個Scene實例和一個Transition實例(可選)终蒂,如果未傳入Transition實例未傳入故黑,則使用默認的AutoTransition實例。

TransitionManager.go(sceneB)

TransitionManager.go(sceneB, fade)

transitionSet

transitionSet允許我們將多個transition組合使用颁独,類似于animatorSet,使用方式如同單個transition伪冰。因為它同樣繼承自Transition類誓酒。

<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:transitionOrdering="sequential">
    <fade android:fadingMode="fade_out" />
    <changeBounds />
    <fade android:fadingMode="fade_in" />
</transitionSet>

不使用scene執(zhí)行一個transition

TransitionManager類有兩個靜態(tài)方法

public static void beginDelayedTransition(final ViewGroup sceneRoot)

public static void beginDelayedTransition(final ViewGroup sceneRoot, Transition transition)

這兩個方法允許我們不使用Scene類實現(xiàn)過渡動畫,使用方法如下:

TransitionManager.beginDelayedTransition(layoutSceneRoot, fade)
layoutSceneRoot.removeAllViews()      
layoutSceneRoot.addView(layoutSceneA)

此方法需要傳入sceneRoot和一個transition實例(可選)贮聂。調用此方法后丰捷,它會在監(jiān)聽到sceneRoot的視圖層級結構發(fā)生的變化時坯墨,使用傳入的transition實現(xiàn)過渡動畫(如果未傳入transition實例,則使用默認的AutoTransition實例)病往。上面的代碼會在layoutSceneRoot的addView調用后捣染,使用傳入的fade實例實現(xiàn)過渡動畫。

transition生命周期回調

Transition框架的TransitionListener可以讓transitions實現(xiàn)如同Acitivty類似的生命周期回調停巷,用以監(jiān)控過渡動畫執(zhí)行流程耍攘。每一個transition實例都允許使用以下方法實現(xiàn)生命回調。

public Transition addListener(TransitionListener listener)

下面是上例中完整的Kotlin代碼

com/iyao/transition/MainActivity.kt

package com.iyao.transition

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.transition.*
import android.util.Log
import android.view.ViewGroup
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {



    private var sceneA : Scene? = null
    private var sceneB : Scene? = null

   /**
     * 當runnableSceneB使用依賴ViewOverlay實現(xiàn)的Transition實例(如Fade)時畔勤,sceneA持有的layoutSceneA會被
     * 添加到ViewOverlay的實際容器mOverlayViewGroup中蕾各,然后添加一個[Transition.TransitionListener](A)
     * 在[Transition.TransitionListener.onTransitionEnd]中將layoutSceneA從mOverlayViewGroup中移除。
     * 但是由于調用順序的原因庆揪,在創(chuàng)建Transition實例時添加的[Transition.TransitionListener](B)的方法先于
     * A的方法執(zhí)行式曲。此時,將復用對象layoutSceneA再次添加到mSceneRoot中缸榛,就會報
     * java.lang.IllegalStateException: The specified child already has a parent. You must call
     * removeView() on the child's parent first.
     *  if (mLayoutId > 0) {
     *      LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
     *  } else {
     *      mSceneRoot.addView(mLayout);
     *  }
     *  @see Scene.enter #lineNum from 172 to 178
     *  @see Visibility.onDisappear #lineNum from 419 to 432
     */
    private val runnableSceneA: Runnable = Runnable {
        (layoutSceneA.parent as ViewGroup).removeView(layoutSceneA)
        TransitionManager.go(sceneA, changeBounds)
    }

    private val runnableSceneB: Runnable = Runnable {
        TransitionManager.go(sceneB, fade)
    }

    private val fade = Fade().apply {
        duration = 1000
        startDelay = 500
        addListener(object : TransitionListenerAdapter() {
            override fun onTransitionEnd(transition: Transition?) {
                runnableSceneA.run()
            }
        })
    }

    private val changeBounds = ChangeBounds().apply {
        duration = 1000
        startDelay = 500
        addListener(object : TransitionListenerAdapter() {
            override fun onTransitionStart(transition: Transition?) {
                Log.e("transitionListener", "sceneA transition start")
            }
            override fun onTransitionEnd(transition: Transition?) {
                Log.e("transitionListener", "sceneA transition end")
                runnableSceneB.run()
            }
        })
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initialScenes()
        layoutSceneRoot.postDelayed({
            runnableSceneB.run()
        }, 1000)
    }


    private fun initialScenes() {
        @Suppress("DEPRECATION")
        sceneA = Scene(layoutSceneRoot, layoutSceneA as ViewGroup).apply{
            setEnterAction {
                Log.e("action", "sceneA enter")
            }
        }
        sceneB = Scene.getSceneForLayout(layoutSceneRoot, R.layout.scene_b, this).apply {
            setExitAction {
                Log.e("action", "sceneB exit")
            }
        }
    }

    open class TransitionListenerAdapter : Transition.TransitionListener {
        override fun onTransitionEnd(transition: Transition?) {
        }

        override fun onTransitionResume(transition: Transition?) {
        }

        override fun onTransitionPause(transition: Transition?) {
        }

        override fun onTransitionCancel(transition: Transition?) {
        }

        override fun onTransitionStart(transition: Transition?) {
        }
    }
}
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末吝羞,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子内颗,更是在濱河造成了極大的恐慌钧排,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件均澳,死亡現(xiàn)場離奇詭異恨溜,居然都是意外死亡,警方通過查閱死者的電腦和手機找前,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評論 2 384
  • 文/潘曉璐 我一進店門糟袁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人躺盛,你說我怎么就攤上這事系吭。” “怎么了颗品?”我有些...
    開封第一講書人閱讀 156,623評論 0 345
  • 文/不壞的土叔 我叫張陵肯尺,是天一觀的道長。 經常有香客問我躯枢,道長则吟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,324評論 1 282
  • 正文 為了忘掉前任锄蹂,我火速辦了婚禮氓仲,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己敬扛,他們只是感情好晰洒,可當我...
    茶點故事閱讀 65,390評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啥箭,像睡著了一般谍珊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上急侥,一...
    開封第一講書人閱讀 49,741評論 1 289
  • 那天砌滞,我揣著相機與錄音,去河邊找鬼坏怪。 笑死贝润,一個胖子當著我的面吹牛,可吹牛的內容都是我干的铝宵。 我是一名探鬼主播打掘,決...
    沈念sama閱讀 38,892評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼鹏秋!你這毒婦竟也來了尊蚁?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,655評論 0 266
  • 序言:老撾萬榮一對情侶失蹤拼岳,失蹤者是張志新(化名)和其女友劉穎枝誊,沒想到半個月后况芒,有當地人在樹林里發(fā)現(xiàn)了一具尸體惜纸,經...
    沈念sama閱讀 44,104評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年绝骚,在試婚紗的時候發(fā)現(xiàn)自己被綠了耐版。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,569評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡压汪,死狀恐怖粪牲,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情止剖,我是刑警寧澤腺阳,帶...
    沈念sama閱讀 34,254評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站穿香,受9級特大地震影響亭引,放射性物質發(fā)生泄漏。R本人自食惡果不足惜皮获,卻給世界環(huán)境...
    茶點故事閱讀 39,834評論 3 312
  • 文/蒙蒙 一焙蚓、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦购公、人聲如沸萌京。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽知残。三九已至,卻和暖如春绘闷,著一層夾襖步出監(jiān)牢的瞬間橡庞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評論 1 264
  • 我被黑心中介騙來泰國打工印蔗, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留扒最,地道東北人。 一個月前我還...
    沈念sama閱讀 46,260評論 2 360
  • 正文 我出身青樓华嘹,卻偏偏與公主長得像吧趣,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子耙厚,可洞房花燭夜當晚...
    茶點故事閱讀 43,446評論 2 348

推薦閱讀更多精彩內容