The sequence of parent View(current View) and child View in View measurement, layout, and drawing
View measurement, layout and rendering process, in the end is the first measurement (layout, rendering) parent View, or the first measurement of child View, this article will give the answer from the source point of View.
OnMeasure process
The View is measured from the measure method. Let’s take a look at the View#measure method:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
if (forceLayout || needsLayout) {
...
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else{... }... }... }Copy the code
As you can see, measure calls View#onMeasure.
Let’s take a look at the implementation of View#onMeasure: it actually sets the width and height of the measurement.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
Copy the code
LinearLayout#onMeasure
A ViewGroup will override the View#onMeasure method. Each ViewGroup implementation is roughly the same.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else{ measureHorizontal(widthMeasureSpec, heightMeasureSpec); }}Copy the code
We choose the measureVertical method here:
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) { mTotalLength = 0; . // Loop over all children, call View#measure
// See how tall everyone is. Also remember max width.
for(int i = 0; i < count; ++i) { final View child = getVirtualChildAt(i); .if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
...
} else{... MeasureChildBeforeLayout (Child, I, widthMeasureSpec, 0, heightMeasureSpec, usedHeight); Final int childHeight = child.getMeasuredHeight(); . MTotalLength = math.max (totalLength, totalLength + childHeight + lp.topMargin + lp.bottomMargin + getNextLocationOffset(child)); }... }... // Addinour padding mTotalLength += mPaddingTop + mPaddingBottom; // Assign mTotalLength to heightSize and convert heightSize int heightSize = mTotalLength; heightSize = Math.max(heightSize, getSuggestedMinimumHeight()); // Convert heightSize and HeightMeasure to heightSizeAndState // Reconcile our calculated size with the HeightMeasure int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0); . / / call the ViewThe setMeasuredDimension method sets the measurement width and height of the LinearLayout
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState); . }Copy the code
Here we start a for loop that measures the size of each child View. ② After the sub-views are measured, mMeasuredWidth and mMeasuredHeight will be assigned to each View. The height of each child View is added to the member variable mTotalLength (mTotalLength). The mTotalLength (mTotalLength) is converted to heightSize and heightSize (heightSize) is converted to heightSizeAndState. (5) Call the setMeasuredDimension method at the end of the measureVertical method, and use the resulting heightSizeAndState to set the height of the LinearLayout.
In short, the measure of the sub-view is first called to measure, and then the width and height of the sub-view are recorded. After the measurement of all sub-views is completed, the width and height of the current view can be obtained.
Therefore, the measurement process is to measure the child View first, and then measure the parent View, because the width and height of the parent View will use the measurement results of the child View.
LinearLayout#measureChildBeforeLayout
Here’s a quick look at the LinearLayout#measureChildBeforeLayout method: it calls measureChildWithMargins
void measureChildBeforeLayout(View child, int childIndex,
int widthMeasureSpec, int totalWidth, int heightMeasureSpec,
int totalHeight) {
measureChildWithMargins(child, widthMeasureSpec, totalWidth,
heightMeasureSpec, totalHeight);
}
Copy the code
LinearLayout# measureChildWithMargins method:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
Copy the code
This method is divided into three steps: ① Obtain the child MarginLayoutParams parameter. (ViewGroup#getChildMeasureSpec); (ViewGroup#getChildMeasureSpec); ③ Using the generated MeasureSpec parameter, the View#measure method is called to measure the child View.
If we take a closer look at the arguments to getChildMeasureSpec, the second argument is the padding of the current View and the margin of the child View.
ViewGroup# getChildMeasureSpec method
Now let’s look at the core of the ViewGroup measurement: the ViewGroup#getChildMeasureSpec method. We often say that the measurement of the child View is influenced by both the parent View and the child View, which comes from this method.
// Spec is the parent View's MeasureSpec parameter; // The padding value is added from the parent View's padding value and the child View's margin value; // childDimension is the child View's MeasureSpec parameter; public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on uscase MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
//noinspection ResourceType
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
Copy the code
As you can see in the figure below, this diagram is a summary of the getChildMeasureSpec method.
View#measure
Once the child View is drawn, mMeasuredWidth and mMeasuredHeight will be assigned.
View#measure -->
View#onMeasure -->
View#setMeasuredDimension -->
View#setMeasuredDimensionRaw
Copy the code
View#setMeasuredDimensionRaw
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
Copy the code
Clearly, the mMeasuredWidth and mMeasuredHeight values of the View are assigned, so that when the View is measured, We can use View#getMeasuredWidth and View#getMeasuredHeight to get the measuremeasuredHeight of the View.
OnLayout process
The layout process starts with the View#layout method.
View#layout
public void layout(int l, int t, int r, int b) { ... Boolean changed = isLayoutModeOptical(mParent)?setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
if(changed | | (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) = = PFLAG_LAYOUT_REQUIRED) {/ / call onLayout method. onLayout(changed, l, t, r, b); . }... }Copy the code
In this case, setOpticalFrame and setFrame methods will be called respectively according to the value of isLayoutModeOptical. So regardless of whether isLayoutModeOptical is true or false, the View#setFrame method is called to lay out the current View.
We then call the View#onLayout method, which determines the position of the child View.
A look at the source code shows that the View and ViewGroup onLayout methods are empty implementations, and the ViewGroup#onLayout method is defined as an abstract method, forcing subclasses to implement it.
View#onLayout:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
Copy the code
ViewGroup#onLayout:
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
Copy the code
View#setFrame
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if(mLeft ! = left || mRight ! = right || mTop ! = top || mBottom ! = bottom) { ... mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); . }return changed;
}
Copy the code
We use the setFrame method to set the position of the View’s four vertices. Once their values are determined, the View’s position in the parent container is determined.
The View#onLayout method is implemented on a LinearLayout.
LinearLayout# onLayout source code:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
layoutVertical(l, t, r, b);
} else{ layoutHorizontal(l, t, r, b); }}Copy the code
We choose LinearLayout#layoutVertical for analysis:
void layoutVertical(int left, int top, int right, int bottom) {
...
for (int i = 0; i < count; i++) {
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if(child.getVisibility() ! = GONE) { ...setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); . }}}Copy the code
As you can see, the LinearLayout#setChildFrame method is called to position the layout of the subviews by iterating through all the subviews.
LinearLayout#setChildFrame
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
Copy the code
It is simply implemented by calling the View#layout method for positioning.
To sum up, the core method of View layout is View#layout method, first to the parent View (current View) layout, and then call onLayout method to the child View layout positioning.
Ontouch process
View drawing also starts with the View#draw(Canvas Canvas) method,
public void draw(Canvas canvas) {
...
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
// Step 1, draw the background, if needed
if (!dirtyOpaque) {
drawBackground(canvas);
}
...
// Step 2, save the canvas' layers ... saveCount = canvas.getSaveCount(); // Step 3, draw the content if (! dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 5, draw the fade effect and restore layers ... canvas.restoreToCount(saveCount); drawAutofilledHighlight(canvas); // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); }Copy the code
As can be seen above, the drawing process is carried out in the following six steps: ① Draw the background ② save the layer if necessary ③ Draw the content of the current View ④ Draw the sub-view with dispatchDraw method ⑤ Draw the boundary and restore the layer if necessary ⑥ Draw the relevant decoration (such as the scroll bar).
Not surprisingly, the dispatchDraw method is an empty implementation in the View as follows:
protected void dispatchDraw(Canvas canvas) {
}
Copy the code
ViewGroup#dispatchDraw
Let’s look at the implementation of dispatchDraw in ViewGroup:
@Override
protected void dispatchDraw(Canvas canvas) {
...
for (int i = 0; i < childrenCount; i++) {
...
if((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! = null) { more |= drawChild(canvas, child, drawingTime); }}... }Copy the code
Here, too, we iterate over each child View and draw each child View by calling the ViewGroup#drawChild method.
Let’s look at the implementation of ViewGroup#drawChild:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
Copy the code
The implementation is simple, just call the View#draw method to draw each child View.
In general, the core method of View drawing is the View#draw method, which first draws the parent View (current View), and then calls the dispatchDraw method to draw the child View.
conclusion
1, the measurement process is to measure the child View first, and then measure the parent View (current View), because the width and height of the parent View needs to use the measurement results of the child View. 2, the core method of View layout is View#layout method, the first parent View (current View) layout, and then call onLayout method on the child View layout positioning. 3, the core method of View drawing is View#draw method, first to the parent View (current View) to draw, and then call dispatchDraw method to draw the child View.
reference
View measurement, layout and drawing principle
2. Key methods in View measurement, layout and drawing process