Material Design 踩坑系列 Motion — Container transform

Voss
9 min readJun 3, 2022

身為一個專業菜雞的前端工程師,踩坑在所難免,但不踩你也不會知道是坑,所謂越踩越勇、吃坑當吃補、踩坑為成功之母

今天就來踩看看Material Design 的Motion

先簡單透過官方的介紹來稍微看看,知曉了有四種動畫的模式

  • Container transform
  • Shared axis
  • Fade Through
  • Fade

因為一開始的Container transform 就遇到非常多坑,導致其他模式還沒時間去碰,就先單純介紹這個模式,而它也算是現今許多主流媒體App會用到的動畫呈現方式

Container transform

顧名思義,就是在容器裡面切換頁面,例如我們常用的FragmentContainer

這個動畫使用的場景,通常是與cardView一起使用,透過點擊展開與收放,讓使用者體驗上感覺是在同一個介面,但在程式裡面,卻是透過Motion的動畫呈現,來優雅切換Fragment

Transition between Fragments

那就實際來運用看看,假如今天我們要切換 A B Fragment

  1. transitionName

必須在我們要執行動畫開始與結束的元件裡面,設定transitionName,而這個類似key,連結的元件都必須設定相同的key,所以假如今天是點選一個cardView or ImageView,就必須在裡面的attribute設定,系統就會透過相同的name來完成判斷並達成跳轉的功能

2. shareElementEnterTransition / shareElementReturnTransition

在跳轉的fragment之前,必須在要transition的fragment 設定當下的shareElementEnter./Retrun Transition

跳轉動畫,直接使用官方給予的MaterialContainerTransform 物件即可

我們有兩種方式可以使用,第一個是在交易之前,把B Fragment的Transition設定好,第二個是 在B Fragment OnCreate 的時候去設定屬性

不過第一種的方式只適用於 supportFragmentManager ,因為當中可以在跳轉前去添加shareElement,像我本身是使用navigation就無法,所以必須是用第二個方法添加,再要跳轉到的Fragment OnCreate的時候去設定它,必須在View生成之前完成

如果是使用Navigation,就必須生成一個extras,放入navigate當中,裡面必須放入View (要過渡動畫的View)、TransitionName

3. MaterialContainerTransform

而動畫的部分可以做自定義或是一些調整,像是動畫的時間、動畫背景的顏色、動畫的模式等等 (詳情請看下圖表格)

那上述的步驟,基本上只是開頭的簡單介紹,也是官方大概略的介紹,真的只是大概 -.- ,因為這些無法滿足許多場景的需求,必須去考慮的一些元件的特性與Fragment的生命週期

踩坑環節

因為我今天的需求,是要在一個recyclerView的item ImageView上面點擊跳轉到更加 detail 的Fragment 畫面,例如點擊書本的圖片,之後會轉到呈現書本細節資料的畫面,其實就跟官方所demo的效果一樣,只不過需要考慮到一些細節

  1. recyclerView & transitionName

因為recyclerView是透過拿取item Layout 並與adapter,不斷的複用子View去變動畫面,所以如果我們把transitionName寫死在xml當中,那每個item View的transitionName都會是相同,所以系統就無法辨識到底是誰要跟誰去shareElement,而且每個畫面中不能有重複的transitionName

所以在使用recyclerView的情境之下,我們必須去動態生成每個item的transitionName,在 ViewHolder 綁定時,為每個itemView的給予不重複的transitionName。那這邊就用item的position來做為 transitionName

這樣每個item都會依據自身的position來設定為book1、book2…的轉換名稱

接下來就可以去Fragment 生成adapter並去覆寫我們的onClickCard讓imageView 點擊之後,啟動navigate 導航到detail的頁面

這邊我把transitionName 透過navigation的 args 傳遞過去detail頁面,而extras就是剛剛上面提到的,navigation傳送view與transitionName的方式

最後我們到detail頁面去接收args傳過來的transitionName,並設定在要呈現View 的 transitionName

這樣就大功告成了~ 成功克服recylerView的問題….. 才怪

2. recyclerView & Fragment & Postponing transitions

原本我是真的這麼認為,但後來發現進入的動畫沒問題,但返回卻無效

而且重點是,如果我刻意把進入動畫調到10秒這麼長,在動畫結束之前返回的話就成功了,當時的我瞬間當機,差點怒摔測試機 (/‵Д′)/~ ╧╧

但後來與友人討論後,開始查詢官方文檔,發現真的有寫,只不過不是在

Material Motion裡面,而是在Fragment (下方連結)

其中提到了一個關鍵的東西 Postponing transitions,也就是延遲過場動畫

因為transition開始前,必須先知道等等要轉入的 View,所以需要等到進入的所有視圖都被測量和佈局,才可準確地捕獲它們的開始和結束狀態以進行transition

所以在某些場景串接數據呈現會有延遲的時候,導致transition無法第一時間捕捉到畫面,就無法成功,必須透過執行延遲轉場動畫,讓View完整呈現後才去執行動畫

繼續看下去,提到了RecyclerView

在進入Fragment中的所有視圖都被測量和佈局之前,不應開始延遲進入轉換。使用 RecyclerView 時,必須等待任何數據加載並等待RecyclerView itemView 準備好繪製,然後才能開始轉換

上述也就解釋了,為何返回動畫無效,因為每個itemView都在等adapter給予資料並刷新頁面,但第一時間transition捕捉到是未加載的畫面,所以才失敗

我們只要在注入資料之前,把transititon延遲,再透過viewTreeObserve去監聽View是否畫完,才開始執行transition,這樣就能確保捕捉到畫面,動畫也就能如預期執行了~

不過還有一個問題,前面提到的轉入動畫到一半,為何執行返回就有效?

仔細想一下,應該是生命週期的問題,也就是執行動畫的過程,Fragment 的生命週期 是如何執行的

所以就在剛剛的情境之下,加入Log看了一下,果不其然!!

在動畫執行當下,要跳轉的畫面已被生成,但前一個畫面還沒被 Destroy,只是 onStop,所以才能在跳轉動畫尚未結束的當下,能成功執行返回的動畫因為前一個 Fragment 還沒被 Destroy。所以transition有成功捕捉到畫面,但如果動畫結束後完成跳轉,navigation預設的跳轉是replace,所以Fragment就被Destroy了,那就必須重新產生,而如果沒有去設定延遲,那就無法捕捉到完整被adapter注入資料之後的recylerView

以上就是我踩了許多關於Material Motion的坑

希望能幫助到許多想入坑的人 如有問題或錯誤 歡迎留言討論~

參考資料 :

--

--

Voss

I’m Voss , a Software Engineer , a Reader , a Body Builder