前言
前面说到了computeFrameLw,用来真正计算每个窗口的大小的函数,并为WindowState的相关frame属性赋值,我们接着上周的内容来继续学习relayoutWindow的相关逻辑
WindowState#computeFrameLw
@Override
public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overscanFrame,
Rect contentFrame, Rect visibleFrame, Rect decorFrame, Rect stableFrame,
Rect outsetFrame, WmDisplayCutout displayCutout,
boolean parentFrameWasClippedByDisplayCutout) {
if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {
// This window is being replaced and either already got information that it's being
// removed or we are still waiting for some information. Because of this we don't
// want to apply any more changes to it, so it remains in this state until new window
// appears.
return;
}
mHaveFrame = true; //mHaveFrame表示这个窗口计算过了
mParentFrameWasClippedByDisplayCutout = parentFrameWasClippedByDisplayCutout;
final Task task = getTask();
final boolean inFullscreenContainer = inFullscreenContainer();
final boolean windowsAreFloating = task != null && task.isFloating();
final DisplayContent dc = getDisplayContent();
// If the task has temp inset bounds set, we have to make sure all its windows uses
// the temp inset frame. Otherwise different display frames get applied to the main
// window and the child window, making them misaligned.
// Otherwise we need to clear the inset frame, to avoid using a stale frame after leaving
// multi window mode.
if (task != null && isInMultiWindowMode()) {
task.getTempInsetBounds(mInsetFrame);
} else {
mInsetFrame.setEmpty();
}
// Denotes the actual frame used to calculate the insets and to perform the layout. When
// resizing in docked mode, we'd like to freeze the layout, so we also need to freeze the
// insets temporarily. By the notion of a task having a different layout frame, we can
// achieve that while still moving the task around.
final Rect layoutContainingFrame;
final Rect layoutDisplayFrame;
// The offset from the layout containing frame to the actual containing frame.
final int layoutXDiff;
final int layoutYDiff;
if (inFullscreenContainer || layoutInParentFrame()) {
// We use the parent frame as the containing frame for fullscreen and child windows
mContainingFrame.set(parentFrame);
mDisplayFrame.set(displayFrame);//通过传递参数给该WindowState的属性赋值
layoutDisplayFrame = displayFrame;
layoutContainingFrame = parentFrame;
layoutXDiff = 0;
layoutYDiff = 0;
} else {
getBounds(mContainingFrame);
if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {
// If the bounds are frozen, we still want to translate the window freely and only
// freeze the size.
Rect frozen = mAppToken.mFrozenBounds.peek();
mContainingFrame.right = mContainingFrame.left + frozen.width();
mContainingFrame.bottom = mContainingFrame.top + frozen.height();
}
final WindowState imeWin = mService.mInputMethodWindow;
// IME is up and obscuring this window. Adjust the window position so it is visible.
if (imeWin != null && imeWin.isVisibleNow() && isInputMethodTarget()) {
if (inFreeformWindowingMode()
&& mContainingFrame.bottom > contentFrame.bottom) {
// In freeform we want to move the top up directly.
// TODO: Investigate why this is contentFrame not parentFrame.
mContainingFrame.top -= mContainingFrame.bottom - contentFrame.bottom;
} else if (!inPinnedWindowingMode()
&& mContainingFrame.bottom > parentFrame.bottom) {
// But in docked we want to behave like fullscreen and behave as if the task
// were given smaller bounds for the purposes of layout. Skip adjustments for
// the pinned stack, they are handled separately in the PinnedStackController.
mContainingFrame.bottom = parentFrame.bottom;
}
}
if (windowsAreFloating) {
// In floating modes (e.g. freeform, pinned) we have only to set the rectangle
// if it wasn't set already. No need to intersect it with the (visible)
// "content frame" since it is allowed to be outside the visible desktop.
if (mContainingFrame.isEmpty()) {
mContainingFrame.set(contentFrame);
}
}
final TaskStack stack = getStack();
if (inPinnedWindowingMode() && stack != null
&& stack.lastAnimatingBoundsWasToFullscreen()) {
// PIP edge case: When going from pinned to fullscreen, we apply a
// tempInsetFrame for the full task - but we're still at the start of the animation.
// To prevent a jump if there's a letterbox, restrict to the parent frame.
mInsetFrame.intersectUnchecked(parentFrame);
mContainingFrame.intersectUnchecked(parentFrame);
}
mDisplayFrame.set(mContainingFrame);
layoutXDiff = !mInsetFrame.isEmpty() ? mInsetFrame.left - mContainingFrame.left : 0;
layoutYDiff = !mInsetFrame.isEmpty() ? mInsetFrame.top - mContainingFrame.top : 0;
layoutContainingFrame = !mInsetFrame.isEmpty() ? mInsetFrame : mContainingFrame;
mTmpRect.set(0, 0, dc.getDisplayInfo().logicalWidth, dc.getDisplayInfo().logicalHeight);
subtractInsets(mDisplayFrame, layoutContainingFrame, displayFrame, mTmpRect);
if (!layoutInParentFrame()) {
subtractInsets(mContainingFrame, layoutContainingFrame, parentFrame, mTmpRect);
subtractInsets(mInsetFrame, layoutContainingFrame, parentFrame, mTmpRect);
}
layoutDisplayFrame = displayFrame;
layoutDisplayFrame.intersect(layoutContainingFrame);
}
final int pw = mContainingFrame.width();
final int ph = mContainingFrame.height();
if (!mParentFrame.equals(parentFrame)) {
//Slog.i(TAG_WM, "Window " + this + " content frame from " + mParentFrame
// + " to " + parentFrame);
mParentFrame.set(parentFrame);
mContentChanged = true;
}
if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) {
mLastRequestedWidth = mRequestedWidth;
mLastRequestedHeight = mRequestedHeight;
mContentChanged = true;
}
mOverscanFrame.set(overscanFrame);
mContentFrame.set(contentFrame);
mVisibleFrame.set(visibleFrame);
mDecorFrame.set(decorFrame); //During layout, the current screen borders along which input method windows are placed. 导航栏不允许停靠iem
mStableFrame.set(stableFrame);
final boolean hasOutsets = outsetFrame != null;
if (hasOutsets) {
mOutsetFrame.set(outsetFrame);
}
final int fw = mFrame.width();
final int fh = mFrame.height();
applyGravityAndUpdateFrame(layoutContainingFrame, layoutDisplayFrame);
// Calculate the outsets before the content frame gets shrinked to the window frame.
if (hasOutsets) {
/**
* Outsets determine the area outside of the surface where we want to pretend that it's possible
* to draw anyway.
*/
mOutsets.set(Math.max(mContentFrame.left - mOutsetFrame.left, 0),
Math.max(mContentFrame.top - mOutsetFrame.top, 0),
Math.max(mOutsetFrame.right - mContentFrame.right, 0),
Math.max(mOutsetFrame.bottom - mContentFrame.bottom, 0));
} else {
mOutsets.set(0, 0, 0, 0);
}
// Make sure the content and visible frames are inside of the
// final window frame.
if (windowsAreFloating && !mFrame.isEmpty()) {
// For pinned workspace the frame isn't limited in any particular
// way since SystemUI controls the bounds. For freeform however
// we want to keep things inside the content frame.
final Rect limitFrame = task.inPinnedWindowingMode() ? mFrame : mContentFrame;
// Keep the frame out of the blocked system area, limit it in size to the content area
// and make sure that there is always a minimum visible so that the user can drag it
// into a usable area..
final int height = Math.min(mFrame.height(), limitFrame.height());
final int width = Math.min(limitFrame.width(), mFrame.width());
final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
final int minVisibleHeight = Math.min(height, WindowManagerService.dipToPixel(
MINIMUM_VISIBLE_HEIGHT_IN_DP, displayMetrics));
final int minVisibleWidth = Math.min(width, WindowManagerService.dipToPixel(
MINIMUM_VISIBLE_WIDTH_IN_DP, displayMetrics));
final int top = Math.max(limitFrame.top,
Math.min(mFrame.top, limitFrame.bottom - minVisibleHeight));
final int left = Math.max(limitFrame.left + minVisibleWidth - width,
Math.min(mFrame.left, limitFrame.right - minVisibleWidth));
mFrame.set(left, top, left + width, top + height);
mContentFrame.set(mFrame);
mVisibleFrame.set(mContentFrame);
mStableFrame.set(mContentFrame);
} else if (mAttrs.type == TYPE_DOCK_DIVIDER) { //针对DockedStackDivider
dc.getDockedDividerController().positionDockedStackedDivider(mFrame);
mContentFrame.set(mFrame);
if (!mFrame.equals(mLastFrame)) {
mMovedByResize = true;
}
} else {
mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
Math.max(mContentFrame.top, mFrame.top),
Math.min(mContentFrame.right, mFrame.right),
Math.min(mContentFrame.bottom, mFrame.bottom));
mVisibleFrame.set(Math.max(mVisibleFrame.left, mFrame.left),
Math.max(mVisibleFrame.top, mFrame.top),
Math.min(mVisibleFrame.right, mFrame.right),
Math.min(mVisibleFrame.bottom, mFrame.bottom));
mStableFrame.set(Math.max(mStableFrame.left, mFrame.left),
Math.max(mStableFrame.top, mFrame.top),
Math.min(mStableFrame.right, mFrame.right),
Math.min(mStableFrame.bottom, mFrame.bottom));
}
if (inFullscreenContainer && !windowsAreFloating) {
// Windows that are not fullscreen can be positioned outside of the display frame,
// but that is not a reason to provide them with overscan insets.
mOverscanInsets.set(Math.max(mOverscanFrame.left - layoutContainingFrame.left, 0),
Math.max(mOverscanFrame.top - layoutContainingFrame.top, 0),
Math.max(layoutContainingFrame.right - mOverscanFrame.right, 0),
Math.max(layoutContainingFrame.bottom - mOverscanFrame.bottom, 0));
}
if (mAttrs.type == TYPE_DOCK_DIVIDER) {
// For the docked divider, we calculate the stable insets like a full-screen window
// so it can use it to calculate the snap positions.
final WmDisplayCutout c = displayCutout.calculateRelativeTo(mDisplayFrame);
mTmpRect.set(mDisplayFrame);
mTmpRect.inset(c.getDisplayCutout().getSafeInsets());
mTmpRect.intersectUnchecked(mStableFrame);
mStableInsets.set(Math.max(mTmpRect.left - mDisplayFrame.left, 0),
Math.max(mTmpRect.top - mDisplayFrame.top, 0),
Math.max(mDisplayFrame.right - mTmpRect.right, 0),
Math.max(mDisplayFrame.bottom - mTmpRect.bottom, 0));
// The divider doesn't care about insets in any case, so set it to empty so we don't
// trigger a relayout when moving it.
mContentInsets.setEmpty();
mVisibleInsets.setEmpty();
displayCutout = WmDisplayCutout.NO_CUTOUT;
} else {
getDisplayContent().getBounds(mTmpRect);
// Override right and/or bottom insets in case if the frame doesn't fit the screen in
// non-fullscreen mode.
boolean overrideRightInset = !windowsAreFloating && !inFullscreenContainer
&& mFrame.right > mTmpRect.right;
boolean overrideBottomInset = !windowsAreFloating && !inFullscreenContainer
&& mFrame.bottom > mTmpRect.bottom;
mContentInsets.set(mContentFrame.left - mFrame.left,
mContentFrame.top - mFrame.top,
overrideRightInset ? mTmpRect.right - mContentFrame.right
: mFrame.right - mContentFrame.right,
overrideBottomInset ? mTmpRect.bottom - mContentFrame.bottom
: mFrame.bottom - mContentFrame.bottom);
mVisibleInsets.set(mVisibleFrame.left - mFrame.left,
mVisibleFrame.top - mFrame.top,
overrideRightInset ? mTmpRect.right - mVisibleFrame.right
: mFrame.right - mVisibleFrame.right,
overrideBottomInset ? mTmpRect.bottom - mVisibleFrame.bottom
: mFrame.bottom - mVisibleFrame.bottom);
mStableInsets.set(Math.max(mStableFrame.left - mFrame.left, 0),
Math.max(mStableFrame.top - mFrame.top, 0),
overrideRightInset ? Math.max(mTmpRect.right - mStableFrame.right, 0)
: Math.max(mFrame.right - mStableFrame.right, 0),
overrideBottomInset ? Math.max(mTmpRect.bottom - mStableFrame.bottom, 0)
: Math.max(mFrame.bottom - mStableFrame.bottom, 0));
}
mDisplayCutout = displayCutout.calculateRelativeTo(mFrame);
// Offset the actual frame by the amount layout frame is off.
mFrame.offset(-layoutXDiff, -layoutYDiff);
mCompatFrame.offset(-layoutXDiff, -layoutYDiff);
mContentFrame.offset(-layoutXDiff, -layoutYDiff);
mVisibleFrame.offset(-layoutXDiff, -layoutYDiff);
mStableFrame.offset(-layoutXDiff, -layoutYDiff);
mCompatFrame.set(mFrame);
if (mEnforceSizeCompat) {
// If there is a size compatibility scale being applied to the
// window, we need to apply this to its insets so that they are
// reported to the app in its coordinate space.
mOverscanInsets.scale(mInvGlobalScale);
mContentInsets.scale(mInvGlobalScale);
mVisibleInsets.scale(mInvGlobalScale);
mStableInsets.scale(mInvGlobalScale);
mOutsets.scale(mInvGlobalScale);
// Also the scaled frame that we report to the app needs to be
// adjusted to be in its coordinate space.
mCompatFrame.scale(mInvGlobalScale);
}
if (mIsWallpaper && (fw != mFrame.width() || fh != mFrame.height())) {
final DisplayContent displayContent = getDisplayContent();
if (displayContent != null) {
final DisplayInfo displayInfo = displayContent.getDisplayInfo();
getDisplayContent().mWallpaperController.updateWallpaperOffset(
this, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
}
}
if (DEBUG_LAYOUT || localLOGV) Slog.v(TAG,
"Resolving (mRequestedWidth="
+ mRequestedWidth + ", mRequestedheight="
+ mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph
+ "): frame=" + mFrame.toShortString()
+ " ci=" + mContentInsets.toShortString()
+ " vi=" + mVisibleInsets.toShortString()
+ " si=" + mStableInsets.toShortString()
+ " of=" + mOutsets.toShortString());
}
关键的计算点在applyGravityAndUpdateFrame,根据Window的Gravity(horizontal等)计算更新frame;每个窗口初次计算时都会有这个逻辑,下面再讲
PhoneWindowManager#layoutStatusBar
计算StatusBar的大小/布局(因为有左上角顶点,长和宽)
private boolean layoutStatusBar(DisplayFrames displayFrames, Rect pf, Rect df, Rect of, Rect vf,
Rect dcf, int sysui, boolean isKeyguardShowing) {
// decide where the status bar goes ahead of time
if (mStatusBar == null) {
return false;
}
// apply any navigation bar insets
of.set(displayFrames.mUnrestricted);
df.set(displayFrames.mUnrestricted);
pf.set(displayFrames.mUnrestricted);
vf.set(displayFrames.mStable);
/*
pf(parentFrame):表示窗口父窗口的大小
df(deviceFrame):设备的屏幕大小
of(OverScanFrame):设备屏幕大小、和df值一样
cf(ContentFrame):窗口内容区域大小
vf(VisibleFrame):窗口可见内容区域大小
dcf(DecorFrame):装饰区域大小。除去状态栏和导航栏的区域。
sf(Stableframe) :除去状态栏和导航栏的区域。
*/
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 */);
// 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);
// Tell the bar controller where the collapsed status bar content is
mTmpRect.set(mStatusBar.getContentFrameLw());
mTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
mTmpRect.top = mStatusBar.getContentFrameLw().top; // Ignore top display cutout inset
mTmpRect.bottom = displayFrames.mStable.top; // Use collapsed status bar size
mStatusBarController.setContentFrame(mTmpRect);
boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
boolean statusBarTranslucent = (sysui
& (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
if (!isKeyguardShowing) {
statusBarTranslucent &= areTranslucentBarsAllowed();
}
// If the status bar is hidden, we don't want to cause windows behind it to scroll.
if (mStatusBar.isVisibleLw() && !statusBarTransient) {
// Status bar may go away, so the screen area it occupies is available to apps but just
// covering them when the status bar is visible.
final Rect dockFrame = displayFrames.mDock;
dockFrame.top = displayFrames.mStable.top;
displayFrames.mContent.set(dockFrame);
displayFrames.mVoiceContent.set(dockFrame);
displayFrames.mCurrent.set(dockFrame);
if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " + String.format(
"dock=%s content=%s cur=%s", dockFrame.toString(),
displayFrames.mContent.toString(), displayFrames.mCurrent.toString()));
if (!mStatusBar.isAnimatingLw() && !statusBarTranslucent
&& !mStatusBarController.wasRecentlyTranslucent()) {
// If the opaque status 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.top = displayFrames.mStable.top;
}
}
return mStatusBarController.checkHiddenLw();
}
上面performLayout的第一步beginLayoutLw来设置屏幕的大小,计算状态栏,导航栏的大小,布局已经处理完毕;接着看第二步(针对其他窗口,父窗口,子窗口的相关布局计算)
首先对于父窗口
forAllWindows(mPerformLayout, true /* traverseTopToBottom */);
针对父窗口的callback-mPerformLayout
private final Consumer<WindowState> mPerformLayout = w -> {
// Don't do layout of a window if it is not visible, or soon won't be visible, to avoid
// wasting time and funky changes while a window is animating away.
final boolean gone = (mTmpWindow != null && mService.mPolicy.canBeHiddenByKeyguardLw(w))
|| w.isGoneForLayoutLw();
if (DEBUG_LAYOUT && !w.mLayoutAttached) {
Slog.v(TAG, "1ST PASS " + w + ": gone=" + gone + " mHaveFrame=" + w.mHaveFrame
+ " mLayoutAttached=" + w.mLayoutAttached
+ " screen changed=" + w.isConfigChanged());
final AppWindowToken atoken = w.mAppToken;
if (gone) Slog.v(TAG, " GONE: mViewVisibility=" + w.mViewVisibility
+ " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden()
+ " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
+ " parentHidden=" + w.isParentWindowHidden());
else Slog.v(TAG, " VIS: mViewVisibility=" + w.mViewVisibility
+ " mRelayoutCalled=" + w.mRelayoutCalled + " hidden=" + w.mToken.isHidden()
+ " hiddenRequested=" + (atoken != null && atoken.hiddenRequested)
+ " parentHidden=" + w.isParentWindowHidden());
}
// If this view is GONE, then skip it -- keep the current frame, and let the caller know
// so they can ignore it if they want. (We do the normal layout for INVISIBLE windows,
// since that means "perform layout as normal, just don't display").
if (!gone || !w.mHaveFrame || w.mLayoutNeeded
|| ((w.isConfigChanged() || w.setReportResizeHints())
&& !w.isGoneForLayoutLw() &&
((w.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
(w.mHasSurface && w.mAppToken != null &&
w.mAppToken.layoutConfigChanges)))) {
if (!w.mLayoutAttached) {
if (mTmpInitial) {
//Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
w.mContentChanged = false;
}
if (w.mAttrs.type == TYPE_DREAM) {
// Don't layout windows behind a dream, so that if it does stuff like hide
// the status bar we won't get a bad transition when it goes away.
mTmpWindow = w;
}
w.mLayoutNeeded = false;
w.prelayout();
final boolean firstLayout = !w.isLaidOut();
mService.mPolicy.layoutWindowLw(w, null, mDisplayFrames);
w.mLayoutSeq = mLayoutSeq;
// If this is the first layout, we need to initialize the last inset values as
// otherwise we'd immediately cause an unnecessary resize.
if (firstLayout) {
w.updateLastInsetValues();
}
if (w.mAppToken != null) {
w.mAppToken.layoutLetterbox(w);
}
if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.mFrame
+ " mContainingFrame=" + w.mContainingFrame
+ " mDisplayFrame=" + w.mDisplayFrame);
}
}
};
DisplayContent#forAllWindows
@Override
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
// Special handling so we can process IME windows with #forAllImeWindows above their IME
// target, or here in order if there isn't an IME target.
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
final DisplayChildWindowContainer child = mChildren.get(i);
if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) {
// In this case the Ime windows will be processed above their target so we skip
// here.
continue;
}
if (child.forAllWindows(callback, traverseTopToBottom)) {
return true;
}
}
} else {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
final DisplayChildWindowContainer child = mChildren.get(i);
if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) {
// In this case the Ime windows will be processed above their target so we skip
// here.
continue;
}
if (child.forAllWindows(callback, traverseTopToBottom)) {
return true;
}
}
}
return false;
}
首先针对DisplayContent的mChildren
调用上述WindowContainer的forAllWindows
WindowContainer#forAllWindows
/**
* For all windows at or below this container call the callback.
* @param callback Calls the {@link ToBooleanFunction#apply} method for each window found and
* stops the search if {@link ToBooleanFunction#apply} returns true.
* @param traverseTopToBottom If true traverses the hierarchy from top-to-bottom in terms of
* z-order, else from bottom-to-top.
* @return True if the search ended before we reached the end of the hierarchy due to
* {@link ToBooleanFunction#apply} returning true.
*/
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
if (traverseTopToBottom) {
for (int i = mChildren.size() - 1; i >= 0; --i) {
if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) {
return true;
}
}
} else {
final int count = mChildren.size();
for (int i = 0; i < count; i++) {
if (mChildren.get(i).forAllWindows(callback, traverseTopToBottom)) {
return true;
}
}
}
return false;
}
针对如TaskStackContainer的情况,其mChildren为:
针对WindowToken的情况,其mChildren为:
WindowContainer中的mChildren可能为list (WindowToken or WindowState) 复用
DisplayContent.AboveAppWindowsContainers包含除了app页面的一些系统windowToken
DisplayContent.TaskStackContainers包含launcher和app的相应AppWindowToken
WindowState#forAllWindows
@Override
boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
if (mChildren.isEmpty()) {
// The window has no children so we just return it.
return applyInOrderWithImeWindows(callback, traverseTopToBottom);
}
if (traverseTopToBottom) {
return forAllWindowTopToBottom(callback);
} else {
return forAllWindowBottomToTop(callback);
}
}
WindowState#forAllWindowTopToBottom
private boolean forAllWindowTopToBottom(ToBooleanFunction<WindowState> callback) {
// We want to consume the positive sublayer children first because they need to appear
// above the parent, then this window (the parent), and then the negative sublayer children
// because they need to appear above the parent.
int i = mChildren.size() - 1;
WindowState child = mChildren.get(i); //前面说过,WindowState的mChildren就是依附于它的子窗口
while (i >= 0 && child.mSubLayer >= 0) {
if (child.applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) { //对于子窗口,在这里调用其callback
return true;
}
--i;
if (i < 0) {
break;
}
child = mChildren.get(i);
}
if (applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) {
return true;
}
while (i >= 0) {
if (child.applyInOrderWithImeWindows(callback, true /* traverseTopToBottom */)) {
return true;
}
--i;
if (i < 0) {
break;
}
child = mChildren.get(i);
}
return false;
}
调用mPerformLayout callbacks的trace
PhoneWindowManager#layoutWindowLw
计算窗口大小是在layoutWindowLw中完成的,该方法不会直接计算出窗口大小,而是先计算出窗口能够扩展的最大空间,然后再调用WindowState的computeFrameLw来计算出窗口的最终大小(很长,针对各种类型窗口的处理)
/** {@inheritDoc} */
@Override
public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
// We've already done the navigation bar, status bar, and all screen decor windows. If the
// status bar can receive input, we need to layout it again to accommodate for the IME
// window.
if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar
|| mScreenDecorWindows.contains(win)) {
return;
}
final WindowManager.LayoutParams attrs = win.getAttrs();
final boolean isDefaultDisplay = win.isDefaultDisplay();
final boolean needsToOffsetInputMethodTarget = isDefaultDisplay &&
(win == mLastInputMethodTargetWindow && mLastInputMethodWindow != null);
if (needsToOffsetInputMethodTarget) {
if (DEBUG_LAYOUT) Slog.i(TAG, "Offset ime target window by the last ime window state");
offsetInputMethodWindowLw(mLastInputMethodWindow, displayFrames);
}
final int type = attrs.type;
final int fl = PolicyControl.getWindowFlags(win, attrs);
final int pfl = attrs.privateFlags;
final int sim = attrs.softInputMode;
final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(null, attrs);
final int sysUiFl = requestedSysUiFl | getImpliedSysUiFlagsForLayout(attrs);
final Rect pf = mTmpParentFrame;
final Rect df = mTmpDisplayFrame;
final Rect of = mTmpOverscanFrame;
final Rect cf = mTmpContentFrame;
final Rect vf = mTmpVisibleFrame;
final Rect dcf = mTmpDecorFrame;
final Rect sf = mTmpStableFrame;
Rect osf = null;
dcf.setEmpty();
final boolean hasNavBar = (isDefaultDisplay && mHasNavigationBar
&& mNavigationBar != null && mNavigationBar.isVisibleLw());
final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0
|| (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;
sf.set(displayFrames.mStable);
if (type == TYPE_INPUT_METHOD) {
vf.set(displayFrames.mDock);
cf.set(displayFrames.mDock);
of.set(displayFrames.mDock);
df.set(displayFrames.mDock);
pf.set(displayFrames.mDock);
// IM dock windows layout below the nav bar...
pf.bottom = df.bottom = of.bottom = displayFrames.mUnrestricted.bottom;
// ...with content insets above the nav bar
cf.bottom = vf.bottom = displayFrames.mStable.bottom;
if (mStatusBar != null && mFocusedWindow == mStatusBar && canReceiveInput(mStatusBar)) {
// The status bar forces the navigation bar while it's visible. Make sure the IME
// avoids the navigation bar in that case.
if (mNavigationBarPosition == NAV_BAR_RIGHT) {
pf.right = df.right = of.right = cf.right = vf.right =
displayFrames.mStable.right;
} else if (mNavigationBarPosition == NAV_BAR_LEFT) {
pf.left = df.left = of.left = cf.left = vf.left = displayFrames.mStable.left;
}
}
// IM dock windows always go to the bottom of the screen.
attrs.gravity = Gravity.BOTTOM;
mDockLayer = win.getSurfaceLayer();
} else if (type == TYPE_VOICE_INTERACTION) {
of.set(displayFrames.mUnrestricted);
df.set(displayFrames.mUnrestricted);
pf.set(displayFrames.mUnrestricted);
if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
cf.set(displayFrames.mDock);
} else {
cf.set(displayFrames.mContent);
}
if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
vf.set(displayFrames.mCurrent);
} else {
vf.set(cf);
}
} else if (type == TYPE_WALLPAPER) {
layoutWallpaper(displayFrames, pf, df, of, cf);
} else if (win == mStatusBar) {
of.set(displayFrames.mUnrestricted);
df.set(displayFrames.mUnrestricted);
pf.set(displayFrames.mUnrestricted);
cf.set(displayFrames.mStable);
vf.set(displayFrames.mStable);
if (adjust == SOFT_INPUT_ADJUST_RESIZE) {
cf.bottom = displayFrames.mContent.bottom;
} else {
cf.bottom = displayFrames.mDock.bottom;
vf.bottom = displayFrames.mContent.bottom;
}
} else {
dcf.set(displayFrames.mSystem);
final boolean inheritTranslucentDecor =
(attrs.privateFlags & PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) != 0;
final boolean isAppWindow =
type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW;
final boolean topAtRest =
win == mTopFullscreenOpaqueWindowState && !win.isAnimatingLw();
if (isAppWindow && !inheritTranslucentDecor && !topAtRest) {
if ((sysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0
&& (fl & FLAG_FULLSCREEN) == 0
&& (fl & FLAG_TRANSLUCENT_STATUS) == 0
&& (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
&& (pfl & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) == 0) {
// Ensure policy decor includes status bar
dcf.top = displayFrames.mStable.top;
}
if ((fl & FLAG_TRANSLUCENT_NAVIGATION) == 0
&& (sysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0
&& (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
// Ensure policy decor includes navigation bar
dcf.bottom = displayFrames.mStable.bottom;
dcf.right = displayFrames.mStable.right;
}
}
if (layoutInScreen && layoutInsetDecor) {
if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+ "): IN_SCREEN, INSET_DECOR");
// This is the case for a normal activity window: we want it to cover all of the
// screen space, and it can take care of moving its contents to account for screen
// decorations that intrude into that space.
if (attached != null) {
// If this window is attached to another, our display
// frame is the same as the one we are attached to.
setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf,
displayFrames); //针对child window的处理
} else {
if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_STATUS_BAR_SUB_PANEL) {
// Status bar panels are the only windows who can go on top of the status
// bar. They are protected by the STATUS_BAR_SERVICE permission, so they
// have the same privileges as the status bar itself.
//
// However, they should still dodge the navigation bar if it exists.
pf.left = df.left = of.left = hasNavBar
? displayFrames.mDock.left : displayFrames.mUnrestricted.left;
pf.top = df.top = of.top = displayFrames.mUnrestricted.top;
pf.right = df.right = of.right = hasNavBar
? displayFrames.mRestricted.right
: displayFrames.mUnrestricted.right;
pf.bottom = df.bottom = of.bottom = hasNavBar
? displayFrames.mRestricted.bottom
: displayFrames.mUnrestricted.bottom;
if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
"Laying out status bar window: (%d,%d - %d,%d)",
pf.left, pf.top, pf.right, pf.bottom));
} else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
&& type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
// Asking to layout into the overscan region, so give it that pure
// unrestricted area.
of.set(displayFrames.mOverscan);
df.set(displayFrames.mOverscan);
pf.set(displayFrames.mOverscan);
} else if (canHideNavigationBar()
&& (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
&& (type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW
|| type == TYPE_VOLUME_OVERLAY)) {
// Asking for layout as if the nav bar is hidden, lets the application
// extend into the unrestricted overscan screen area. We only do this for
// application windows and certain system windows to ensure no window that
// can be above the nav bar can do this.
df.set(displayFrames.mOverscan);
pf.set(displayFrames.mOverscan);
// We need to tell the app about where the frame inside the overscan is, so
// it can inset its content by that amount -- it didn't ask to actually
// extend itself into the overscan region.
of.set(displayFrames.mUnrestricted);
} else {
df.set(displayFrames.mRestrictedOverscan);
pf.set(displayFrames.mRestrictedOverscan);
// We need to tell the app about where the frame inside the overscan
// is, so it can inset its content by that amount -- it didn't ask
// to actually extend itself into the overscan region.
of.set(displayFrames.mUnrestricted);
}
if ((fl & FLAG_FULLSCREEN) == 0) {
if (win.isVoiceInteraction()) {
cf.set(displayFrames.mVoiceContent);
} else {
if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
cf.set(displayFrames.mDock);
} else {
cf.set(displayFrames.mContent);
}
}
} else {
// Full screen windows are always given a layout that is as if the status
// bar and other transient decors are gone. This is to avoid bad states when
// moving from a window that is not hiding the status bar to one that is.
cf.set(displayFrames.mRestricted);
}
applyStableConstraints(sysUiFl, fl, cf, displayFrames);
if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
vf.set(displayFrames.mCurrent);
} else {
vf.set(cf);
}
}
} else if (layoutInScreen || (sysUiFl
& (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {
if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+ "): IN_SCREEN");
// A window that has requested to fill the entire screen just
// gets everything, period.
if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_STATUS_BAR_SUB_PANEL) {
cf.set(displayFrames.mUnrestricted);
of.set(displayFrames.mUnrestricted);
df.set(displayFrames.mUnrestricted);
pf.set(displayFrames.mUnrestricted);
if (hasNavBar) {
pf.left = df.left = of.left = cf.left = displayFrames.mDock.left;
pf.right = df.right = of.right = cf.right = displayFrames.mRestricted.right;
pf.bottom = df.bottom = of.bottom = cf.bottom =
displayFrames.mRestricted.bottom;
}
if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
"Laying out IN_SCREEN status bar window: (%d,%d - %d,%d)",
pf.left, pf.top, pf.right, pf.bottom));
} else if (type == TYPE_NAVIGATION_BAR || type == TYPE_NAVIGATION_BAR_PANEL) {
// The navigation bar has Real Ultimate Power.
of.set(displayFrames.mUnrestricted);
df.set(displayFrames.mUnrestricted);
pf.set(displayFrames.mUnrestricted);
if (DEBUG_LAYOUT) Slog.v(TAG, String.format(
"Laying out navigation bar window: (%d,%d - %d,%d)",
pf.left, pf.top, pf.right, pf.bottom));
} else if ((type == TYPE_SECURE_SYSTEM_OVERLAY || type == TYPE_SCREENSHOT)
&& ((fl & FLAG_FULLSCREEN) != 0)) {
// Fullscreen secure system overlays get what they ask for. Screenshot region
// selection overlay should also expand to full screen.
cf.set(displayFrames.mOverscan);
of.set(displayFrames.mOverscan);
df.set(displayFrames.mOverscan);
pf.set(displayFrames.mOverscan);
} else if (type == TYPE_BOOT_PROGRESS) {
// Boot progress screen always covers entire display.
cf.set(displayFrames.mOverscan);
of.set(displayFrames.mOverscan);
df.set(displayFrames.mOverscan);
pf.set(displayFrames.mOverscan);
} else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
&& type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
// Asking to layout into the overscan region, so give it that pure unrestricted
// area.
cf.set(displayFrames.mOverscan);
of.set(displayFrames.mOverscan);
df.set(displayFrames.mOverscan);
pf.set(displayFrames.mOverscan);
} else if (canHideNavigationBar()
&& (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
&& (type == TYPE_STATUS_BAR
|| type == TYPE_TOAST
|| type == TYPE_DOCK_DIVIDER
|| type == TYPE_VOICE_INTERACTION_STARTING
|| (type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW))) {
// Asking for layout as if the nav bar is hidden, lets the
// application extend into the unrestricted screen area. We
// only do this for application windows (or toasts) to ensure no window that
// can be above the nav bar can do this.
// XXX This assumes that an app asking for this will also
// ask for layout in only content. We can't currently figure out
// what the screen would be if only laying out to hide the nav bar.
cf.set(displayFrames.mUnrestricted);
of.set(displayFrames.mUnrestricted);
df.set(displayFrames.mUnrestricted);
pf.set(displayFrames.mUnrestricted);
} else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) {
of.set(displayFrames.mRestricted);
df.set(displayFrames.mRestricted);
pf.set(displayFrames.mRestricted);
if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
cf.set(displayFrames.mDock);
} else {
cf.set(displayFrames.mContent);
}
} else {
cf.set(displayFrames.mRestricted);
of.set(displayFrames.mRestricted);
df.set(displayFrames.mRestricted);
pf.set(displayFrames.mRestricted);
}
applyStableConstraints(sysUiFl, fl, cf,displayFrames);
if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
vf.set(displayFrames.mCurrent);
} else {
vf.set(cf);
}
} else if (attached != null) {
if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle()
+ "): attached to " + attached);
// A child window should be placed inside of the same visible
// frame that its parent had.
setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf,
displayFrames);
} else {
if (DEBUG_LAYOUT) Slog.v(TAG, "layoutWindowLw(" + attrs.getTitle() +
"): normal window");
// Otherwise, a normal window must be placed inside the content
// of all screen decorations.
if (type == TYPE_STATUS_BAR_PANEL) {
// Status bar panels can go on
// top of the status bar. They are protected by the STATUS_BAR_SERVICE
// permission, so they have the same privileges as the status bar itself.
cf.set(displayFrames.mRestricted);
of.set(displayFrames.mRestricted);
df.set(displayFrames.mRestricted);
pf.set(displayFrames.mRestricted);
} else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) {
// These dialogs are stable to interim decor changes.
cf.set(displayFrames.mStable);
of.set(displayFrames.mStable);
df.set(displayFrames.mStable);
pf.set(displayFrames.mStable);
} else {
pf.set(displayFrames.mContent);
if (win.isVoiceInteraction()) {
cf.set(displayFrames.mVoiceContent);
of.set(displayFrames.mVoiceContent);
df.set(displayFrames.mVoiceContent);
} else if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
cf.set(displayFrames.mDock);
of.set(displayFrames.mDock);
df.set(displayFrames.mDock);
} else {
cf.set(displayFrames.mContent);
of.set(displayFrames.mContent);
df.set(displayFrames.mContent);
}
if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
vf.set(displayFrames.mCurrent);
} else {
vf.set(cf);
}
}
}
}
boolean parentFrameWasClippedByDisplayCutout = false;
final int cutoutMode = attrs.layoutInDisplayCutoutMode;
final boolean attachedInParent = attached != null && !layoutInScreen;
final boolean requestedHideNavigation =
(requestedSysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;
// TYPE_BASE_APPLICATION windows are never considered floating here because they don't get
// cropped / shifted to the displayFrame in WindowState.
final boolean floatingInScreenWindow = !attrs.isFullscreen() && layoutInScreen
&& type != TYPE_BASE_APPLICATION;
// Ensure that windows with a DEFAULT or NEVER display cutout mode are laid out in
// the cutout safe zone.
if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
final Rect displayCutoutSafeExceptMaybeBars = mTmpDisplayCutoutSafeExceptMaybeBarsRect;
displayCutoutSafeExceptMaybeBars.set(displayFrames.mDisplayCutoutSafe);
if (layoutInScreen && layoutInsetDecor && !requestedFullscreen
&& cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
// At the top we have the status bar, so apps that are
// LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR but not FULLSCREEN
// already expect that there's an inset there and we don't need to exclude
// the window from that area.
displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
}
if (layoutInScreen && layoutInsetDecor && !requestedHideNavigation
&& cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
// Same for the navigation bar.
switch (mNavigationBarPosition) {
case NAV_BAR_BOTTOM:
displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
break;
case NAV_BAR_RIGHT:
displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
break;
case NAV_BAR_LEFT:
displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
break;
}
}
if (type == TYPE_INPUT_METHOD && mNavigationBarPosition == NAV_BAR_BOTTOM) {
// The IME can always extend under the bottom cutout if the navbar is there.
displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
}
// Windows that are attached to a parent and laid out in said parent already avoid
// the cutout according to that parent and don't need to be further constrained.
// Floating IN_SCREEN windows get what they ask for and lay out in the full screen.
// They will later be cropped or shifted using the displayFrame in WindowState,
// which prevents overlap with the DisplayCutout.
if (!attachedInParent && !floatingInScreenWindow) {
mTmpRect.set(pf);
pf.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
parentFrameWasClippedByDisplayCutout |= !mTmpRect.equals(pf);
}
// Make sure that NO_LIMITS windows clipped to the display don't extend under the
// cutout.
df.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
}
// Content should never appear in the cutout.
cf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
// TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
// Also, we don't allow windows in multi-window mode to extend out of the screen.
if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && type != TYPE_SYSTEM_ERROR
&& !win.isInMultiWindowMode()) {
df.left = df.top = -10000;
df.right = df.bottom = 10000;
if (type != TYPE_WALLPAPER) {
of.left = of.top = cf.left = cf.top = vf.left = vf.top = -10000;
of.right = of.bottom = cf.right = cf.bottom = vf.right = vf.bottom = 10000;
}
}
// If the device has a chin (e.g. some watches), a dead area at the bottom of the screen we
// need to provide information to the clients that want to pretend that you can draw there.
// We only want to apply outsets to certain types of windows. For example, we never want to
// apply the outsets to floating dialogs, because they wouldn't make sense there.
final boolean useOutsets = shouldUseOutsets(attrs, fl);
if (isDefaultDisplay && useOutsets) {
osf = mTmpOutsetFrame;
osf.set(cf.left, cf.top, cf.right, cf.bottom);
int outset = ScreenShapeHelper.getWindowOutsetBottomPx(mContext.getResources());
if (outset > 0) {
int rotation = displayFrames.mRotation;
if (rotation == Surface.ROTATION_0) {
osf.bottom += outset;
} else if (rotation == Surface.ROTATION_90) {
osf.right += outset;
} else if (rotation == Surface.ROTATION_180) {
osf.top -= outset;
} else if (rotation == Surface.ROTATION_270) {
osf.left -= outset;
}
if (DEBUG_LAYOUT) Slog.v(TAG, "applying bottom outset of " + outset
+ " with rotation " + rotation + ", result: " + osf);
}
}
if (DEBUG_LAYOUT) Slog.v(TAG, "Compute frame " + attrs.getTitle()
+ ": sim=#" + Integer.toHexString(sim)
+ " attach=" + attached + " type=" + type
+ String.format(" flags=0x%08x", fl)
+ " pf=" + pf.toShortString() + " df=" + df.toShortString()
+ " of=" + of.toShortString()
+ " cf=" + cf.toShortString() + " vf=" + vf.toShortString()
+ " dcf=" + dcf.toShortString()
+ " sf=" + sf.toShortString()
+ " osf=" + (osf == null ? "null" : osf.toShortString()));
win.computeFrameLw(pf, df, of, cf, vf, dcf, sf, osf, displayFrames.mDisplayCutout,
parentFrameWasClippedByDisplayCutout); //最后调用computeFrameLw计算该窗口大小相关的属性
// Dock windows carve out the bottom of the screen, so normal windows
// can't appear underneath them.
if (type == TYPE_INPUT_METHOD && win.isVisibleLw()
&& !win.getGivenInsetsPendingLw()) {
setLastInputMethodWindowLw(null, null);
offsetInputMethodWindowLw(win, displayFrames);
}
if (type == TYPE_VOICE_INTERACTION && win.isVisibleLw()
&& !win.getGivenInsetsPendingLw()) {
offsetVoiceInputWindowLw(win, displayFrames);
}
}
调用子窗口的callback mPerformLayoutAttached
private final Consumer<WindowState> mPerformLayoutAttached = w -> {
if (w.mLayoutAttached) { //对于子窗口,mLayoutAttached的值为true
if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame
+ " mViewVisibility=" + w.mViewVisibility
+ " mRelayoutCalled=" + w.mRelayoutCalled);
// If this view is GONE, then skip it -- keep the current frame, and let the caller
// know so they can ignore it if they want. (We do the normal layout for INVISIBLE
// windows, since that means "perform layout as normal, just don't display").
if (mTmpWindow != null && mService.mPolicy.canBeHiddenByKeyguardLw(w)) {
return;
}
if ((w.mViewVisibility != GONE && w.mRelayoutCalled) || !w.mHaveFrame
|| w.mLayoutNeeded) {
if (mTmpInitial) {
//Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
w.mContentChanged = false;
}
w.mLayoutNeeded = false;
w.prelayout();
mService.mPolicy.layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
w.mLayoutSeq = mLayoutSeq;
if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.mFrame
+ " mContainingFrame=" + w.mContainingFrame
+ " mDisplayFrame=" + w.mDisplayFrame);
}
} else if (w.mAttrs.type == TYPE_DREAM) {
// Don't layout windows behind a dream, so that if it does stuff like hide the
// status bar we won't get a bad transition when it goes away.
mTmpWindow = mTmpWindow2;
}
};
layoutWindowLw->setAttachedWindowFrames
computeFrameLw 第一次计算mHaveFrame = false,一开始计算时mFrame = (0,0-0,0)
以后再走到这里时mHaveFrame = false,虽然也会走到下面,但mFrame已经是window的大小了
第一次计算mFrame时,computeFrameLw中调用相关逻辑:
applyGravityAndUpdateFrame(layoutContainingFrame, layoutDisplayFrame);
WindowState#applyGravityAndUpdateFrame
更新mFrame
void applyGravityAndUpdateFrame(Rect containingFrame, Rect displayFrame) {
final int pw = containingFrame.width();
final int ph = containingFrame.height();
final Task task = getTask();
final boolean inNonFullscreenContainer = !inFullscreenContainer();
final boolean noLimits = (mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;
// We need to fit it to the display if either
// a) The window is in a fullscreen container, or we don't have a task (we assume fullscreen
// for the taskless windows)
// b) If it's a secondary app window, we also need to fit it to the display unless
// FLAG_LAYOUT_NO_LIMITS is set. This is so we place Popups, dialogs, and similar windows on
// screen, but SurfaceViews want to be always at a specific location so we don't fit it to
// the display.
final boolean fitToDisplay = (task == null || !inNonFullscreenContainer)
|| ((mAttrs.type != TYPE_BASE_APPLICATION) && !noLimits);
float x, y;
int w,h;
if ((mAttrs.flags & FLAG_SCALED) != 0) {
if (mAttrs.width < 0) {
w = pw;
} else if (mEnforceSizeCompat) {
w = (int)(mAttrs.width * mGlobalScale + .5f);
} else {
w = mAttrs.width;
}
if (mAttrs.height < 0) {
h = ph;
} else if (mEnforceSizeCompat) {
h = (int)(mAttrs.height * mGlobalScale + .5f);
} else {
h = mAttrs.height;
}
} else {
if (mAttrs.width == MATCH_PARENT) {
w = pw;
} else if (mEnforceSizeCompat) {
w = (int)(mRequestedWidth * mGlobalScale + .5f);
} else {
w = mRequestedWidth;
}
if (mAttrs.height == MATCH_PARENT) {
h = ph;
} else if (mEnforceSizeCompat) {
h = (int)(mRequestedHeight * mGlobalScale + .5f);
} else {
h = mRequestedHeight;
}
}
if (mEnforceSizeCompat) {
x = mAttrs.x * mGlobalScale;
y = mAttrs.y * mGlobalScale;
} else {
x = mAttrs.x;
y = mAttrs.y;
}
//根据attr得到height,width和左上角顶点
if (inNonFullscreenContainer && !layoutInParentFrame()) {
// Make sure window fits in containing frame since it is in a non-fullscreen task as
// required by {@link Gravity#apply} call.
w = Math.min(w, pw);
h = Math.min(h, ph);
}
// Set mFrame
Gravity.apply(mAttrs.gravity, w, h, containingFrame,
(int) (x + mAttrs.horizontalMargin * pw),
(int) (y + mAttrs.verticalMargin * ph), mFrame); //设置WindowState的mFrame
// Now make sure the window fits in the overall display frame.
if (fitToDisplay) {
Gravity.applyDisplay(mAttrs.gravity, displayFrame, mFrame);
}
// We need to make sure we update the CompatFrame as it is used for
// cropping decisions, etc, on systems where we lack a decor layer.
mCompatFrame.set(mFrame);
if (mEnforceSizeCompat) {
// See comparable block in computeFrameLw.
mCompatFrame.scale(mInvGlobalScale);
}
}
Gravity#apply
/**
* Apply a gravity constant to an object.
*
* @param gravity The desired placement of the object, as defined by the
* constants in this class.
* @param w The horizontal size of the object.
* @param h The vertical size of the object.
* @param container The frame of the containing space, in which the object
* will be placed. Should be large enough to contain the
* width and height of the object.
* @param xAdj Offset to apply to the X axis. If gravity is LEFT this
* pushes it to the right; if gravity is RIGHT it pushes it to
* the left; if gravity is CENTER_HORIZONTAL it pushes it to the
* right or left; otherwise it is ignored.
* @param yAdj Offset to apply to the Y axis. If gravity is TOP this pushes
* it down; if gravity is BOTTOM it pushes it up; if gravity is
* CENTER_VERTICAL it pushes it down or up; otherwise it is
* ignored.
* @param outRect Receives the computed frame of the object in its
* container.
*/
public static void apply(int gravity, int w, int h, Rect container,
int xAdj, int yAdj, Rect outRect) {
switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_X_SHIFT)) {
case 0:
outRect.left = container.left
+ ((container.right - container.left - w)/2) + xAdj;
outRect.right = outRect.left + w;
if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
== (AXIS_CLIP<<AXIS_X_SHIFT)) {
if (outRect.left < container.left) {
outRect.left = container.left;
}
if (outRect.right > container.right) {
outRect.right = container.right;
}
}
break;
case AXIS_PULL_BEFORE<<AXIS_X_SHIFT:
outRect.left = container.left + xAdj;
outRect.right = outRect.left + w;
if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
== (AXIS_CLIP<<AXIS_X_SHIFT)) {
if (outRect.right > container.right) {
outRect.right = container.right;
}
}
break;
case AXIS_PULL_AFTER<<AXIS_X_SHIFT:
outRect.right = container.right - xAdj;
outRect.left = outRect.right - w;
if ((gravity&(AXIS_CLIP<<AXIS_X_SHIFT))
== (AXIS_CLIP<<AXIS_X_SHIFT)) {
if (outRect.left < container.left) {
outRect.left = container.left;
}
}
break;
default:
outRect.left = container.left + xAdj;
outRect.right = container.right + xAdj;
break;
}
switch (gravity&((AXIS_PULL_BEFORE|AXIS_PULL_AFTER)<<AXIS_Y_SHIFT)) {
case 0:
outRect.top = container.top
+ ((container.bottom - container.top - h)/2) + yAdj;
outRect.bottom = outRect.top + h;
if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
== (AXIS_CLIP<<AXIS_Y_SHIFT)) {
if (outRect.top < container.top) {
outRect.top = container.top;
}
if (outRect.bottom > container.bottom) {
outRect.bottom = container.bottom;
}
}
break;
case AXIS_PULL_BEFORE<<AXIS_Y_SHIFT:
outRect.top = container.top + yAdj;
outRect.bottom = outRect.top + h;
if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
== (AXIS_CLIP<<AXIS_Y_SHIFT)) {
if (outRect.bottom > container.bottom) {
outRect.bottom = container.bottom;
}
}
break;
case AXIS_PULL_AFTER<<AXIS_Y_SHIFT:
outRect.bottom = container.bottom - yAdj;
outRect.top = outRect.bottom - h;
if ((gravity&(AXIS_CLIP<<AXIS_Y_SHIFT))
== (AXIS_CLIP<<AXIS_Y_SHIFT)) {
if (outRect.top < container.top) {
outRect.top = container.top;
}
}
break;
default:
outRect.top = container.top + yAdj;
outRect.bottom = container.bottom + yAdj;
break;
}
}
attr是客户端赋值完成后调用到服务端的