如何自定義屬于自己的 Transition

WHY?

我們先來看一張圖

before.gif

這是我們只用了 ChangeBounds 這一個(gè) Transition 的情況。我們 focus 到色塊字體處债蜜,可以看到饱搏,當(dāng)兩個(gè)場(chǎng)景進(jìn)行切換的時(shí)候,位置和 bounds 的變化都有過渡效果寥殖,看起來很自然,但是色塊背景顏色的變化以及色塊內(nèi)字體的變化涩蜘,都有種閃現(xiàn)的感覺嚼贡,視覺上效果并不是很好。其實(shí)這個(gè)效果是符合 ChangeBounds 的同诫,查看 ChangeBounds 的源碼就可以發(fā)現(xiàn)粤策,它內(nèi)部并沒有對(duì)字體顏色及背景色做過渡動(dòng)畫。所以误窖,要實(shí)現(xiàn)字體顏色和背景顏色能平滑過渡叮盘,我們就需要自定義 Transition。

如何自定義屬于自己的 Transition

通過前面的介紹霹俺,我們已經(jīng)知道了 Transition 有兩個(gè)主要任務(wù):

  1. 捕獲前后想要跟蹤的屬性值柔吼,
  2. 根據(jù)屬性值變化構(gòu)建出自己的動(dòng)畫并執(zhí)行。

所以自定義 Transition 丙唧,我們就圍繞著兩個(gè)方向去處理就可以了愈魏。其實(shí)自定義 Transition 也比較簡單,大致上分為兩步:

  1. 繼承 Transition 并實(shí)現(xiàn) captureStartValuescaptureEndValues 兩個(gè)方法想际。
  2. 重寫 createAnimator 培漏,根據(jù) startValues 和 endValues 創(chuàng)建自己的 Animator

下面就以平滑改變背景色作為栗子

繼承 Transition 并實(shí)現(xiàn) captureStartValuescaptureEndValues

class BackgroundColorTransition : Transition() {
    private val PROPNAME_BACKGROUND ="com.wangguan.transitions.material.custom.BackgroundColorTransition:background"

    override fun captureStartValues(transitionValues: TransitionValues?) {
        captureValues(transitionValues)
    }

    override fun captureEndValues(transitionValues: TransitionValues?) {
        captureValues(transitionValues)
    }

    private fun captureValues(transitionValues: TransitionValues?) {
        // 將需要跟蹤的屬性塞到 values 里面
        transitionValues?.also {
            it.values[PROPNAME_BACKGROUND] = it.view.background
        }
    }

這里我們都實(shí)現(xiàn)了 ** captureStartValues** 和 ** captureEndValues** ,這兩個(gè)方法都是捕獲想要跟蹤的屬性胡本,所以里面的具體實(shí)現(xiàn)牌柄,我們調(diào)用了 captureValues 方法來捕獲屬性。transitionValues 包含一個(gè) view 和一個(gè)屬性的 map侧甫,這里我們需要將我們要跟蹤的屬性塞進(jìn)這個(gè) map 里面友鼻。這里我們關(guān)心的是 view 的背景顏色傻昙,所以將 view 的 background 塞進(jìn) key 為 PROPNAME_BACKGROUND 的 map 里面。

為了保證 key 的唯一性彩扔,官方建議 key 的聲明最好遵循以下格式:

package_name:transition_name:property_name

這里我們就將需要跟蹤的屬性告知 Transition 了妆档,接下來就是創(chuàng)建相應(yīng)動(dòng)畫了。

重寫 createAnimator

    override fun createAnimator(
        sceneRoot: ViewGroup?,
        startValues: TransitionValues?,
        endValues: TransitionValues?
    ): Animator? {
        startValues ?: return null
        endValues ?: return null

        val view: View = endValues.view

        // 根據(jù)屬性 key 取出前后的背景
        val startBackground = startValues.values[PROPNAME_BACKGROUND] as Drawable?
        val endBackground = endValues.values[PROPNAME_BACKGROUND] as Drawable?


        // 格式判斷
        if (startBackground is ColorDrawable && endBackground is ColorDrawable) {

            // 前后顏色發(fā)生改變才執(zhí)行動(dòng)畫
            if (startBackground.color != endBackground.color) {
                val animator = ValueAnimator.ofObject(
                    ArgbEvaluator(),
                    startBackground.color, endBackground.color
                )
                animator.addUpdateListener { animation ->
                    val value = animation.animatedValue

                    if (null != value) {
                        // 不斷更新背景以達(dá)到平滑過渡效果
                        view.setBackgroundColor(value as Int)
                    }
                }

                return animator
            }
        }

        return null
    }

當(dāng)這個(gè)方法被 framework 調(diào)用的時(shí)候虫碉,它會(huì)給我們傳場(chǎng)景的根視圖和 startValues 和 endValues 贾惦。通過 key 就可以拿到我們關(guān)心的屬性值了,拿到前后屬性值敦捧,就可以通過前后的變化創(chuàng)建我們自己的 animator须板。至此,一個(gè)簡單的 BackgroundColorTransition 就完成了兢卵∠肮澹看下最終效果:


after.gif

One More Thing

那么在一次 Transition 變化中, createAnimator 會(huì)執(zhí)行多少次呢秽荤?Transition 本來就是將前后 scene 做差異動(dòng)畫的甜奄,所以,createAnimator 的調(diào)用次數(shù)其實(shí)就和前后 scene 有關(guān)窃款。
就拿本例來看

scene_first.xml

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

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

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

</androidx.constraintlayout.widget.ConstraintLayout>

scene_second.xml

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

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

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

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


</androidx.constraintlayout.widget.ConstraintLayout>

從以上可以看到课兄,場(chǎng)景二較場(chǎng)景一多了 id 為 text_detail 的 TextView,它在場(chǎng)景一中是找不到相對(duì)應(yīng) id 控件的晨继,那么 createAnimator 會(huì)回調(diào)嗎烟阐?答案是會(huì)的,它會(huì)從 null 變到 text_detail紊扬,所以蜒茄,就本例來說,createAnimator 一共回調(diào)了3 次餐屎。

androidx.appcompat.widget.AppCompatImageView{aec2280 V.ED..... ......ID 0,0-216,216 #7f0800a5 app:id/image_icon}  
androidx.appcompat.widget.AppCompatImageView{78b6a29 V.ED..... ......ID 0,0-216,216 #7f0800a5 app:id/image_icon}

createAnimator androidx.appcompat.widget.AppCompatTextView{80de0b9 V.ED..... ......ID 264,52-432,164 #7f080140 app:id/text_name}  
androidx.appcompat.widget.AppCompatTextView{56ae1ae V.ED..... ......ID 264,52-432,164 #7f080140 app:id/text_name}

createAnimator null  
androidx.appcompat.widget.AppCompatTextView{9d94b4f V.ED..... ......ID 0,880-1080,1427 #7f080138 app:id/text_detail}

最后

BackgroundColorTransition.kt
祝好
共勉
不喜勿噴

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末檀葛,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子啤挎,更是在濱河造成了極大的恐慌驻谆,老刑警劉巖卵凑,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件庆聘,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡勺卢,警方通過查閱死者的電腦和手機(jī)伙判,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來黑忱,“玉大人宴抚,你說我怎么就攤上這事勒魔。” “怎么了菇曲?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵冠绢,是天一觀的道長。 經(jīng)常有香客問我常潮,道長弟胀,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任喊式,我火速辦了婚禮孵户,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘岔留。我一直安慰自己夏哭,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布献联。 她就那樣靜靜地躺著竖配,像睡著了一般。 火紅的嫁衣襯著肌膚如雪酱固。 梳的紋絲不亂的頭發(fā)上械念,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音运悲,去河邊找鬼龄减。 笑死,一個(gè)胖子當(dāng)著我的面吹牛班眯,可吹牛的內(nèi)容都是我干的希停。 我是一名探鬼主播,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼署隘,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼宠能!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起磁餐,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤违崇,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后诊霹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體羞延,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年脾还,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了伴箩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鄙漏,死狀恐怖嗤谚,靈堂內(nèi)的尸體忽然破棺而出棺蛛,到底是詐尸還是另有隱情,我是刑警寧澤巩步,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布旁赊,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜你踩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望声离。 院中可真熱鬧,春花似錦瘫怜、人聲如沸术徊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赠涮。三九已至,卻和暖如春暗挑,著一層夾襖步出監(jiān)牢的瞬間笋除,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國打工炸裆, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留垃它,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓烹看,卻偏偏與公主長得像国拇,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子惯殊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359