“This is the 15th day of my participation in the First Challenge 2022. For details: First Challenge 2022”
Related articles: DecorView Android View DecorView Android View DecorView View rendering process (3) -Activity View -WindowManager Android View rendering process (4) -Activity View -ViewRootImpl Android View rendering process (5) -Measure Android View View drawing process (six) -Layout Android View drawing process (seven) -Draw
Last article the View drawing process in the measurement (Mearsure) logic through the introduction of the main content of the source analysis, mainly divided into View and View subclass measurement, finally return the overall size, an important part is MearsureSpec, The understanding of mearSure and onMessure methods, which will be used frequently in subsequent custom views, is also an important aspect of solving View display problems.
In ViewRootImpl, the performLayout method is called to determine the DecorView’s position on the screen. Here’s the code logic:
// ViewRootImpl private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { mLayoutRequested = false; mScrollMayChange = true; mInLayout = true; final View host = mView; if (host == null) { return; } if (DEBUG_ORIENTATION || DEBUG_LAYOUT) { Log.v(mTag, "Laying out " + host + " to (" + host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")"); } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout"); Try {// Draw host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()) based on the measurement results; mInLayout = false; int numViewsRequestingLayout = mLayoutRequesters.size(); if (numViewsRequestingLayout > 0) { // requestLayout() was called during layout. // If no layout-request flags are set on the requesting views, there is no problem. // If some requests are still pending, then we need to clear those flags and do // a full request/measure/layout pass to handle this situation. ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, false); if (validLayoutRequesters ! = null) { // Set this flag to indicate that any further requests are happening during // the second pass, which may result in posting those requests to the next // frame instead mHandlingLayoutInLayoutRequest = true; // Process fresh layout requests, then measure and layout int numValidRequests = validLayoutRequesters.size(); for (int i = 0; i < numValidRequests; ++i) { final View view = validLayoutRequesters.get(i); Log.w("View", "requestLayout() improperly called by " + view + " during layout: running second layout pass"); view.requestLayout(); } measureHierarchy(host, lp, mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight); mInLayout = true; host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); mHandlingLayoutInLayoutRequest = false; // Check the valid requests again, this time without checking/clearing the // layout flags, since requests happening during the second pass get noop'd validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true); if (validLayoutRequesters ! = null) { final ArrayList<View> finalRequesters = validLayoutRequesters; // Post second-pass requests to the next frame getRunQueue().post(new Runnable() { @Override public void run() { int numValidRequests = finalRequesters.size(); for (int i = 0; i < numValidRequests; ++i) { final View view = finalRequesters.get(i); Log.w("View", "requestLayout() improperly called by " + view + " during second layout pass: posting in next frame"); view.requestLayout(); }}}); } } } } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false; }Copy the code
PFLAG_FORCE_LAYOUT (view.pFLAG_force_layout, view.pflag_force_layout, view.pflag_force_layout, view.pflag_force_layout); DecorView: Measure layout, measure layout, measure layout, measure layout, measure layout, measure layout, measure layout
// ViewGroup public final void layout(int l, int t, int r, int b) { if (! mSuppressLayout && (mTransition == null || ! mTransition.isChangingLayout())) { if (mTransition ! = null) { mTransition.layoutChange(this); } super.layout(l, t, r, b); } else { // record the fact that we noop'd it; request layout when transition finishes mLayoutCalledWhileSuppressed = true; }}Copy the code
As you can see, the viewGroup layout is final, so we call the parent layout method, that is, the view layout method.
// View public void layout(int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) ! = 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } int oldL = mLeft; int oldT = mTop; int oldB = mBottom; Int oldR = mRight; Boolean changed = isLayoutModeOptical(mParent)? SetOpticalFrame (l, t, r, b) : setFrame(L, t, r, b); // The layout has changed or needs to be rearranged. If so will enter onLayout logic (changed | | (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) = = PFLAG_LAYOUT_REQUIRED) {/ / onLayout(changed, l, t, r, b); if (shouldDrawRoundScrollbar()) { if(mRoundScrollbarRenderer == null) { mRoundScrollbarRenderer = new RoundScrollbarRenderer(this); } } else { mRoundScrollbarRenderer = null; } // Clear the request layout flag mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; if (li ! = null && li.mOnLayoutChangeListeners ! = null) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); int numListeners = listenersCopy.size(); for (int i = 0; i < numListeners; ++i) { listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); } } } final boolean wasLayoutValid = isLayoutValid(); mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; if (! wasLayoutValid && isFocused()) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; if (canTakeFocus()) { // We have a robust focus, so parents should no longer be wanting focus. clearParentsWantFocus(); } else if (getViewRootImpl() == null || ! getViewRootImpl().isInLayout()) { // This is a weird case. Most-likely the user, rather than ViewRootImpl, called // layout. In this case, there's no guarantee that parent layouts will be evaluated // and thus the safest action is to clear focus here. clearFocusInternal(null, /* propagate */ true, /* refocus */ false); clearParentsWantFocus(); } else if (! hasParentWantsFocus()) { // original requestFocus was likely on this view directly, so just clear focus clearFocusInternal(null, /* propagate */ true, /* refocus */ false); } // otherwise, we let parents handle re-assigning focus during their layout passes. } else if ((mPrivateFlags & PFLAG_WANTS_FOCUS) ! = 0) { mPrivateFlags &= ~PFLAG_WANTS_FOCUS; View focused = findFocus(); if (focused ! = null) { // Try to restore focus as close as possible to our starting focus. if (! restoreDefaultFocus() && ! hasParentWantsFocus()) { // Give up and clear focus once we've reached the top-most parent which wants // focus. focused.clearFocusInternal(null, /* propagate */ true, /* refocus */ false); } } } if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) ! = 0) { mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; notifyEnterOrExitForAutoFillIfNeeded(true); }}Copy the code
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); Finally, the setFram method is called, passing the top left, bottom right and bottom four arguments:
protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; if (DBG) { Log.d(VIEW_LOG_TAG, this + " View.setFrame(" + left + "," + top + "," + right + "," + bottom + ")"); } // If (mLeft! = left || mRight ! = right || mTop ! = top || mBottom ! = bottom) { changed = true; // Remember our drawn bit int drawn = mPrivateFlags & PFLAG_DRAWN; int oldWidth = mRight - mLeft; int oldHeight = mBottom - mTop; int newWidth = right - left; int newHeight = bottom - top; boolean sizeChanged = (newWidth ! = oldWidth) || (newHeight ! = oldHeight); // Invalidate our old position invalidate(sizeChanged); mLeft = left; mTop = top; mRight = right; mBottom = bottom; MRenderNode. SetLeftTopRightBottom (mLeft, mTop, mRight, mBottom); / / marking mPrivateFlags | = PFLAG_HAS_BOUNDS; // size change callback if (sizeChanged) {sizeChange(newWidth, newHeight, oldWidth, oldHeight); } if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView ! = null) { // If we are visible, force the DRAWN bit to on so that // this invalidate will go through (at least to our parent). // This is because someone may have invalidated this view // before this call to setFrame came in, thereby clearing // the DRAWN bit. mPrivateFlags |= PFLAG_DRAWN; invalidate(sizeChanged); // parent display list may need to be recreated based on a change in the bounds // of any child invalidateParentCaches(); } // Reset drawn bit to original value (invalidate turns it off) mPrivateFlags |= drawn; mBackgroundSizeChanged = true; mDefaultFocusHighlightSizeChanged = true; if (mForegroundInfo ! = null) { mForegroundInfo.mBoundsChanged = true; } notifySubtreeAccessibilityStateChangedIfNeeded(); } return changed; }Copy the code
For each View or ViewGroup, the assigned value is the corresponding position in the layout, which is the final width and height. If you want to get the position information of the View, you must complete the layout execution to get the corresponding data. Otherwise, 0 is returned. SizeChange is called if there is a change, and onSizeChanged() is called as a callback. Boolean checks if there has been a change. This is also called the first time laout is called. When customizing a view, you can get the width and height of the control here.
private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) { onSizeChanged(newWidth, newHeight, OldWidth, oldHeight); . }Copy the code
The View and ViewGroup classes are empty implementations of the onLayout method. The FrameLayout implementation is as follows:
// FrameLaout void layoutChildren(int left, int top, int right, int bottom, Boolean forceLeftGravity) {final int count = getChildCount(); / / get DecoverView remaining space final int parentLeft = getPaddingLeftWithForeground (); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() ! = GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop;Copy the code
/ / DEFAULT_CHILD_GRAVITY = Gravity. TOP | Gravity. START default is in the upper left cornerCopy the code
int gravity = lp.gravity; if (gravity == -1) { gravity = DEFAULT_CHILD_GRAVITY; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); Final int verticalGravity = gravity & gravity_mask; Switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: if (! forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; } case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } switch (verticalGravity) {case Gravity.TOP: childTop = parentTop + lp.topmargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; } child.layout(childLeft, childTop, childLeft + width, childTop + height); }}}Copy the code
What does this code do? First, the padding value of the parent container determines how much space the DecorView should display. Then, by traversing through, The position of the upper left corner of the child View is determined according to the layout_gravity property of the child View, the measuring width and height of the child View, and the padding value of the parent container. Finally, the child.layout method is used to combine the upper left corner coordinates with the width and height of the child View. This will determine the location of the child View.
If the child View is a ViewGroup, repeat the steps above. If it is a View, it calls the View layout method directly, which sets the View’s four layout parameters inside, and then calls the onLayout method, which requires subclasses to implement. Different views are implemented differently, so I won’t analyze them here.
The basic idea of layout stage is to start from the root View and recursively complete the layout of the whole control tree. Summarize the overall process of layout in View drawing process:
Here is a brief introduction to the commonly used method of obtaining width and height in our development process:
GetMeasuredWidth (), getMeasuredHeight(), measuredWidth (), measuredHeight (); You can call the above method in onLayout to get the value,
GetWidth (),getHeight(), getWidth(), getWidth(),getHeight(), getWidth(), getWidth();
In general, the width and height values are the same in the above two cases, but in non-general cases, by rewriting the View layout(), the measured value is not the same as the final value.
The above is the introduction of the layout in the process of View drawing, there is an understanding of the wrong place, please correct, welcome comments