// # ViewRootImpl publicvoiddispatchApplyInsets(View host){ Trace.traceBegin(Trace.TRACE_TAG_VIEW, "dispatchApplyInsets"); mApplyInsetsRequested = false; // 关注点1,计算 Insets WindowInsets insets = getWindowInsets(true/* forceConstruct */); if (!shouldDispatchCutout()) { // Window is either not laid out in cutout or the status bar inset takes care of // clearing the cutout, so we don't need to dispatch the cutout to the hierarchy. insets = insets.consumeDisplayCutout(); } // 关注点2,分发 Insets host.dispatchApplyWindowInsets(insets); mAttachInfo.delayNotifyContentCaptureInsetsEvent(insets.getInsets(Type.all())); Trace.traceEnd(Trace.TRACE_TAG_VIEW); }
private WindowInsets mLastInsets; privatefinal InsetsState mState = new InsetsState();
@VisibleForTesting public WindowInsets calculateInsets(boolean isScreenRound, boolean alwaysConsumeSystemBars, int windowType, int windowingMode, int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags){ mWindowType = windowType; mLastWindowingMode = windowingMode; mLastLegacySoftInputMode = legacySoftInputMode; mLastLegacyWindowFlags = legacyWindowFlags; mLastLegacySystemUiFlags = legacySystemUiFlags; // mLastInsets = mState.calculateInsets(mFrame, null/* ignoringVisibilityState*/, isScreenRound, alwaysConsumeSystemBars, legacySoftInputMode, legacyWindowFlags, legacySystemUiFlags, windowType, windowingMode, null/* idSideMap */); return mLastInsets; }
接着调用 InsetsState::calculateInsets:
// # InsetsState
privatefinal SparseArray mSources;
public WindowInsets calculateInsets(Rect frame, @Nullable InsetsState ignoringVisibilityState, boolean isScreenRound, boolean alwaysConsumeSystemBars, int legacySoftInputMode, int legacyWindowFlags, int legacySystemUiFlags, int windowType, @WindowConfiguration.WindowingMode int windowingMode, @Nullable @InternalInsetsSide SparseIntArray idSideMap){
Insets[] typeInsetsMap = new Insets[Type.SIZE]; Insets[] typeMaxInsetsMap = new Insets[Type.SIZE]; boolean[] typeVisibilityMap = newboolean[Type.SIZE]; final Rect relativeFrame = new Rect(frame); final Rect relativeFrameMax = new Rect(frame); @InsetsTypeint suppressScrimTypes = 0;
// 关注点2,输入法单独处理,流程大体一致 // IME won't be reported in max insets as the size depends on the EditorInfo of the IME // target. if (source.getType() != WindowInsets.Type.ime()) { InsetsSource ignoringVisibilitySource = ignoringVisibilityState != null ? ignoringVisibilityState.peekSource(source.getId()) : source; if (ignoringVisibilitySource == null) { continue; } processSource(ignoringVisibilitySource, relativeFrameMax, true/* ignoreVisibility */, typeMaxInsetsMap, null/* idSideMap */, null/* typeVisibilityMap */); } }
if (type == Type.MANDATORY_SYSTEM_GESTURES) { // Mandatory system gestures are also system gestures. // TODO: find a way to express this more generally. One option would be to define // Type.systemGestureInsets() as NORMAL | MANDATORY, but then we lose the // ability to set systemGestureInsets() independently from // mandatorySystemGestureInsets() in the Builder. processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap, insets, Type.SYSTEM_GESTURES); } if (type == Type.CAPTION_BAR) { // Caption should also be gesture and tappable elements. This should not be needed when // the caption is added from the shell, as the shell can add other types at the same // time. processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap, insets, Type.SYSTEM_GESTURES); processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap, insets, Type.MANDATORY_SYSTEM_GESTURES); processSourceAsPublicType(source, typeInsetsMap, idSideMap, typeVisibilityMap, insets, Type.TAPPABLE_ELEMENT); } }
接着调用 calculateInsets 方法
// # InsetsSource
private Insets calculateInsets(Rect relativeFrame, Rect frame, boolean ignoreVisibility){ if (!ignoreVisibility && !mVisible) { return Insets.NONE; } // During drag-move and drag-resizing, the caption insets position may not get updated // before the app frame get updated. To layout the app content correctly during drag events, // we always return the insets with the corresponding height covering the top. if (getType() == WindowInsets.Type.captionBar()) { return Insets.of(0, frame.height(), 0, 0); } // Checks for whether there is shared edge with insets for 0-width/height window. finalboolean hasIntersection = relativeFrame.isEmpty() ? getIntersection(frame, relativeFrame, mTmpFrame) : mTmpFrame.setIntersect(frame, relativeFrame); if (!hasIntersection) { return Insets.NONE; }
// TODO: Currently, non-floating IME always intersects at bottom due to issues with cutout. // However, we should let the policy decide from the server. if (getType() == WindowInsets.Type.ime()) { return Insets.of(0, 0, 0, mTmpFrame.height()); }
// Intersecting at top/bottom if (mTmpFrame.width() == relativeFrame.width()) { if (mTmpFrame.top == relativeFrame.top) { return Insets.of(0, mTmpFrame.height(), 0, 0); } elseif (mTmpFrame.bottom == relativeFrame.bottom) { return Insets.of(0, 0, 0, mTmpFrame.height()); } // TODO: remove when insets are shell-customizable. // This is a hack that says "if this is a top-inset (eg statusbar), always apply it // to the top". It is used when adjusting primary split for IME. if (mTmpFrame.top == 0) { return Insets.of(0, mTmpFrame.height(), 0, 0); } } // Intersecting at left/right elseif (mTmpFrame.height() == relativeFrame.height()) { if (mTmpFrame.left == relativeFrame.left) { return Insets.of(mTmpFrame.width(), 0, 0, 0); } elseif (mTmpFrame.right == relativeFrame.right) { return Insets.of(0, 0, mTmpFrame.width(), 0); } } return Insets.NONE; }
不同的条件生成不同的 Insets 返回。
processSourceAsPublicType 将生成的 Insets 插入会数组中。
// # InsetsState
privatevoidprocessSourceAsPublicType(InsetsSource source, Insets[] typeInsetsMap, @InternalInsetsSide @Nullable SparseIntArray idSideMap, @Nullable boolean[] typeVisibilityMap, Insets insets, int type){ int index = indexOf(type); Insets existing = typeInsetsMap[index]; if (existing == null) { typeInsetsMap[index] = insets; } else { typeInsetsMap[index] = Insets.max(existing, insets); }
if (typeVisibilityMap != null) { typeVisibilityMap[index] = source.isVisible(); }
if (idSideMap != null) { @InternalInsetsSideint insetSide = getInsetSide(insets); if (insetSide != ISIDE_UNKNOWN) { idSideMap.put(source.getId(), insetSide); } } }
3.2.2 dispatchApplyWindowInsets 分发 Insets
我们接着看 dispatchApplyWindowInsets 分发 Insets 的过程:
// # ViewGroup @Override public WindowInsets dispatchApplyWindowInsets(WindowInsets insets){ insets = super.dispatchApplyWindowInsets(insets); if (insets.isConsumed()) { return insets; } if (View.sBrokenInsetsDispatch) { return brokenDispatchApplyWindowInsets(insets); } else { return newDispatchApplyWindowInsets(insets); } }
无论那种情况都会去调用 View::dispatchApplyWindowInsets
// # ViewGroup private WindowInsets brokenDispatchApplyWindowInsets(WindowInsets insets){ finalint count = getChildCount(); for (int i = 0; i insets = getChildAt(i).dispatchApplyWindowInsets(insets); if (insets.isConsumed()) { break; } } return insets; }
private WindowInsets newDispatchApplyWindowInsets(WindowInsets insets){ finalint count = getChildCount(); for (int i = 0; i getChildAt(i).dispatchApplyWindowInsets(insets); } return insets; }
if (activeControls != null) { for (InsetsSourceControl activeControl : activeControls) { if (activeControl != null) { // TODO(b/122982984): Figure out why it can be null. mTmpControlArray.put(activeControl.getId(), activeControl); } } }
// Ensure to update all existing source consumers for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
final InsetsSourceConsumer consumer = mSourceConsumers.valueAt(i); final InsetsSourceControl control = mTmpControlArray.get(consumer.getId());
if (control != null) { controllableTypes |= control.getType(); consumedControlCount++; }
// control may be null, but we still need to update the control to null if it got // revoked. consumer.setControl(control, showTypes, hideTypes); }
// Ensure to create source consumers if not available yet. if (consumedControlCount != mTmpControlArray.size()) { for (int i = mTmpControlArray.size() - 1; i >= 0; i--) { final InsetsSourceControl control = mTmpControlArray.valueAt(i); // Consumer 和 Control 关联起来 getSourceConsumer(control.getId(), control.getType()) .setControl(control, showTypes, hideTypes); } }
if (mTmpControlArray.size() > 0) { // Update surface positions for animations. for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { mRunningAnimations.get(i).runner.updateSurfacePosition(mTmpControlArray); } } mTmpControlArray.clear();
// Do not override any animations that the app started in the OnControllableInsetsChanged // listeners. int animatingTypes = invokeControllableInsetsChangedListeners(); showTypes[0] &= ~animatingTypes; hideTypes[0] &= ~animatingTypes;
if (showTypes[0] != 0) { applyAnimation(showTypes[0], true/* show */, false/* fromIme */, null/* statsToken */); } if (hideTypes[0] != 0) { applyAnimation(hideTypes[0], false/* show */, false/* fromIme */, null/* statsToken */); }
if (mControllableTypes != controllableTypes) { if (WindowInsets.Type.hasCompatSystemBars(mControllableTypes ^ controllableTypes)) { mCompatSysUiVisibilityStaled = true; } mControllableTypes = controllableTypes; }
// InsetsSourceConsumer#setControl might change the requested visibility. reportRequestedVisibleTypes(); }