Three process triggers
The three major processes of View are: measure — > Layout — >draw,
In combination with the start process of the activity, the activity object is created, and then after the create, start, resume stages, wm.addView(decor, L) is called in the resume. This method creates the ViewRootImpl object and calls the setVIew(Decorview) method of ViewRootImpl, followed by requestLayout and scheduleTraversals().
ScheduleTraversals () sends the TraversalRunnable, and the TraversalRunnable callback calls doduletraversal. Then performTraversals is called, and measureHierarchy is called. Finally, performMeasure, performLayout and performDraw start the three processes of measurement, layout and drawing.
So let’s write a simple layout that we’ll use next
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/layout1"
android:layout_width="match_parent"
android:layout_height="300dp">
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World" />
</FrameLayout>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="I am the button." />
</FrameLayout>
Copy the code
Measure the measure
Viewrootimp #performMeasure DecorView#measure ->View#onMeasure ->FrameLayout#onMeasure-> subview #measure.
Based on the above layout, the important method calls in measure are as follows:
The measure of R.I. D. Layout is implemented. The onMeasure of R.I. D. Layout is implemented. The Measure of R.I. D. Layout is implemented The measure for ext is measured. The onMeasure for EXT is measured. The setMeasureDimension for EXT is measured R.measuredtton onMeasure R.MEASUREdTton setMeasureDimension R.MeasuredOut SetMeasureDimension R.MeasuredOutCopy the code
Because the parent View needs the measured dimensions of the child View to set its own size, the setMeasuredimension of the parent View is always later than that of the child View.
Calculation of the subview measurement specification: The MeasureSpec class consists of mode and size, which are packaged into an int value using binary. An int has 32 bits, with two bits 31 and 32 representing mode and the first 30 bits representing size. The MeasureSpec class uses one variable to carry two pieces of data (size, mode) to reduce object memory allocation and provides methods to package and unpack.
OnMeasure -> measureChildWithMargins -> getChildMeasureSpec -> child
The key is to look at the getChildMeasureSpec method, where the switchcase and if-else can be summarized into the following table.
Horizontal: Parent View measurement mode Vertical: child View’s LayoutParms |
EXACTLY. | AT_MOST | UNSPECIFIED (UNSPECIFIED) |
---|---|---|---|
Specific numerical | EXACTLY+childDimension | EXACTLY+size | EXACTLY+size |
match_parent | EXACTLY+size | AT_MOST+size | UNSPECIFIED+0 |
wrap_content | AT_MOST+size | AT_MOST+size | UNSPECIFIED+0 |
Size is the measurement size of the parent – the inner and outer margins of the child View, with the maximum value 0
The MeasureSpec (width and height) of the child View is calculated according to its own characteristics. The default calculation is as follows:
Suggestedminimumwidth is calculated based on the minimum width and the background, so the measurement mode is UNSPECIFIED, which is based on this suggested size, and the other two modes are used (see table above).
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
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:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
Copy the code
Layout of the layout
ViewRootImpl#performLayout DecorView#layout ->DecorView#onLayout->FrameLayout#onLayout.
Based on the above layout, the important method calls in the layout are as follows:
The layout is executed. The setFrame is executed. The sizeChange is executed. The onLayout is executed Layout1. SetFrame. Layout1. SizeChange. OnLayout. Layout1 Rt setFrame is executed. Rt sizeChange is executed. Rt onLayout is executed. Rt onLayoutChange() is executed When the onLayoutChange() is executed, the layout is executed, the setFrame is executed, the sizeChange is executed The onLayoutChange() of R.I. D. Tutton is executed and the onLayoutChange() of R.I. D. Layout is executedCopy the code
Layout-> setFrame (save l, t, r, b) ->sizeChange ->onLayout->onLayoutChange()
When onMeasure is triggered, PFLAG_LAYOUT_REQUIRED is marked, so onLayout must be triggered.
Draw the draw
Viewrootimp #performDraw DecorView#draw ->View#draw ->View#draw ->View#draw ->View#dispatchDraw ->View#draw ->View#draw ->View# drawforegrou Nd painting foreground.
PFLAG_INVALIDATE (); pformDraw (); PFLAG_INVALIDATE ();
Based on the above layout, the important method calls in Draw are as follows:
R.I.D. layout's draw is executed, its drawBackground is executed, its onDraw is executed, its dispatchDraw is executed, and its dispatchDraw is executed The draw is executed, the drawBackground is executed, the onDraw is executed, the dispatchDraw is executed, and the draw is executed The drawBackground of EXT is implemented. The onDraw of ext is implemented. The dispatchDraw of ext is implemented. The onDrawForeground of ext is implemented The onDrawForeground of THE foreground foreground is implemented. The draw of THE foreground foreground is implemented. The drawBackground of the foreground foreground is implemented The dispatchDraw of THE command is implemented. The onDrawForeground of R.I. D. Putton is implemented. The onDrawForeground of R.I. D. Layout is implementedCopy the code
RequestLayout and invalidate
requestLayout
Call the parent’s requestLayout, and then call the Parent’s requestLayout, and then call the ViewRootImp’s requestLayout.
//View#requestLayout
public void requestLayout(a) {...// Added the PFLAG_FORCE_LAYOUT flag
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
// The PFLAG_INVALIDATED flag is added
mPrivateFlags |= PFLAG_INVALIDATED;
if(mParent ! =null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
……
}
Copy the code
//ViewRootImp#requestLayout
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}Copy the code
- In the requestLayout method of the View, set PFLAG_FORCE_LAYOUT to the View. This flag will set forceLayout in the measure method of the View to true, triggering the onMeasure measure. When you measure it, you set PFLAG_LAYOUT_REQUIRED, and that flag causes the View to fire onLayout.
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {...final booleanforceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; ...if (forceLayout || needsLayout) {
// first clears the measured dimension flag... onMeasure(widthMeasureSpec, heightMeasureSpec); ... mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; }... }Copy the code
public void layout(int l, int t, int r, int b) {...if(changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); ... }... }Copy the code
- In the requestLayout method of ViewRootImp, set mLayoutRequested to true and when performTraversals uses mLayoutRequested to determine whether measureHierarchy is invoked, This method triggers performMeasure, performLayout, and performDraw.
PerformMeasure and performLayout execute onMeasure and onLayout. MDirty is null during performDraw, so all views draw will not be called.
//ViewRootImp#draw
private boolean draw(boolean fullRedrawNeeded) {...// When requestLayout is empty, the dirty part cannot trigger the view's draw method
if(! Dirty. IsEmpty () | | mIsAnimating | | accessibilityFocusDirty) {... }... }Copy the code
It is worth noting that: So in the layout method, you call setFrame, and that method has a logic that triggers invalidate, and invalidate triggers onDraw, so you can say requestLayout only changes the l,t,r,b of a particular view, Will trigger invalidate, which will trigger onDraw.
/ / pseudo code
if(l,t,r,b changed) {... Invalidate... }Copy the code
RequestLayout is called upwards, so the child view has no chance to flag PFLAG_FORCE_LAYOUT, so it will not use forceLayout to determine whether to measure or not, but needsLayout.
final booleanspecChanged = widthMeasureSpec ! = mOldWidthMeasureSpec || heightMeasureSpec ! = mOldHeightMeasureSpec;final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
final booleanneedsLayout = specChanged && (sAlwaysRemeasureExactly || ! isSpecExactly || ! matchesSpecSize);Copy the code
According to the code above:
- SpecChanged means that the measurement specification of width or height has changed. It is understood that the measurement specification has changed as a prerequisite for needsLayout.
- It is followed by three conditions, one of which can be satisfied:
- The version must be 6.0 or smaller
- The view width or height measurement specification is not accurate
- The measured dimensions of the width or height of the view have changed
invalidate
Invalidate (); invalidate(); invalidate(); invalidate(); Then call parent’s invalidateChild(this, damage);
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {.../ / add PFLAG_DIRTY
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
// Add PFLAG_INVALIDATED. Only the View uses the PFLAG_INVALIDATED flag. Parent does not use the PFLAG_INVALIDATED flag
mPrivateFlags |= PFLAG_INVALIDATED;
/ / remove PFLAG_DRAWING_CACHE_VALID
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
……
p.invalidateChild(this, damage); ... }Copy the code
InvalidateChild has a do while loop inside that calls parent’s invalidateChildInParent until it calls ViewRootImpl’s invalidateChildInParent.
public final void invalidateChild(View child, final Rect dirty) {...if(child.mLayerType ! = LAYER_TYPE_NONE) {/ / add PFLAG_INVALIDATED
mPrivateFlags |= PFLAG_INVALIDATED;
/ / remove PFLAG_DRAWING_CACHE_VALIDmPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; }...do{...if(view ! =null) {
if((view.mPrivateFlags & PFLAG_DIRTY_MASK) ! = PFLAG_DIRTY) {//mPrivateFlags Removes PFLAG_DIRTY_MASK and adds PFLAG_DIRTYview.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DIRTY; } } parent = parent.invalidateChildInParent(location, dirty); ... }while(parent ! =null); }}Copy the code
//ViewGroup#invalidateChildInParent
public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
// mPrivateFlags contains PFLAG_DRAWN or PFLAG_DRAWING_CACHE_VALID flags
if((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) ! =0) {
// Do not need to calculate dirty, hardware rendering do not need to use dirty.../ / remove PFLAG_DRAWING_CACHE_VALID
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
//// LayerType is not set, so no PFLAG_INVALIDATED is added
if(mLayerType ! = LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; }return mParent;
}
return null;
}
Copy the code
The invalidateChildInParent method of the ViewGroup is used to compute the total number of dirty items. PFLAG_DRAWING_CACHE_VALID is removed. Note that no PFLAG_INVALIDATED is used.
InvalidateChildInParent calls invalidateRectOnScreen, then scheduleTraversals, then performDraw->draw, MDirty non-empty will adjust mAttachInfo. MThreadedRenderer. The draw (mView, mAttachInfo, this); Eventually transferred to the mThreadedRenderer updateViewTreeDisplayList
private void updateViewTreeDisplayList(View view) {
// Added view. PFLAG_DRAWN;
view.mPrivateFlags |= View.PFLAG_DRAWN;
// Check whether any pFLAG_in is validated. If any, the value is true
view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)== View.PFLAG_INVALIDATED;
/ / remove PFLAG_INVALIDATED
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
// Set updateDisplayListIfDirty for the view
view.updateDisplayListIfDirty();
view.mRecreateDisplayList = false;
}
Copy the code
The View of updateDisplayListIfDirty ();
Public RenderNode updateDisplayListIfDirty() {...... if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0 || ! RenderNode. HasDisplayList () | | (mRecreateDisplayList)) {/ / only added mRecreateDisplayList PFLAG_INVALIDATED tags to true if (renderNode.hasDisplayList() && ! mRecreateDisplayList) { mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID; mPrivateFlags &= ~PFLAG_DIRTY_MASK; dispatchGetDisplayList(); return renderNode; // If mRecreateDisplayList is true, you will have a chance to go to the following draw... draw(canvas); ... return renderNode; }Copy the code
So dispatchGetDisplayList in ViewGroup, basically recreateChildDisplayList for loop, RecreateChildDisplayList call child. UpdateDisplayListIfDirty (); In this way, draw will not be invoked until a view with pFLAG_invalidate is used and the value of the pFLAG_invalidate flag is validated.
Other problems
-
Does calling requestLayout twice cause the process to execute twice?
The requestLayout is transferred to scheduleTraversals, which is controlled by mTraversalScheduled fields. If the previous scheduleTraversals message is executed, MTraversalScheduled will be set to false and the second requestLayout will take effect. If the last scheduleTraversals message was not executed, the second requestLayout will not take effect. Whether it takes effect is controlled by mTraversalScheduled.
-
Can I update the UI on the non-main thread?
When requestLayout is called by an asynchronous thread during the resume phase of the Activity, the parent is not assigned yet, so the ViewRootImpl is not called. It will not call checkThread() in the requestLayout, so it will not throw an exception. The checkThread determines whether the current field is the same thread that created the ViewRootImpl.
-
What is the difference between getMeasureWidth() and getWidth()?
- GetMeasureWidth () is the value after measure, and getWidth() is the value after layout.
- View#getWidth() is calculated by mRight – mLeft. View#layout method is public. You can modify the values of mRight and mLeft by custom parameters. This results in inconsistent values for getMeasureWidth() and getWidth().
-
When will onDraw not be called? How do I make it call?
In the draw method, the judgment is transparent, and the onDraw method is no longer used. When the ViewGroup is initialized, it calls a private method: initViewGroup, which has a setFlags(WILLL_NOT_DRAW, DRAW_MASK); SetWillNotDraw (true) is called, so for ViewGroup, it is considered transparent. So viewgroups generally don’t draw themselves, only subviews, so they don’t call onDraw(), again for performance and efficiency reasons. If you want to, you need to have a background or you need to call setWillNotDraw(false) manually at initialization time.