1. FitSystemWindow
在 layout 布局中設(shè)置 android:fitsSystemWindows="true"
到底發(fā)生了什么畦贸?
默認(rèn)的 View的行為是在該屬性設(shè)置后霎箍,系統(tǒng)通過在布局中預(yù)留出padding,使得布局有個(gè)相對(duì)的偏移舱权。特別的, 在頂層layout是 CoordinatorLayout
逾冬,DrawerLayout
包吝,或是CollapsingToolbarLayout
時(shí)锥余,這些layout (相比一般的布局腹纳,如FrameLayout
) 已經(jīng)有了自定義的行為。
1.1 以 CoordinatorLayout 為例
if (ViewCompat.getFitsSystemWindows(this)) {
if (mApplyWindowInsetsListener == null) {
mApplyWindowInsetsListener =
new androidx.core.view.OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
return setWindowInsets(insets);
}
};
}
// First apply the insets listener
ViewCompat.setOnApplyWindowInsetsListener(this, mApplyWindowInsetsListener);
// Now set the sys ui flags to enable us to lay out in the window insets
setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
}
在5.0+系統(tǒng)中驱犹, 其方法setupForInsets()
會(huì)檢測(cè) fitsSystemWindows
屬性是否有設(shè)置過嘲恍,如果有,則設(shè)置當(dāng)前View的屬性為View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
然后進(jìn)一步調(diào)用dispatchApplyWindowInsetsToBehaviors
雄驹,將WindowInsetsCompat
傳遞個(gè)也設(shè)置過fitsSystemWindows
子view的Behavior
class,直至WindowInsetsCompat
被最終消費(fèi)掉(consumed)佃牛。
private WindowInsetsCompat dispatchApplyWindowInsetsToBehaviors(WindowInsetsCompat insets) {
if (insets.isConsumed()) {
return insets;
}
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
if (ViewCompat.getFitsSystemWindows(child)) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Behavior b = lp.getBehavior();
if (b != null) {
// If the view has a behavior, let it try first
insets = b.onApplyWindowInsets(this, child, insets);
if (insets.isConsumed()) {
// If it consumed the insets, break
break;
}
}
}
}
return insets;
}
1.2 Inset issue (e.g. Fragment transition)
在單Activity 多Fragment的UI結(jié)構(gòu)下,可能出現(xiàn)多個(gè)Fragment的布局都需要處理 Window insets的情況医舆。而實(shí)際上ViewGroup#dispatchApplyWindowInsets()
默認(rèn)的實(shí)現(xiàn)中是會(huì)遍歷子View (DFS
)俘侠,開始dispatch window insets 直至有子view消費(fèi)了insets。這就意味著彬向,一旦有子view消費(fèi)了insets兼贡,后續(xù)的子view就不會(huì)再有機(jī)會(huì)處理insets。
所以對(duì)應(yīng)的解決方法就是在Fragment的 Container中借助OnApplyWindowInsetsListener
改寫處理insets的機(jī)制娃胆。
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
}
1.3 代碼實(shí)踐
如果你使用了
CoordinatorLayout
或是DrawerLayout
,而且想要在system bars (包括status bar) 之下顯示 View等曼,可在其直接的子View中(遞歸地)指定android:fitsSystemWindows="true"
里烦。對(duì)于當(dāng)前status Bar顏色的設(shè)置,一般地為了能讓自定義的view填充status bar的background區(qū)域禁谦,statusBarColor通常設(shè)置為transparent :
Window#setStatusBarColor(int)
或在Activity Theme中指定為:
<item name="android:statusBarColor">@android:color/transparent</item>
如果想要獲取status bar的高度胁黑,不必使用hardcoded value (24 dp) 或是讀取系統(tǒng)resource,可通過監(jiān)聽系統(tǒng)
WindowInsets
實(shí)現(xiàn):
myView.setOnApplyWindowInsetsListener { view, insets ->
val statusBarSize = insets.systemWindowInsetTop
return insets
}
2. Fragment中添加 Option Menu
一般來說州泊,常見的對(duì)于ActionBar的處理是放在Host Activity中丧蘸。但根據(jù)特定的的UI設(shè)計(jì),F(xiàn)ragment中可能都有自定義的ActionBar遥皂,故將ActionBar的建立以及menu的響應(yīng)處理分散到各自的Fragment中力喷,這是更為內(nèi)聚的做法刽漂。
常見的在Fragment中使用Options Menu 需要注意:
2.1 啟用menu
在Fragment中設(shè)置setHasOptionsMenu(true)。
2.2 ActionBar 的對(duì)應(yīng)更新
由于其它Fragment中也有設(shè)置 menu弟孟,在切換Fragment后贝咙,當(dāng)前Activity關(guān)聯(lián)的的ActionBar可能已經(jīng)不是對(duì)應(yīng)到當(dāng)前的Fragment了,此時(shí)需要重新設(shè)置當(dāng)前的ActionBar :
// toolbar in current Fragment
setSupportActionBar(toolbar)