The introduction
In the previous Android Layout Window drawing analysis article, we showed you how to load and display a layout into a PhoneWindows window. In the Detail of the Inflate source for Android, we look at how to transform the XML layout file into a View tree. However, there is no specific explanation about the location and size of the View tree. In this article, we will continue to study how views are laid out and drawn on the basis of the previous two chapters.
Remember the requestLayout() method we highlighted in the addView block at the end of the Android Layout Window Rendering Analysis article?
Don’t worry if you don’t remember, I will post the code
//ViewRootImpl.java public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { ... // The performTraversals method will be called to render the View. // The View will be measured, layout, and render requestLayout(); . }}Copy the code
This line of code is the entrance to the View inflate. After measure,layout and draw, the View tree created in the “Detail of Android’s Inflate Source” is drawn. When this article is complete, you can complete the entire process of how Android goes from XML to view tree, then draws the view tree, then adds the View to DecterView and displays it.
Basic knowledge of
The drawing process of Android View is divided into 3 steps: measurement, layout and drawing
The source code
//ViewRootImpl.java
public void requestLayout(a) {
. / / the Boolean variables can be in ViewRootImpl performLayout () at the beginning of the set to true and false end
// Indicates that you are not in the Layout process
if(! mHandlingLayoutInLayoutRequest) {// To check thread safety, only the thread that created the view can operate on this thread (i.e. main thread).
checkThread();
// Flag requests to draw
mLayoutRequested = true;
// Perform scheduling drawingscheduleTraversals(); }}Copy the code
This code is mainly a check, if the layout is currently in progress, then not processing. Otherwise, schedule drawing is performed.
//ViewRootImpl.java
void scheduleTraversals(a) {
if(! mTraversalScheduled) {/// indicates that no other draw requests will be queued until this draw request is queued
mTraversalScheduled = true;
//Handler synchronization barrier, which intercepts Looper to get and distribute synchronous messages, can only handle asynchronous messages
// In other words, rendering of the View takes precedence
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//mChoreographer is able to accept system time pulses and unify animation, input, and drawing timing, enabling frame-by-frame drawing
// Add an event callback type. When drawing, the mTraversalRunnable method is called
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
Copy the code
Inside this function, mChoreographer will start scheduling the work we want to perform. The main job of this object is to cut the input, animation, drawing and other work according to the frame drawing according to the system time pulse. The callback object mTraversalRunnable of the input parameter is the object that will be executed when the corresponding frame period arrives. So let’s keep track of what’s going on inside.
//ViewRootImpl.java
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run(a) { doTraversal(); }}void doTraversal(a) {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// Remove the synchronization barrier
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// The focus method performs the drawing work
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false; }}}Copy the code
So the final traversal is the doTraversal() method. Then performTraversals() is called. This method is the function object that we are going to finally examine, and this function is part of the core code of View drawing. All measurement, layout, and drawing work is performed in this function.
With nearly a thousand lines of code, we can only break them down and parse them.
//ViewRootImpl.java
private void performTraversals(a) {
// Cache mView with final modifier to avoid runtime modification
final View host = mView;
// If it is not added to the DecorView, it returns directly
if (host == null| |! mAdded)return;
// Set the flag bit being traversed
mIsInTraversal = true;
// The tag is about to start drawing the View
mWillDrawSoon = true;
// Flag bit for whether the view size changes
boolean windowSizeMayChange = false;
boolean surfaceChanged = false;
/ / property
WindowManager.LayoutParams lp = mWindowAttributes;
// The width and height of the top-level Decor
int desiredWindowWidth;
int desiredWindowHeight;
// Whether the top-level Decor is visible
final int viewVisibility = getHostVisibility();
// The visibility of view Decor has changed
final booleanviewVisibilityChanged = ! mFirst&& (mViewVisibility ! = viewVisibility || mNewSurfaceNeeded || mAppVisibilityChanged); mAppVisibilityChanged =false;
final boolean viewUserVisibilityChanged = !mFirst &&((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
...
requestLayout = 0;
// Displays the width and height of the currently drawn Activity window
Rect frame = mWinFrame;
// In the constructor, set to true to indicate whether it is the first request to perform measurement, layout drawing
if (mFirst) {
// Whether to draw all
mFullRedrawNeeded = true;
// Whether the layout request needs to be executed
mLayoutRequested = true;
final Configuration config = mContext.getResources().getConfiguration();
// If there is a status bar or input box, the width and height of the Activity window strip out the status bar
if (shouldUseDisplaySize(lp)) {
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
// Otherwise it is the width and height of the entire screen
desiredWindowWidth = mWinFrame.width();
desiredWindowHeight = mWinFrame.height();
}
/** * for the first time, The View tree is first displayed in the Window * and then some assignment is made to mAttachinfo * AttachInfo is the object of the AttachInfo class of the static inner class of the View * it mainly stores a set of View information when the View is attached to its parent Window */
mAttachInfo.mUse32BitDrawingCache = true;
mAttachInfo.mWindowVisibility = viewVisibility;
mAttachInfo.mRecomputeGlobalAttributes = false;
mLastConfigurationFromResources.setTo(config);
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
// Set the layout direction
if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
host.setLayoutDirection(config.getLayoutDirection());
}
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
} else {// If it is not the first time to perform the measurement and layout operation, just use the width and height information of the frame, that is, the width and height saved last time
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
// mWidth and mHeight are also used to describe the current width and height of the Activity window. Their values are calculated the last time the application process actively requests the WindowManagerService service and remain the same until the next time the application process requests the WindowManagerService service to recalculate
//desiredWindowWidth and desiredWindowHeight represent the current width of the Activity window
if(desiredWindowWidth ! = mWidth || desiredWindowHeight ! = mHeight) {//mWidth represents the frame.width() value from the last time the method was executed. If the two values are not equal, then the view has changed and needs to be remeasured.
if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
// We need to redraw the flag bit
mFullRedrawNeeded = true;
// The layout flag bit needs to be executed
mLayoutRequested = true;
// Mark window width and height changed
windowSizeMayChange = true; }}Copy the code
The main job of this code is to calculate the width and height of the current Activity. The knowledge point that involves is relative say still a few less
- If it is the first time it is requested to draw, it will be set according to the screen information. If the window has a status bar, the Activity width is stripped from the Decor for the status bar, otherwise it’s set to the entire screen width
- If it is not the first time the Activity is executed, the width and height is the value saved when the layout was last measured and drawn. That’s the width and height information in the frame member variable.
- The mWidth and mHeight values in the frame are calculated by WMS and remain unchanged until the next WMS calculation. While desiredWindowWidth and desiredWindowHeight are the width and height of the current Activity. If the two are different, it indicates that the window has changed, and you need to set mLayoutRequested and windowSizeMayChange to indicate that the layout needs to be performed in the subsequent processing.
- There is a related assignment handling of the mAttachInfo object. This is the View.AttachInfo class, which is responsible for attaching the current View to its parent window.
Let’s move on to the second piece of code.
//ViewRootImpl.java
// If the window is not visible, remove the accessibility focus
if(mAttachInfo.mWindowVisibility ! = View.VISIBLE) { host.clearAccessibilityFocus(); }// Execute the Runnable stored in the HandlerActionQueue HandlerAction array
getRunQueue().executeActions(mAttachInfo.mHandler);
boolean insetsChanged = false;
// Whether the layout needs to be reexecuted (the layoutRequest request was executed and the current page did not stop).
booleanlayoutRequested = mLayoutRequested && (! mStopped || mReportNextDraw);if (layoutRequested) {
/ / access to Resources
final Resources res = mView.getContext().getResources();
if (mFirst) {
// Indicates whether the View Window is in touch modemAttachInfo.mInTouchMode = ! mAddedTouchMode;// Make sure the Window's touch mode is set
ensureTouchModeLocally(mAddedTouchMode);
} else {
// Determine whether the insects have changed.// If the root layout of the current window is wrap, such as dialog, give it as much width and height as possible, the width and height of the screen will be assigned to it
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;
// If there is a status bar or input box, the width and height of the Activity window strip out the status bar
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
// Get the configuration information of the mobile phoneConfiguration config = res.getConfiguration(); desiredWindowWidth = dipToPx(config.screenWidthDp); desiredWindowHeight = dipToPx(config.screenHeightDp); }}}// Make the prediction window size to achieve better display size. For example, dialog, if wrap is set, we will give the maximum width and height, but if just one line is displayed, the display will not be particularly elegant, so we will use it
//measureHierarchy to try to show a comfortable UI
windowSizeMayChange |= measureHierarchy(host, lp, res,desiredWindowWidth, desiredWindowHeight);
}
Copy the code
This code mainly deals with the visibility of some views, etc
- Set the focus
- Set touch mode
- If the window and layout uses wrap, the width and height are maximized and then reprocessed using the measureHierarchy() method.
The measureHierarchy() method above is one place we can look
//ViewRootImpl.java
// Measure the hierarchy
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
// The spec used to describe the final width
int childWidthMeasureSpec;
// The spec used to describe the final height
int childHeightMeasureSpec;
boolean windowSizeMayChange = false;
boolean goodMeasure = false;
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
// On large screens, we don't want the dialog box to just stretch to fill the entire screen width to display a line of text. Try the layout in a smaller size first to see if it fits
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
// Get the config_prefDialogWidth setting through the assertManager. This information is stored in the mTmpValue
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
// Get the height set in dim
baseSize = (int) mTmpValue.getDimension(packageMetrics);
}
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize+ ", desiredWindowWidth=" + desiredWindowWidth);
if(baseSize ! =0 && desiredWindowWidth > baseSize) {
// combine SPEC_MODE with SPEC_SIZE as a MeasureSpec
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
// Perform a measurement
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight()
+ ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
+ " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
/ / getMeasuredWidthAndState get drawn as a result, if the dissatisfaction with the results of the set MEASURED_STATE_TOO_SMALL figure,
if ((host.getMeasuredWidthAndState() & View.MEASURED_STATE_TOO_SMALL) == 0) {
// Make sure you are satisfied
goodMeasure = true;
} else {
// The drawing is not satisfactory
// Reset the width to an average?
baseSize = (baseSize + desiredWindowWidth) / 2;
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="+ baseSize);
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
// Measure this line again
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("+ host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
if ((host.getMeasuredWidthAndState() & View.MEASURED_STATE_TOO_SMALL) == 0) {
if (DEBUG_DIALOG) Log.v(mTag, "Good!");
goodMeasure = true; }}}}if(! goodMeasure) {// If you are not satisfied after two times, you can only display it in full screen
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
// Perform the third measurement
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if(mWidth ! = host.getMeasuredWidth() || mHeight ! = host.getMeasuredHeight()) { windowSizeMayChange =true; }}return windowSizeMayChange;
}
Copy the code
Can see if the Dialog is set wide high are wrap, will be the first to use system com. Android. Internal. R.d imen. Config_prefDialogWidth value for test, if it is used, If not, use the average of this value and the height of the view. If not, set the width and height of the view. As you can see, the performMeasure method may be executed multiple times, but these three times are not part of the View drawing process, but are just auxiliary processing to determine the size of Windows.
Now that all the preparatory work is done, it’s time to move on to the topic and measure, lay out, and draw the top-level View tree.
measurement
Conditions for measurement:
//ViewRootImpl.java
// Clear layoutRequested, so that if layoutRequested=true, a new Layout request is added.
if (layoutRequested) {
mLayoutRequested = false;
}
// Used to determine if the window needs to be changed.
//layoutRequested is true to indicate that the View is invoking its own requestLayout.
// windowSizeMayChange: Indicates that the size of the View tree is inconsistent with the size of the window
boolean windowShouldResize = layoutRequested && windowSizeMayChange
// Determine whether the size of the View tree is equal to the size of the window&& ((mWidth ! = host.getMeasuredWidth() || mHeight ! = host.getMeasuredHeight())// Window width changed. Window set wrap. The calculated window size desiredWindowWidth is larger than the last measured frame.width(), and is inconsistent with the calculated width and height of WindowManagerService, indicating that the window size has changed|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT && frame.width() < desiredWindowWidth && frame.width() ! = mWidth) || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT && frame.height() < desiredWindowHeight && frame.height() ! = mHeight));// The height of the window changes.
windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
// If the activity is restarted, force resize via WMS
windowShouldResize |= mActivityRelaunched;
final boolean computesInternalInsets =mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()|| mAttachInfo.mHasNonEmptyGivenInternalInsets;
boolean insetsPending = false;
int relayoutResult = 0;
boolean updatedConfiguration = false;
final int surfaceGenerationId = mSurface.getGenerationId();
final boolean isViewVisible = viewVisibility == View.VISIBLE;
final boolean windowRelayoutWasForced = mForceNextWindowRelayout;
boolean surfaceSizeChanged = false;
// If the window information changes, it needs to be measured
if(mFirst || windowShouldResize || insetsChanged ||viewVisibilityChanged || params ! =null || mForceNextWindowRelayout) {
Copy the code
Sometimes there is no need for our interface to measure work, after all, measurement is a time-consuming and tedious work. Therefore, there are certain execution conditions for the measurement work, and the above code can tell us when the whole page measurement work.
- MFirst to true. The presentation window is the first time the measurement, layout, and drawing operations are performed.
- WindowShouldResize flag bit is true. The flag bit is mainly used to determine whether the window size has changed.
- InsetsChanged to true. This indicates that the window overscan and other side lining areas have changed
- ViewVisibilityChanged to true. The flag bit is that the View’s visibility has changed
- Params indicates that the properties of the window have changed.
- MForceNextWindowRelayout to true. Indicates that a layout operation is set to enforce
When we determine that we need to make measurements, the next step is to make specific measurements.
Measurement execution:
//ViewRootImpl.java.// Ask WMS to calculate the size of the Activity window and the size of the edge area
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
if(! mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) {if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: "+ mPendingMergedConfiguration.getMergedConfiguration()); performConfigurationChange(mPendingMergedConfiguration, ! mFirst,INVALID_DISPLAY/* same display */);
updatedConfiguration = true;
}
// Do some boundary processing
final booleanoverscanInsetsChanged = ! mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets); contentInsetsChanged = ! mPendingContentInsets.equals(mAttachInfo.mContentInsets);final booleanvisibleInsetsChanged = ! mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets);final booleanstableInsetsChanged = ! mPendingStableInsets.equals(mAttachInfo.mStableInsets);final booleancutoutChanged = ! mPendingDisplayCutout.equals(mAttachInfo.mDisplayCutout);final booleanoutsetsChanged = ! mPendingOutsets.equals(mAttachInfo.mOutsets); surfaceSizeChanged = (relayoutResult& WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) ! =0;
surfaceChanged |= surfaceSizeChanged;
final booleanalwaysConsumeSystemBarsChanged =mPendingAlwaysConsumeSystemBars ! = mAttachInfo.mAlwaysConsumeSystemBars;final booleancolorModeChanged = hasColorModeChanged(lp.getColorMode()); .// Get the maximum size from window Session as the current window size
if (mWidth != frame.width() || mHeight != frame.height()) {
mWidth = frame.width();
mHeight = frame.height();
}
....
// The current page is not paused or has received a request to draw
if(! mStopped || mReportNextDraw) {// Get focus
booleanfocusChangedDueToTouchMode = ensureTouchModeLocally((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) ! =0);
// The width and height have changed
if(focusChangedDueToTouchMode || mWidth ! = host.getMeasuredWidth()|| mHeight ! = host.getMeasuredHeight() || contentInsetsChanged ||updatedConfiguration) {Lp.width and lp.height denote the width and height of the DecorView root layout
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Perform the measurement
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
// The marker needs to be remeasured
boolean measureAgain = false;
//lp.horizontalWeight indicates how much extra space will be allocated horizontally (in the horizontal direction) to views associated with these LayoutParam. if
// The view should not be stretched, specify 0. Otherwise, additional pixels will be allocated in views where ownership is greater than zero.
if (lp.horizontalWeight > 0.0 f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0 f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY);
measureAgain = true;
}
// If there is a change, perform the measurement again
if (measureAgain) {
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
// Set the request Layout flag
layoutRequested = true; }}}else {
maybeHandleWindowMove(frame);
}
Copy the code
A series of boundary treatments are performed before the measurement operation is performed. Then, if the page is visible, the performMeasure() method is called, and when the measurement is complete, the secondary measurement is determined based on whether the weight is set. Here we look at the performMeasure() function execution. The two parameters here are MeasureSpec generated based on the width and height of the screen.
// Perform the measurement
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
try {
Call the measure methodmView.measure(childWidthMeasureSpec, childHeightMeasureSpec); }}Copy the code
The mView here is a DecorView root layout that records the root node of the View tree managed by the View WrootimPL, which is a ViewGroup. The measure() method is then called.
//View.java
// Measure the width and height of the view. The parameter is the width and height information of the parent class
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {...long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
// See if the spec changes
final booleanspecChanged = widthMeasureSpec ! = mOldWidthMeasureSpec|| heightMeasureSpec ! = mOldHeightMeasureSpec;// Whether the width and height are fixed
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
// The height measured last time is the same as the current maximum height
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
// Whether layout is required
final booleanneedsLayout = specChanged&& (sAlwaysRemeasureExactly || ! isSpecExactly || ! matchesSpecSize);// If you need to draw or set a mandatory layout
if (forceLayout || needsLayout) {
// Clear the measurement mark first
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
//*** focus on measuring ourselves
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
long value = mMeasureCache.valueAt(cacheIndex);
setMeasuredDimensionRaw((int) (value >> 32), (int) value);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// An error will be reported if the developer sets the onMeasure but does not call setMeasuredDimension()
// The setMeasuredDimension() method sets PFLAG_MEASURED_DIMENSION_SET to mPrivateFlags
if((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) ! = PFLAG_MEASURED_DIMENSION_SET) {throw new IllegalStateException("View with id " + getId() + ":"+ getClass().getName() + "#onMeasure() did not set the" + " measured dimension by calling"+ " setMeasuredDimension()");
}
// Set the request layout flag for further layout work
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
// Record the MeasureSpec value given by the parent control for later determination of whether the parent control has changed
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
// cache it
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32| (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
Copy the code
As you can see, there is no measurement in this method. The actual measurement is handled by onMeasure(). This function only checks the correctness of the onMeasure method
- If there is no change in the width and height information of the parent class transmitted last time, the measurement will not be carried out.
- Because the onMeasure method can be overridden by subclasses, a subclass must call setMeasuredDimension() while overwriting or an error will be reported
- There is a caching mechanism, from which previous measurements can be retrieved if the measurements are not forced.
//View.java
// The specific implementation of the onMeasure method should be overridden by subclasses to provide a more reasonable and efficient implementation
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Save the measurement results.
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
Copy the code
In the View implementation, only the setMeasuredDimension method is called inside the onMeasure.
//View.java
// The onMeasue method must call this method to save the measurement data
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
// If there is an optical boundary, do some processing for the optical boundary
if(optical ! = isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets();int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
// Save measurement width and height information
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
// Add PFALG_MEASURED_DIMENSION_SET to mPrivateFlags to prove that onMeasure() saves the measurement
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
Copy the code
In turn, let’s look at how setMeasuredDimension input parameters are obtained. GetDefaultSize and getSuggestedMinimumWidth
//ViewRootImpl.java
// Returns the recommended minimum width that the view should use.
protected int getSuggestedMinimumWidth(a) {
// If there is no background, return the minimum width. If there is a background, use mMinWidth and the minimum width of the background, the maximum of both
return (mBackground == null)? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }// Returns the final width and height information based on the proposed size and the current control mode
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:// If (wrap_content) is not specified, use the suggested size value
result = size;
break;
case MeasureSpec.AT_MOST:// Set the maximum value (match_parent)
case MeasureSpec.EXACTLY:// If width="20dp", use the size value passed in by the parent class
result = specSize;
break;
}
return result;
}
Copy the code
So the minimum width and height of the control is computed once, and the values vary depending on the measureSpec information passed in by the parent class.
This is just the View of the measurement of the work of the analysis, in fact, in actual use, more is the subclass of the ViewGroup measurement, its implementation is more complex, these are left for later processing, we are just tracking the View of the drawing process.
Now that the entire measurement is done, let’s go back to the main line and see what happens when the measurement is done.
layout
//ViewRootImpl.java
// Unpaused state or request layout with the request Layout flag set.
final booleandidLayout = layoutRequested && (! mStopped || mReportNextDraw);boolean triggerGlobalLayoutListener = didLayout || mAttachInfo.mRecomputeGlobalAttributes;
// The layout operation needs to be performed
if (didLayout) {
// key method **** to execute layout, internal will call the View layout method, thus calling onLayout method to achieve the layout
performLayout(lp, mWidth, mHeight);
// By now, all views have been measured and positioned. Now we can calculate the transparent region
if((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) ! =0) {... }// Trigger the global Layout listener, which we set up as mTreeObserver
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
Copy the code
For control layout operations, the amount of code is relatively small. The main is to determine whether the layout operation is needed, and then call the performLayout method to carry out the layout. PerformLayout calls the View’s Layout() method and then calls its onLayout() method, similar to measure. So here is no more analysis, interested friends can see for themselves. Or check out my Github source code parsing project, which periodically updates comments on the source code.
draw
//ViewRootImpl.java
booleancancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || ! isViewVisible;// No cancel drawing
if(! cancelDraw) {// If an animation exists, perform the animation effect
if(mPendingTransitions ! =null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
// Key method *** performs the drawing work
performDraw();
} else {// Cancel the drawing.
if (isViewVisible) {
// Try again
// If the current page is visible, reschedule
scheduleTraversals();
} else if(mPendingTransitions ! =null && mPendingTransitions.size() > 0) {
// Unanimate if animation effect exists
for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).endChangingAnimations(); } mPendingTransitions.clear(); }}// Clear the traversing flag bit
mIsInTraversal = false;
}
Copy the code
The above code implements the rendering of the View. The key method inside is performDraw().
For rendering, unlike measurement and layout, which is handed over to the ThreadedRenderer, a ThreadedRenderer, let’s track the main flow
/ / ViewRootViewImpl class
private void performDraw(a) {... draw(fullRedrawNeeded); . } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --/ / ViewRootViewImpl class
private void draw(boolean fullRedrawNeeded) {... mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo,this); . } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --/ / ThreadedRenderer class
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {... updateRootDisplayList(view, callbacks); . } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --/ / ThreadedRenderer class
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {... updateViewTreeDisplayList(view); . } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --/ / ThreadedRenderer class
private void updateViewTreeDisplayList(View view) {
view.mPrivateFlags |= View.PFLAG_DRAWN;
view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
== View.PFLAG_INVALIDATED;
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
// The View's updateDisplayListIfDirty method is called
// This View is actually a DecorView
view.updateDisplayListIfDirty();
view.mRecreateDisplayList = false;
}
Copy the code
As you can see, the updateDisplayListIfDirty method with its argument view (that is, DecorView) is finally called.
//View.java
public RenderNode updateDisplayListIfDirty(a) {... draw(canvas); . }Copy the code
In this method, the View’s draw(Canvas) drawing method is called. Since the DecorView method overrides the draw method, the DecorView’s draw method is executed first.
//DecorView
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if (mMenuBackground != null) {
mMenubackground.draw(canvas);
}
}
Copy the code
So you end up calling the Draw method in the View class.
public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; int saveCount; DrawBackground (canvas); // Skip step 2 and step 5 final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) ! = 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) ! = 0;if(! verticalEdges && ! HorizontalEdges) {// Step 3 Draw the content onDraw(canvas); // Step 4 Draw children dispatchDraw(canvas); // Step 6 Draw foreground (foreground, scroll bar) onDrawForeground(canvas); / / Step 7, the draw the default focus highlight / / Step 7, draw the focus of the default highlight drawDefaultFocusHighlight (canvas); // we're done... return; }Copy the code
In the drawing method, the drawing process is divided into 7 steps, among which step 2 and Step 5 are generally skipped
Let’s take a look at the execution steps. The specific drawing process is no longer analyzed one by one.
- Draw the background.
- If necessary, save the canvas layer for fading in
- Draws the contents of the view
- Draw the Children
- If necessary, draw the fade edge and restore the layer
- Draw decorations (for example, scroll bars)
- Draw the default focus highlighting
At this point, our entire View drawing process is complete. There’s plenty of room to dig into the details. We’ll analyze it when we have a chance.
conclusion
- The Handler has a synchronization barrier mechanism that can mask synchronization messages (we’ll develop that later).
- Frame drawing for the screen is done through Choreographer, which performs screen refreshes, frame discarding, and so on.
- If the width and height of the Dialog wrap is set to the default height, the default height is set to /2, and the default height is set to /2.
- For measurement work, it happens many times during the whole process.
- Throughout the View drawing process, there are callbacks to the mTreeObserver. Here we can do whatever we need to do.
- Custom controls that inherit views must override onMeasure methods. MeasureSpec = AT+MOST (wrap); And the maximum is the height of the parent class, which is equivalent to match_parent. So you have to override the onMeasure method. You can see this in TextView, Button, etc. We just need to give our custom View a default internal width and height, and set the width and height when using wrap_context.
- The width and height of the control can be obtained only after onMeasure. However, in practice, multiple measurements exist and onMeasure is called in onResume. So if you want to get the width and height, you need to go through other ways. Here are four
- OnWindowFocusChanged derive
- To get the post
- ViewTreeObserver listening
- Manually call view.measure
This article is published by Kaiken!
Synchronous public number [open Ken]