[譯] WindowsInsets 和 Fragment 過渡動畫

一個悲傷的故事

這篇文章是我寫的關於 fragment 過渡動畫的小系列中的第二篇。第一篇能夠經過下面的連接查看,裏面寫了如何讓 fragment 過渡動畫開始工做。html


在我開始進一步探討以前,我會假設你知道什麼是 WindowsInsets 以及它們是如何分發的。若是你不知道,我建議你先看這個演講(是的,這是個人演講 🙋)前端


我須要坦白。當我在寫本系列第一篇博客文章的時候,我對視頻作了點手腳。實際上我遇到了 WindowInsets 的問題,也就是說我實際上最終獲得的是如下結果:java

過渡動畫破壞了狀態欄的效果。android

Woops,跟我在第一篇文章中展現的效果不太同樣 🤐。我不想讓第一篇文章變得太複雜,因此決定單獨寫這篇文章。不管如何,你能夠看到當添加過渡動畫以後,咱們忽然失去了全部狀態欄的效果,並且視圖被推到狀態欄的下面。ios

問題

這兩個 fragment 爲了在系統欄下面進行繪製都大量使用了 WindowInsets。Fragment A 使用了 CoordinatorLayoutAppBarLayout,而 Fragment B 使用自定義 WindowInsets 來處理(經過一個 OnApplyWindowInsetsListener)。不管它們是如何實現的,過渡動畫都會混淆二者。git

那麼爲何會這樣呢?其實當你在使用 fragment 過渡動畫時,退出(Fragment A)和進入(Fragment B)的內容視圖實際上經歷瞭如下幾個過程:github

  1. 過渡動畫開始。
  2. 由於咱們對 Fragment A 使用了一個退出的過渡動畫,因此 View A 還留在原來的位置,過渡動畫在上面運行。
  3. View B 被添加到內容視圖裏面,而且被當即設置成不可見。
  4. Fragment B 的進入動畫和「共享元素進入」過渡動畫開始執行。
  5. View B 被設置成可見的。
  6. 當 Fragment A 的退出動畫結束的時候,View A 從容器視圖中移除。

這一切聽起來都很好,那爲何會忽然影響到 WindowInsets 的效果呢?這是由於在過渡的過程當中,兩個 fragment 的視圖都存在於容器中。windows

可是這聽起來徹底 OK 啊,不是嗎?然而在個人場景中,這兩個 fragment 的視圖都想要處理和消費 WindowInsets,由於它們都指望在屏幕上顯示惟一的「主」視圖。但是隻有其中的一個視圖會收到 WindowInsets:也就是第一個子 view。這取決於 ViewGroup 是如何分發 WindowInsets 的,也就是經過按順序遍歷它的子節點直到其中的一個消費了 WindowInsets。 若是第一個子 view(就是這裏的 Fragment A)消費了 WindowInsets,任何後續的子 view(就是這裏的 Fragment B)都不會獲得它們,咱們最終就會獲得這種狀況。後端

讓咱們再來一步一步檢查一遍,只是這一次加上分發 windowinsets 的時機:app

  1. 過渡動畫開始。
  2. 由於咱們對 Fragment A 使用了一個退出的過渡動畫,因此 View A 還留在原來的位置,過渡動畫在上面運行。
  3. View B 被添加到內容視圖裏面,而且被當即設置成不可見。
  4. 分發 WindowInsets。咱們但願 View B(child 1)拿到它們,可是 View A(child 0)又一次拿到了 WindowInsets。
  5. Fragment B 的進入動畫和‘共享元素進入’過渡動畫開始執行。
  6. View B 被設置成可見的。
  7. 當 Fragment A 的退出動畫結束的時候,View A 從容器視圖中移除。

修復

這個修復實際上相對簡單:咱們只須要確保兩個視圖都可以拿到 WindowInsets。

我實現這一點的方法是經過在容器視圖(在這個例子中就是在宿主 activity)裏添加一個 OnApplyWindowInsetsListener,它會手動分發 WindowInsets 給全部的子 view,直到其中一個子 view 消費掉這個 WindowInsets。

fragment_container.setOnApplyWindowInsetsListener { view, insets ->
	var consumed = false

	(view as ViewGroup).forEach { child ->
		// Dispatch the insets to the child
		val childResult = child.dispatchApplyWindowInsets(insets)
		// If the child consumed the insets, record it
		if (childResult.isConsumed) {
  			consumed = true
		}
	}

	// If any of the children consumed the insets, return
	// an appropriate value
	if (consumed) insets.consumeSystemWindowInsets() else insets
}
複製代碼

在咱們應用這個修復以後,這兩個 fragment 都會收到 WindowInsets,而後咱們就會獲得第一篇文章中實際顯示的結果:


額外部分 💃: 必定要進行請求

還有一件我差點忘了寫的小事。若是你要在 fragment 裏面處理 WindowInsets,不管是隱式(經過使用 AppBarLayout 等)仍是顯式,你須要確保請求了一些 WindowInsets。只須要調經過 requestApplyInsets() 就能很容易作到:

override fun onViewCreated(view: View, icicle: Bundle) {
	super.onViewCreated(view, savedInstanceState)
	// yadda, yadda
	ViewCompat.requestApplyInsets(view)
}
複製代碼

你必須這樣作是由於窗口只有在整個視圖層級整體的系統 UI 可見性的值發生改變的時候纔會自動分發 WindowInsets。 因爲有時你的兩個 fragment 可能提供徹底相同的值,整體的值不會改變,所以系統將忽略這個「改變」。


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章
相關標籤/搜索