基于 Android 9 源码。
NavigationBar 和 StatusBar 都属于 SystemBar,也叫做 decor,就是说给 App 装饰的意思。一般的 window 的布局是在 PhoneWindowManager 的 layoutWindowLw()
方法中,而 SystemBar 是在 beginLayoutLw()
方法中布局。
当前最上层的 Activity 可以修改 SystemBar 的 visibility,可以调用 View#setSystemUiVisibility()
方法,系统也有一些针对 SystemBar visibility 的策略。最终的 visibility 保存在 PhoneWindowManager 中的 mLastSystemUiFlags
变量中。
PhoneWindowManager 实现了 WindowManagerPolicy 接口。beginLayoutLw()
中会调用 layoutNavigationBar()
方法布局 NavigationBar。
layoutNavigationBar()
方法会先调用 navigationBarPosition()
方法返回 NavigationBar 的位置,根据不同的位置布局方式不同。以布局在下边为例,会计算 NavigationBar 的 top,然后更新 mTmpNavigationFrame
和 DisplayFrames 的相关属性。其中比较重要的是更新 DisplayFrames.mDock
变量,然后用这个变量设置 mCurrent
、mVoiceContent
、mContent
等变量。DisplayFrames 的各个属性在「DisplayFrames 分析:Android 中都有哪些 Frame?」有说明。
// frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
public class PhoneWindowManager implements WindowManagerPolicy {
private boolean layoutNavigationBar(DisplayFrames displayFrames, int uiMode, Rect dcf,
boolean navVisible, boolean navTranslucent, boolean navAllowedHidden,
boolean statusBarExpandedNotKeyguard) {
if (mNavigationBar == null) {
return false;
}
boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
// Force the navigation bar to its appropriate place and size. We need to do this directly,
// instead of relying on it to bubble up from the nav bar, because this needs to change
// atomically with screen rotations.
final int rotation = displayFrames.mRotation;
final int displayHeight = displayFrames.mDisplayHeight;
final int displayWidth = displayFrames.mDisplayWidth;
final Rect dockFrame = displayFrames.mDock;
mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight, rotation);
if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
// It's a system nav bar or a portrait screen; nav bar goes on bottom.
final int top = cutoutSafeUnrestricted.bottom
- getNavigationBarHeight(rotation, uiMode);
mTmpNavigationFrame.set(0, top, displayWidth, displayFrames.mUnrestricted.bottom);
displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top;
if (transientNavBarShowing) {
mNavigationBarController.setBarShowingLw(true);
} else if (navVisible) {
mNavigationBarController.setBarShowingLw(true);
dockFrame.bottom = displayFrames.mRestricted.bottom
= displayFrames.mRestrictedOverscan.bottom = top;
} else {
// We currently want to hide the navigation UI - unless we expanded the status bar.
mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
}
if (navVisible && !navTranslucent && !navAllowedHidden
&& !mNavigationBar.isAnimatingLw()
&& !mNavigationBarController.wasRecentlyTranslucent()) {
// If the opaque nav bar is currently requested to be visible and not in the process
// of animating on or off, then we can tell the app that it is covered by it.
displayFrames.mSystem.bottom = top;
}
} else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
// Landscape screen; nav bar goes to the right.
...
} else if (mNavigationBarPosition == NAV_BAR_LEFT) {
// Seascape screen; nav bar goes to the left.
...
}
// Make sure the content and current rectangles are updated to account for the restrictions
// from the navigation bar.
displayFrames.mCurrent.set(dockFrame);
displayFrames.mVoiceContent.set(dockFrame);
displayFrames.mContent.set(dockFrame);
mStatusBarLayer = mNavigationBar.getSurfaceLayer();
// And compute the final frame.
mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
mTmpNavigationFrame, displayFrames.mDisplayCutoutSafe, mTmpNavigationFrame, dcf,
mTmpNavigationFrame, displayFrames.mDisplayCutoutSafe,
displayFrames.mDisplayCutout, false /* parentFrameWasClippedByDisplayCutout */);
mNavigationBarController.setContentFrame(mNavigationBar.getContentFrameLw());
return mNavigationBarController.checkHiddenLw();
}
}
NavigationBar 的 top 是 cutoutSafeUnrestricted.bottom
减去 NavigationBar 高度的结果。cutoutSafeUnrestricted
是安全的窗口(Android 9 针对刘海平新增的),当没有 Overscan 的时候与 mUnrestricted
相同,即 cutoutSafeUnrestricted.bottom
的值与 DisplayFrames.mDisplayHeight
值相同。计算 NavigationBar 的 top 后设置 mTmpNavigationFrame
。mTmpNavigationFrame
就是 NavigationBar 的窗口区域。
如果 NavigationBar 可见的话更新 dockFrame
、mRestricted
、mRestrictedOverscan
的 bottom 值。然后用 dockFrame 去更新 mCurrent
、mVoiceContent
、mContent
。
最后调用 computeFrameLw()
方法计算 NavigationBar 的 contentFrame
大小,然后绑定到 mNavigationBarController
。
com.android.server.policy.PhoneWindowManager
StatusBar 的布局在 PhoneWindowManager 的 beginLayoutLw()
方法中,调用 layoutStatusBar()
方法。
首先设定 of /* overscanFrame */
、df /* displayFrame */
、pf /* parentFrame */
、vf /* visibleFrame */
,其中 of
、df
、pf
设定为 displayFrames.mUnrestricted
,即屏幕大小。vf
设定为 displayFrames.mStable
,mStable
的大小在调用 layoutNavigationBar()
方法后变成了除去 NavigationBar 的窗口。然后调用 mStatusBar.computeFrameLw()
方法计算 StatusBar 的 mContentFrame
大小。mContentFrame
在 IME 不存在时与 mDecorFrame
相同,IME 存在时是 mDecorFrame
除去 IME 窗口的大小。
// apply any navigation bar insets
of.set(displayFrames.mUnrestricted);
df.set(displayFrames.mUnrestricted);
pf.set(displayFrames.mUnrestricted);
vf.set(displayFrames.mStable);
mStatusBarLayer = mStatusBar.getSurfaceLayer();
// Let the status bar determine its size.
mStatusBar.computeFrameLw(pf /* parentFrame */, df /* displayFrame */,
vf /* overlayFrame */, vf /* contentFrame */, vf /* visibleFrame */,
dcf /* decorFrame */, vf /* stableFrame */, vf /* outsetFrame */,
displayFrames.mDisplayCutout, false /* parentFrameWasClippedByDisplayCutout */);
下一步修改 displayFrames.mStable
,即应用的窗口。因为 StatusBar 默认显示在顶部的,所以修改 mStable.top
值。
// For layout, the status bar is always at the top with our fixed height.
displayFrames.mStable.top = displayFrames.mUnrestricted.top
+ mStatusBarHeightForRotation[displayFrames.mRotation];
// Make sure the status bar covers the entire cutout height
displayFrames.mStable.top = Math.max(displayFrames.mStable.top,
displayFrames.mDisplayCutoutSafe.top);