This paper mainly introduces the view loading process of app and the view loading process of StatusBar and Navigation Bar.
Generally speaking, the UI layout that a user can see is the status bar View, the UI interface ContentView of app program, and the navigation bar View.
Related categories:
frameworks\base\core\java\android\view\View.java
frameworks\base\core\java\android\app\Activity.java
frameworks\base\core\java\com\android\internal\policy\DecorView.java
frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java
frameworks\base\services\core\java\com\android\server\policy\PhoneWindowManager.java
frameworks\base\core\res\res\values\attrs.xml
The key implementation is in DecorView and Phone Windows.
1. app executes setContentView
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
2. setContentView in Activity
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/); mWindow = new PhoneWindow(this, window, activityConfigCallback); //Omit part of the code //...
}
2. setContentView in Phone Windows
Step 1: installDecor(), creating the top-level window DecorView
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true;
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) { mContentParent = generateLayout(mDecor); //Omit part of the code, //... } //Omit part of the code, //...
}
// Create a top-level window DecorView through new DecorView()
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
DecorView Creation
The above process shows that DecorView was created in generateDecor. When the creation is complete, generateLayout() is executed in the installDecor.
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle(); if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { requestFeature(FEATURE_NO_TITLE); } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) { // Don't allow an action bar if there is no title. requestFeature(FEATURE_ACTION_BAR); } //Omit part of the code, //... // Inflate the window decor. int layoutResource; int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features)); if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { layoutResource = R.layout.screen_swipe_dismiss; setCloseOnSwipeEnabled(true); } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleIconsDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_title_icons; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); // System.out.println("Title Icons!"); } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0 && (features & (1 << FEATURE_ACTION_BAR)) == 0) { // Special case for a window with only a progress bar (and title). // XXX Need to have a no-title version of embedded windows. layoutResource = R.layout.screen_progress; // System.out.println("Progress!"); } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { // Special case for a window with a custom title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogCustomTitleDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_custom_title; } // XXX Remove this once action bar supports these features. removeFeature(FEATURE_ACTION_BAR); } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { // If no other features and not embedded, only need a title. // If the window is floating, we need a dialog layout if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleDecorLayout, res, true); layoutResource = res.resourceId; } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) { layoutResource = a.getResourceId( R.styleable.Window_windowActionBarFullscreenDecorLayout, R.layout.screen_action_bar); } else { layoutResource = R.layout.screen_title; } // System.out.println("Title!"); } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) { layoutResource = R.layout.screen_simple_overlay_action_mode; } else { // Embedded, so no decoration is needed. layoutResource = R.layout.screen_simple; // System.out.println("Simple!"); } //Key point 1 mDecor.startChanging(); mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) { ProgressBar progress = getCircularProgressBar(false); if (progress != null) { progress.setIndeterminate(true); } } if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { registerSwipeCallbacks(contentParent); } // Remaining setup -- of background and title -- that only applies // to top-level windows. if (getContainer() == null) { final Drawable background; if (mBackgroundResource != 0) { background = getContext().getDrawable(mBackgroundResource); } else { background = mBackgroundDrawable; } mDecor.setWindowBackground(background); final Drawable frame; if (mFrameResource != 0) { frame = getContext().getDrawable(mFrameResource); } else { frame = null; } mDecor.setWindowFrame(frame); mDecor.setElevation(mElevation); mDecor.setClipToOutline(mClipToOutline); if (mTitle != null) { setTitle(mTitle); } if (mTitleColor == 0) { mTitleColor = mTextColor; } setTitleColor(mTitleColor); } mDecor.finishChanging(); return contentParent;
}
Loading window style
Notice that the function generateLayout does a lot of work. According to the theme style getWindows Style (), to set the style of DecorView. Common window styles are:
// omit part //...Detailed description refers to the official website: https://developer.android.com/reference/android/R.attr
Enumeration of usage in app, add in styles.xml:
The layout of DecorView can be referred to in the article: https://blog.csdn.net/a553181867/article/details/51477040
Key points 1. mDecor. onResources Loaded (mLayout Inflater, layoutResource);
By looking at the assignment of the layoutResource, you can see that the layout is determined by the theme style. It can be divided into the following parts:
R.layout.screen_swipe_dismiss; // theme style set true, Activity can exit by sliding to the right
R.layout.screen_title_icons;
Set the following code in onCreate, in Title Bar
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_LEFT_ICON);
setFeatureDrawableResource(Window.FEATURE_LEFT_ICON,R.drawable.ic_launcher_background);
setContentView(R.layout.activity_main);
}
The values that request Windows Feature can set are:
// 1.DEFAULT_FEATURES: The default state of the system, generally not required to specify
// 2.FEATURE_CONTEXT_MENU: Enable ContextMenu by default, which is not normally specified
// 3.FEATURE_CUSTOM_TITLE: Custom title. You must specify when you need to customize the title. For example, when the title is a button
// 4.FEATURE_INDETERMINATE_PROGRESS: Uncertain Progress
// 5.FEATURE_LEFT_ICON: icon on the left side of the title bar
// 6.FEATURE_NO_TITLE: No Title
// 7.FEATURE_OPTIONS_PANEL: Enable the "Options Panel" function, which is enabled by default.
// 8.FEATURE_PROGRESS: Progress Indicator Function
// 9.FEATURE_RIGHT_ICON: The icon on the right side of the title bar
Detailed instructions can be referred to: https://blog.csdn.net/axi295309066/article/details/53363792
R.layout.screen_progress; // / Set FEATURE_PROGRESS window feature and false
R.layout.screen_custom_title;
R.layout.screen_action_bar
R.layout.screen_title;
R.layout.screen_simple_overlay_action_mode;
R.layout.screen_simple; // This is the optimal layout of the screen, with minimal functionality enabled
Not one explanation.
onResourcesLoaded adds the layout above to DecorVew.
Key point 2: ViewGroup contentParent = ViewGroup findViewById (ID_ANDROID_CONTENT);
A contentParent View is created to load the activity_main.xml of the app. See the structure diagram above, which is the ContentView in the structure diagram. Assign the returned contentParent to mContentParent.
Looking back at the implementation in setContentView:
mLayoutInflater.inflate(layoutResID, mContentParent);
Or:
mContentParent.addView(view, params);
Here is the activity_main.xml to load the app or add other layout view s. And set mContentParent to the parent layout of activity_main.xml.
Load Status Bar
The layout of mContentParent, ViewGroup contentParent = ViewGroup findViewById (ID_ANDROID_CONTENT);
ID_ANDROID_CONTENT is the ContentView of DecorView, which is used to load the activity_main.xml of app.
Official website explanation: https://developer.android.com/reference/android/view/Window
The ID that the main layout in the XML layout file should have.
It's actually FrameLayout android:id="@android:id/content" in the layout before loading.
List a code for R.layout.screen_simple:
The key point is: android:fitsSystemWindows= "true"
Callback executes onApply Windows Insets in DecorView ():
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
final WindowManager.LayoutParams attrs = mWindow.getAttributes();
mFloatingInsets.setEmpty();
Log.d(TAG, "sunxiaolin,onApplyWindowInsets(0x" + Integer.toHexString(attrs.flags) + ")"); if ((attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0) { // For dialog windows we want to make sure they don't go over the status bar or nav bar. // We consume the system insets and we will reuse them later during the measure phase. // We allow the app to ignore this and handle insets itself by using // FLAG_LAYOUT_IN_SCREEN. if (attrs.height == WindowManager.LayoutParams.WRAP_CONTENT) { mFloatingInsets.top = insets.getSystemWindowInsetTop(); mFloatingInsets.bottom = insets.getSystemWindowInsetBottom(); insets = insets.inset(0, insets.getSystemWindowInsetTop(), 0, insets.getSystemWindowInsetBottom()); } if (mWindow.getAttributes().width == WindowManager.LayoutParams.WRAP_CONTENT) { mFloatingInsets.left = insets.getSystemWindowInsetTop(); mFloatingInsets.right = insets.getSystemWindowInsetBottom(); insets = insets.inset(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), 0); } } mFrameOffsets.set(insets.getSystemWindowInsets()); //Update Status Bar color or View insets = updateColorViews(insets, true /* animate */); insets = updateStatusGuard(insets); if (getForeground() != null) { drawableChanged(); } return insets;
}
Key points: updateColorViews();
Source code:
WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
WindowManager.LayoutParams attrs = mWindow.getAttributes();
int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
// IME is an exceptional floating window that requires color view. final boolean isImeWindow = mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD; if (!mWindow.mIsFloating || isImeWindow) { boolean disallowAnimate = !isLaidOut(); disallowAnimate |= ((mLastWindowFlags ^ attrs.flags) & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0; mLastWindowFlags = attrs.flags; if (insets != null) { mLastTopInset = getColorViewTopInset(insets.getStableInsetTop(), insets.getSystemWindowInsetTop()); mLastBottomInset = getColorViewBottomInset(insets.getStableInsetBottom(), insets.getSystemWindowInsetBottom()); mLastRightInset = getColorViewRightInset(insets.getStableInsetRight(), insets.getSystemWindowInsetRight()); mLastLeftInset = getColorViewRightInset(insets.getStableInsetLeft(), insets.getSystemWindowInsetLeft()); // Don't animate if the presence of stable insets has changed, because that // indicates that the window was either just added and received them for the // first time, or the window size or position has changed. boolean hasTopStableInset = insets.getStableInsetTop() != 0; disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset); mLastHasTopStableInset = hasTopStableInset; boolean hasBottomStableInset = insets.getStableInsetBottom() != 0; disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset); mLastHasBottomStableInset = hasBottomStableInset; boolean hasRightStableInset = insets.getStableInsetRight() != 0; disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset); mLastHasRightStableInset = hasRightStableInset; boolean hasLeftStableInset = insets.getStableInsetLeft() != 0; disallowAnimate |= (hasLeftStableInset != mLastHasLeftStableInset); mLastHasLeftStableInset = hasLeftStableInset; mLastShouldAlwaysConsumeNavBar = insets.shouldAlwaysConsumeNavBar(); } boolean navBarToRightEdge = isNavBarToRightEdge(mLastBottomInset, mLastRightInset); boolean navBarToLeftEdge = isNavBarToLeftEdge(mLastBottomInset, mLastLeftInset); int navBarSize = getNavBarSize(mLastBottomInset, mLastRightInset, mLastLeftInset); updateColorViewInt(mNavigationColorViewState, sysUiVisibility, mWindow.mNavigationBarColor, mWindow.mNavigationBarDividerColor, navBarSize, navBarToRightEdge || navBarToLeftEdge, navBarToLeftEdge, 0 /* sideInset */, animate && !disallowAnimate, false /* force */); boolean statusBarNeedsRightInset = navBarToRightEdge && mNavigationColorViewState.present; boolean statusBarNeedsLeftInset = navBarToLeftEdge && mNavigationColorViewState.present; int statusBarSideInset = statusBarNeedsRightInset ? mLastRightInset : statusBarNeedsLeftInset ? mLastLeftInset : 0; updateColorViewInt(mStatusColorViewState, sysUiVisibility, calculateStatusBarColor(), 0, mLastTopInset, false /* matchVertical */, statusBarNeedsLeftInset, statusBarSideInset, animate && !disallowAnimate, mForceWindowDrawsStatusBarBackground); } // When we expand the window with FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS, we still need // to ensure that the rest of the view hierarchy doesn't notice it, unless they've // explicitly asked for it. // Determine whether the navigation bar is displayed, that is, whether the ContentView display area extends below the Navigation Bar boolean consumingNavBar = (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0 && (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0 && (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0 || mLastShouldAlwaysConsumeNavBar; // If we didn't request fullscreen layout, but we still got it because of the // mForceWindowDrawsStatusBarBackground flag, also consume top inset. // Without requesting full screen, calculate whether the ContentView extends to the display below StatuaBar, that is, calculate the ContentView display area according to StatusBar's instructions boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0 && (sysUiVisibility & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0 && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0 && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0 && mForceWindowDrawsStatusBarBackground && mLastTopInset != 0; int consumedTop = consumingStatusBar ? mLastTopInset : 0; int consumedRight = consumingNavBar ? mLastRightInset : 0; int consumedBottom = consumingNavBar ? mLastBottomInset : 0; int consumedLeft = consumingNavBar ? mLastLeftInset : 0;
// Compute the display area of ContentView according to consuming StatusBar and consuming NavBar above.
if (mContentRoot != null
&& mContentRoot.getLayoutParams() instanceof MarginLayoutParams) {
MarginLayoutParams lp = (MarginLayoutParams) mContentRoot.getLayoutParams();
if (lp.topMargin != consumedTop || lp.rightMargin != consumedRight
|| lp.bottomMargin != consumedBottom || lp.leftMargin != consumedLeft) {
lp.topMargin = consumedTop;
lp.rightMargin = consumedRight;
lp.bottomMargin = consumedBottom;
lp.leftMargin = consumedLeft;
mContentRoot.setLayoutParams(lp);
// Re-calculation results
if (insets == null) {
// The insets have changed, but we're not currently in the process
// of dispatching them.
requestApplyInsets();
}
}
if (insets != null) {
insets = insets.inset(consumedLeft, consumedTop, consumedRight, consumedBottom);
}
}
if (insets != null) { insets = insets.consumeStableInsets(); } return insets;
}
Conditions for consumedTop:
1. SysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN. The SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN attribute is not set.
2. SysUiVisibility & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS. No FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS is set. The window is transparent and shows the color of StatusBar in System UI.
3. attrs. Flags & FLAG_LAYOUT_IN_SCREEN, without setting the FLAG_LAYOUT_IN_SCREEN attribute, which ignores the surrounding decorative View (such as status bar)
4. Attrs. Flags & FLAG_LAYOUT_INSET_DECOR. No FLAG_LAYOUT_INSET_DECOR attribute is set. It is similar to FLAG_LAYOUT_IN_SCREEN. It ensures that StatusBar will not block your UI.
5. mForce Windows DrawsStatusBarBackground with transparent background
6. mLastTopInset!= 0. Last Top consumption was not 0.
Key points: updateColorViewInt();
The updateColorViewInt() method is executed twice in updateColorViews. The first parameter is mNavigationColorViewState, and the second parameter is mStatusColorViewState.
DecorView creates the Views of Status Bar and Navigation Bar, both in updateColorViewInt. These views are just a background color or transparent View, without any other UI display. The UI of the real Status Bar and Navigation Bar is implemented in the System UI through the window.
Source code:
private void updateColorViewInt(final ColorViewState state, int sysUiVis, int color,
int dividerColor, int size, boolean verticalBar, boolean seascape, int sideMargin,
boolean animate, boolean force) {
state.present = state.attributes.isPresent(sysUiVis, mWindow.getAttributes().flags, force);
boolean show = state.attributes.isVisible(state.present, color,
mWindow.getAttributes().flags, force);
boolean showView = show && !isResizing() && size > 0;
boolean visibilityChanged = false; View view = state.view; int resolvedHeight = verticalBar ? LayoutParams.MATCH_PARENT : size; int resolvedWidth = verticalBar ? size : LayoutParams.MATCH_PARENT; int resolvedGravity = verticalBar ? (seascape ? state.attributes.seascapeGravity : state.attributes.horizontalGravity) : state.attributes.verticalGravity;
//add by sunxiaolin
if (view == null) {
if (showView) {
// When view is null, wearing a View is actually creating Views for Status Bar and Navigation Bar
state.view = view = new View(mContext);
setColor(view, color, dividerColor, verticalBar, seascape);
view.setTransitionName(state.attributes.transitionName);
view.setId(state.attributes.id);
visibilityChanged = true;
view.setVisibility(INVISIBLE);
state.targetVisibility = VISIBLE;
LayoutParams lp = new LayoutParams(resolvedWidth, resolvedHeight, resolvedGravity); if (seascape) { lp.leftMargin = sideMargin; } else { lp.rightMargin = sideMargin; } addView(view, lp); updateColorViewTranslations(); } } else { int vis = showView ? VISIBLE : INVISIBLE; visibilityChanged = state.targetVisibility != vis; state.targetVisibility = vis; LayoutParams lp = (LayoutParams) view.getLayoutParams(); int rightMargin = seascape ? 0 : sideMargin; int leftMargin = seascape ? sideMargin : 0; if (lp.height != resolvedHeight || lp.width != resolvedWidth || lp.gravity != resolvedGravity || lp.rightMargin != rightMargin || lp.leftMargin != leftMargin) { lp.height = resolvedHeight; lp.width = resolvedWidth; lp.gravity = resolvedGravity; lp.rightMargin = rightMargin; lp.leftMargin = leftMargin; view.setLayoutParams(lp); } if (showView) { setColor(view, color, dividerColor, verticalBar, seascape); } } if (visibilityChanged) { view.animate().cancel(); if (animate && !isResizing()) { if (showView) { if (view.getVisibility() != VISIBLE) { view.setVisibility(VISIBLE); view.setAlpha(0.0f); } view.animate().alpha(1.0f).setInterpolator(mShowInterpolator). setDuration(mBarEnterExitDuration); } else { view.animate().alpha(0.0f).setInterpolator(mHideInterpolator) .setDuration(mBarEnterExitDuration) .withEndAction(new Runnable() { @Override public void run() { state.view.setAlpha(1.0f); state.view.setVisibility(INVISIBLE); } }); } } else { view.setAlpha(1.0f); view.setVisibility(showView ? VISIBLE : INVISIBLE); } } state.visible = show; state.color = color;
}