Android控制元件的fitSystemWindows屬性

NOSAE發表於2019-12-06

官方描述:

根據系統窗體裡的元素比如狀態列來調整View的佈局。如果被設為true,控制元件的padding將會被調整為頂部留出一個statusBar的空間。類似於虛擬碼paddingTop="statusBarHeight"。

重點說明

  1. 當佈局內容可以延伸到狀態列,被狀態列覆蓋時(比如設定了View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,預設不會有這個flag,佈局不會延伸到狀態列下),該屬性才會起作用
  2. 靜態佈局中多個View的fitSystemWindows都為true,只對最外層(如同層,則為第一個)的View起作用
  3. 動態新增子View過程中,只會對第一次新增的子View起作用

上述2、3點和官方描述的行為都是預設行為,而這個行為可以通過自定義View來進行個性化,比如CoordinateLayout就過載了這種行為(可以參考下方連結文章)

多Fragment時fitSystemWindows無效的坑

最近一個專案中,有幾個介面是一個Activity裝載多個Fragment的形式,為了實現沉浸式佈局,將Activity的decorView加上View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN(使佈局延伸到狀態列),所有Fragment的佈局中,則將在最頂部的View(比如Toolbar)的fitSystemWindows設為true。 卻驚訝地發現,只有第一個被加入到Activity的Fragment顯示正常,隨後被新增的Fragment都適配錯誤,如下圖:

Android控制元件的fitSystemWindows屬性

原因

新增Fragment的過程可看作往容器佈局新增子View的過程。當第一個Fragment被新增到容器佈局時,容器佈局找出fitSystemWindows為true的子View,併為其paddingTop一個狀態列的高度,當其他Fragment隨後被新增時,上述的paddingTop適配已經被消費過一次,並不會再為其後新增的View進行適配(預設行為),因此我們要自定義容器佈局View,使其每個子View都消費一次ViewGroup分發的WindowsInsets,相當於每個子Fragment都能適配狀態列

注意

此方法實現的佈局容器會對其每個子View都適配一次

實現程式碼

我一般用FrameLayout作為容器佈局,因此繼承了FrameLayout,每次addView都requestApplyInsets請求分發WindowInsets,並且儲存當前新增的子View,在過載方法onApplyWindowInsets中呼叫子View的dispatchApplyWindowInsets,使每個子View都有機會消費一次insets

class WindowInsetsFrameLayout: FrameLayout {

    private var requestView: View? = null

    constructor(context: Context): this(context, null)
    constructor(context: Context, attrs: AttributeSet?): this(context, attrs, 0)
    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int): super(context, attrs, defStyleAttr) {
        setOnHierarchyChangeListener(object : OnHierarchyChangeListener {
            override fun onChildViewAdded(parent: View?, child: View?) {
                requestView = child
                requestApplyInsets() //子View新增時申請解析inset
            }

            override fun onChildViewRemoved(parent: View?, child: View?) {}
        })
    }

    override fun onApplyWindowInsets(insets: WindowInsets?): WindowInsets? {
        val t = requestView
        return if (t == null) {
            super.onApplyWindowInsets(insets)
        } else {
            val res = t.dispatchApplyWindowInsets(insets) //子View解析
            requestView = null
            res
        }
    }
}
複製程式碼

參考連結

www.twblogs.net/a/5cc8d540b…

www.jianshu.com/p/7bbce110a…

相關文章