This article provides an in-depth understanding of how Android View works. It is recommended to read the article for a sample project, which is linked below:
ViewDemo
The source code analyzed in this article is based on Android SDK 29 (Android 10.0, i.e. Android Q).
Due to the word limit of the nuggets article, it is divided into two articles:
Learn more about how Android View works (Part 1)
Learn more about how Android View works
Drawing process
The process starts with the **requestLayout()** method.
// frameworks/base/core/java/android/view/ViewRootImpl.java
@Override
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) {Call the checkThread() method
checkThread();
mLayoutRequested = true;
// Call the scheduleTraversals() methodscheduleTraversals(); }}Copy the code
The checkThread() method checks whether the current thread is the same thread that created the ViewRootImpl.
// frameworks/base/core/java/android/view/ViewRootImpl.java
void checkThread(a) {
if(mThread ! = Thread.currentThread()) {/ / if the current thread is not create ViewRootImpl's thread is thrown CalledFromWrongThreadException anomalies
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views."); }}Copy the code
The scheduleTraversals() method allows the Android system to execute asynchronous messages related to View updates and logic related to View updates first. In the in-depth understanding of Android messaging mechanism and source code analysis (Java layer and Native layer) (top) and in-depth understanding of Android messaging mechanism and source code analysis (Java layer and Native layer) (bottom) have been mentioned in the two articles, the source code is as follows:
// frameworks/base/core/java/android/view/ViewRootImpl.java
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent.View.AttachInfo.Callbacks.ThreadedRenderer.DrawCallbacks {
// Omit some code
final Thread mThread;
// Omit some code
public ViewRootImpl(Context context, Display display) {
// Get the current thread
mThread = Thread.currentThread();
}
@UnsupportedAppUsage
void scheduleTraversals(a) {
if(! mTraversalScheduled) { mTraversalScheduled =true;
// Add synchronization barrier messages
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// Execute the add synchronization barrier message
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
// Omit some code
// Create TraversalRunnable object
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
// Omit some code
}
Copy the code
TraversalRunnable class TraversalRunnable class TraversalRunnable class TraversalRunnable class TraversalRunnable class TraversalRunnable class TraversalRunnable class TraversalRunnable class
// frameworks/base/core/java/android/view/ViewRootImpl.java
final class TraversalRunnable implements Runnable {
@Override
public void run(a) {
// Invoke doTraversal()doTraversal(); }}Copy the code
The **doTraversal() traversal method:
// frameworks/base/core/java/android/view/ViewRootImpl.java
void doTraversal(a) {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// Delete the synchronization barrier message
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// Call the performTraversals() method
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false; }}}Copy the code
**performTraversals() traversals ()
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performTraversals(a) {
// Omit some code
if(mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params ! =null || mForceNextWindowRelayout) {
// Omit some code
if(! mStopped || mReportNextDraw) {booleanfocusChangedDueToTouchMode = ensureTouchModeLocally( (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) ! =0);
if(focusChangedDueToTouchMode || mWidth ! = host.getMeasuredWidth() || mHeight ! = host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) {int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " coveredInsetsChanged=" + contentInsetsChanged);
// Call performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) and pass in the root view's width and height MeasureSpec, respectively
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
/ / implementation WindowManager. LayoutParams of weight, according to the need to increase the size, and when needed to measure
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
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 (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
// Call performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) again, The root view's width and height MeasureSpec are passed in, respectively
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true; }}}else {
// Omit some code
}
// Omit some code
final booleandidLayout = layoutRequested && (! mStopped || mReportNextDraw);boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
/ / call performLayout (WindowManager. LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) method
performLayout(lp, mWidth, mHeight);
// Omit some code
}
// Omit some code
booleancancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || ! isViewVisible;if(! cancelDraw) {// Omit some code
// Call performDraw()
performDraw();
} else {
if (isViewVisible) {
// If draw is cancelled and the View is visible, the scheduleTraversals() method is called again
scheduleTraversals();
} else if(mPendingTransitions ! =null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).endChangingAnimations(); } mPendingTransitions.clear(); }}// Omit some code
}
Copy the code
**performMeasure(int childWidthMeasureSpec, Int childHeightMeasureSpec) method, the * * * * performLayout (WindowManager. LayoutParams lp, int desiredWindowWidth, Int desiredWindowHeight) method and performDraw()** method start.
Measure the process
MeasureSpec is static inner class of the View class, it represents a 32-bit int value, high two representative SpecMode (measurement mode), low 30 representative SpecSize (specification size) in a measurement mode, the source code is as follows:
// View.java
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
/ * *@hide* /
@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})
@Retention(RetentionPolicy.SOURCE)
public @interface MeasureSpecMode {}
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
// Omit some code
}
Copy the code
SpecMode is divided into three modes, as follows:
- UNSPECIFIED: The parent element has no constraints added to the child elements, which can be of any size. This mode is typically used internally.
- EXACTLY: The parent has already determined the exact size of the child, i.e. the final size of the child is determined by the value of SpecSize, corresponding to the match_parent and concrete value modes in LayoutParams.
- AT_MOST: The size of the child element cannot be greater than the value of the SpecSize of the parent element. The default size of the child element corresponds to wrAP_content in LayoutParams.
A View’s MeasureSpec is determined by the parent’s MeasureSpec and its own LayoutParams.
Measure process starts with ViewRootImpl **performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec)**.
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
// Call the View's measure(int widthMeasureSpec, int heightMeasureSpec) method
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally{ Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code
View **measure(int widthMeasureSpec, int heightMeasureSpec)**
// View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// Omit some code
if (forceLayout || needsLayout) {
// Omit some code
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// Call onMeasure(int widthMeasureSpec, int heightMeasureSpec)
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} else {
// Omit some code
}
// Omit some code
}
// Omit some code
}
Copy the code
**onMeasure(int widthMeasureSpec, int heightMeasureSpec)
// View.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
Copy the code
Developers can rewrite this method to change the logic of measuring views.
Look at the first * * getSuggestedMinimumWidth () method and getSuggestedMinimumHeight () method, * * source as shown below:
// View.java
protected int getSuggestedMinimumHeight(a) {
return (mBackground == null)? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }protected int getSuggestedMinimumWidth(a) {
return (mBackground == null)? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }Copy the code
The getSuggestedMinimumWidth() method returns the View’s suggested minimum width, and minWidth is returned if the View has no background, and the minimum width of the background otherwise.
GetSuggestedMinimumHeight () method returns the is suggested that the minimum height of the View, if there is no background, the View is returned minHeight, otherwise it returns the minimum height of the background.
**getDefaultSize(int size, int measureSpec)**
// View.java
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
// Get the measurement mode
int specMode = MeasureSpec.getMode(measureSpec);
// Get the size in this measurement mode
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
**setMeasuredDimension(int measuredWidth, int measuredHeight)
// View.java
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if(optical ! = isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets();int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
// Get the measurement width of the View
measuredWidth += optical ? opticalWidth : -opticalWidth;
// Get the measured height of the View
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
// Call the setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) method
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
Copy the code
**setMeasuredDimensionRaw(int measuredWidth, int measuredHeight)**
// View.java
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
Copy the code
The View’s getMeasuredWidth() method uses the value of the member variable mMeasuredWidth, which returns the original measurement width, and the View’s getMeasuredHeight() method uses the value of the member variable mMeasuredWidth, which returns the original measurement width. Its function is to return the original measurement height, the source code is as follows:
// View.java
public final int getMeasuredWidth(a) {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getMeasuredHeight(a) {
return mMeasuredHeight & MEASURED_SIZE_MASK;
}
Copy the code
So if we look at the measure process of a View, let’s look at the measure process of a ViewGroup, **measureChildren(int widthMeasureSpec, int heightMeasureSpec)** measureChildren(int widthMeasureSpec, int heightMeasureSpec)
// View.java
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
// Execute the loop
for (int i = 0; i < size; ++i) {
// Get the children of the ViewGroup
final View child = children[i];
if((child.mViewFlags & VISIBILITY_MASK) ! = GONE) {// If the child is VISIBLE or INVISIBLE, measure the child. Call the measureChild(View Child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) methodmeasureChild(child, widthMeasureSpec, heightMeasureSpec); }}}Copy the code
MeasureChild (View Child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)** measureChild(View Child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)
// View.java
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// Call the View's measure(int widthMeasureSpec, int heightMeasureSpec) method
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
Copy the code
example
LinearLayout (int widthMeasureSpec, int heightMeasureSpec)** LinearLayout (int widthMeasureSpec, int heightMeasureSpec)**
// LinearLayout.java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (mOrientation == VERTICAL) {
// If it is vertical, call measureVertical(int widthMeasureSpec, int heightMeasureSpec)
measureVertical(widthMeasureSpec, heightMeasureSpec);
} else {
// If horizontal, call measureHorizontal(int widthMeasureSpec, int heightMeasureSpec)measureHorizontal(widthMeasureSpec, heightMeasureSpec); }}Copy the code
**measureVertical(int widthMeasureSpec, int heightMeasureSpec)** measureVertical(int widthMeasureSpec, int heightMeasureSpec)** measureVertical(int widthMeasureSpec, int heightMeasureSpec)
// LinearLayout.java
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {
// mTotalLength is the height of all the children plus paddingTop and paddingBottom. Note that this is different from the height of the LinearLayout itself
mTotalLength = 0;
// The maximum width of all child elements
int maxWidth = 0;
int childState = 0;
// The maximum width of any child element whose layout_weight attribute is less than or equal to 0
int alternativeMaxWidth = 0;
// The maximum width of any child element whose layout_weight attribute has a value greater than 0
int weightedMaxWidth = 0;
boolean allFillParent = true;
// The sum of the weights of all child elements
float totalWeight = 0;
final int count = getVirtualChildCount();
// Get the width measurement mode
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
// Get the height measurement mode
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
boolean matchWidth = false;
boolean skippedMeasure = false;
final int baselineChildIndex = mBaselineAlignedChildIndex;
final boolean useLargestChild = mUseLargestChild;
int largestChildHeight = Integer.MIN_VALUE;
int consumedExcessSpace = 0;
int nonSkippedChildCount = 0;
// Execute the loop
for (int i = 0; i < count; ++i) {
// Get the child element
final View child = getVirtualChildAt(i);
if (child == null) {
mTotalLength += measureNullChild(i);
continue;
}
if (child.getVisibility() == View.GONE) {
i += getChildrenSkipCount(child, i);
continue;
}
nonSkippedChildCount++;
if (hasDividerBeforeChildAt(i)) {
mTotalLength += mDividerHeight;
}
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
totalWeight += lp.weight;
final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;
if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {
final int totalLength = mTotalLength;
mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);
skippedMeasure = true;
} else {
// Omit some code
final int usedHeight = totalWeight == 0 ? mTotalLength : 0;
MeasureChildBeforeLayout (View Child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, Int totalHeight) method
measureChildBeforeLayout(child, i, widthMeasureSpec, 0,
heightMeasureSpec, usedHeight);
// Omit some code
}
// Omit some code
}
// Omit some code
// mTotalLength plus paddingTop and paddingBottom
mTotalLength += mPaddingTop + mPaddingBottom;
int heightSize = mTotalLength;
// Take the maximum height and the recommended minimum height
heightSize = Math.max(heightSize, getSuggestedMinimumHeight());
// Omit some code
if(! allFillParent && widthMode ! = MeasureSpec.EXACTLY) { maxWidth = alternativeMaxWidth; }// maxWidth plus paddingLeft and paddingRight
maxWidth += mPaddingLeft + mPaddingRight;
// Take the maximum width and recommended minimum width
maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
// Call setMeasuredDimension(int measuredWidth, int measuredHeight)
setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
heightSizeAndState);
if(matchWidth) { forceUniformWidth(count, heightMeasureSpec); }}Copy the code
measureChildBeforeLayout(View child, int childIndex, int widthMeasureSpec, int totalWidth, int heightMeasureSpec, Int totalHeight) is used to measure the subelements of the LinearLayout, and the system uses the member variable mTotalLength to store the initial vertical height of the LinearLayout. Both make the member variable mTotalLength plus the height of the child element and the margin and padding properties of the child element in the vertical direction.
Layout process
Layout process from ViewRootImpl class * * performLayout (WindowManager. LayoutParams lp, int desiredWindowWidth, Int desiredWindowHeight)** method start, source code as shown below:
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
// Omit some code
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 {
// Call View layout(int L, int t, int r, int b)
host.layout(0.0, host.getMeasuredWidth(), host.getMeasuredHeight());
// Omit some code
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
Copy the code
Call layout(int L, int T, int r, int B) and pass in 0 for left position, 0 for top position, 0 for right position, and pass in height for bottom position, source code as follows:
// View.java
@SuppressWarnings({"unchecked"})
public void layout(int l, int t, int r, int b) {
// Omit some code
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);
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
OnLayout (Boolean changed, int left, int top, int right, int bottom
onLayout(changed, l, t, r, b);
// Omit some code
}
// Omit some code
}
Copy the code
The isLayoutModeOptical(Object O) method returns true if it is passed a ViewGroup that uses an optical boundary layout, and false otherwise.
// View.java
public static boolean isLayoutModeOptical(Object o) {
return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}
Copy the code
**setOpticalFrame(int left, int top, int right, int bottom)**
// View.java
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
Insets parentInsets = mParent instanceof View ?
((View) mParent).getOpticalInsets() : Insets.NONE;
Insets childInsets = getOpticalInsets();
Call setFrame(int left, int top, int right, int bottom)
return setFrame(
left + parentInsets.left - childInsets.left,
top + parentInsets.top - childInsets.top,
right + parentInsets.left + childInsets.right,
bottom + parentInsets.top + childInsets.bottom);
}
Copy the code
SetFrame (int left, int top, int right, int bottom); setFrame(int left, int top, int right, int bottom);
// View.java
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
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;
int drawn = mPrivateFlags & PFLAG_DRAWN;
int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
booleansizeChanged = (newWidth ! = oldWidth) || (newHeight ! = oldHeight);// Invalidate the old position
invalidate(sizeChanged);
// Assign left to the member variable mLeft
mLeft = left;
// Assign top to the member variable mTop
mTop = top;
// Assign right to the member variable mRight
mRight = right;
// Assign bottom to the member variable mBottom
mBottom = bottom;
mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
mPrivateFlags |= PFLAG_HAS_BOUNDS;
// Omit some code
}
return changed;
}
Copy the code
View getWidth() value is to use the mRight value minus the mLeft value, View getHeight() value is to use the mBottom value minus the mTop value, source code:
// View.java
@ViewDebug.ExportedProperty(category = "layout")
public final int getWidth(a) {
return mRight - mLeft;
}
@ViewDebug.ExportedProperty(category = "layout")
public final int getHeight(a) {
return mBottom - mTop;
}
Copy the code
**onLayout(Boolean changed, int left, int top, int right, int bottom)
// View.java
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}Copy the code
Developers can override this method to change the layout View logic.
example
LinearLayout (Boolean changed, int L, int T, int R, int B)**
// LinearLayout.java
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (mOrientation == VERTICAL) {
// Call layoutVertical(int left, int top, int right, int bottom) if it is vertical
layoutVertical(l, t, r, b);
} else {
// Call layoutHorizontal(int left, int top, int right, int bottom) if it is verticallayoutHorizontal(l, t, r, b); }}Copy the code
**layoutVertical(int left, int top, int right, int bottom)** layoutVertical(int left, int top, int right, int bottom)
// LinearLayout.java
void layoutVertical(int left, int top, int right, int bottom) {
final int paddingLeft = mPaddingLeft;
int childTop;
int childLeft;
// The default width of the parent element
final int width = right - left;
// The child element defaults to the value of right
int childRight = width - mPaddingRight;
// The space available for child elements
int childSpace = width - paddingLeft - mPaddingRight;
// Number of child elements
final int count = getVirtualChildCount();
final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
// Set the value of top for the first child element based on the gravity property set
switch (majorGravity) {
case Gravity.BOTTOM:
childTop = mPaddingTop + bottom - top - mTotalLength;
break;
case Gravity.CENTER_VERTICAL:
childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
break;
case Gravity.TOP:
default:
childTop = mPaddingTop;
break;
}
// Execute the loop
for (int i = 0; i < count; i++) {
// Get the child element
final View child = getVirtualChildAt(i);
if (child == null) {
childTop += measureNullChild(i);
} else if(child.getVisibility() ! = GONE) {// Get the measured width of the child element
final int childWidth = child.getMeasuredWidth();
// Get the measured height of the child element
final int childHeight = child.getMeasuredHeight();
// Get the child's LayoutParams
final LinearLayout.LayoutParams lp =
(LinearLayout.LayoutParams) child.getLayoutParams();
int gravity = lp.gravity;
if (gravity < 0) {
gravity = minorGravity;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
// Set the left value according to the gravity property of the child element
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = paddingLeft + ((childSpace - childWidth) / 2)
+ lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
childLeft = childRight - childWidth - lp.rightMargin;
break;
case Gravity.LEFT:
default:
childLeft = paddingLeft + lp.leftMargin;
break;
}
if (hasDividerBeforeChildAt(i)) {
// Add the height of the divider if there is one
childTop += mDividerHeight;
}
// The value of top plus the value of marginTop
childTop += lp.topMargin;
// Call setChildFrame(View Child, int left, int top, int width, int height) to set the layout position of the child in the parent elementsetChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); }}}Copy the code
SetChildFrame (View Child, int left, int top, int width, int height) setChildFrame(View Child, int left, int top, int width, int height)
// LinearLayout.java
private void setChildFrame(View child, int left, int top, int width, int height) {
child.layout(left, top, left + width, top + height);
}
Copy the code
This method calls the layout(int L, int T, int R, int B) methods of the child element to execute the Layout process.
The draw process
The draw process starts with the **performDraw()** method of the ViewRootImpl class.
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performDraw(a) {
// Omit some code
try {
// Call draw(Canvas Canvas)
boolean canUseAsync = draw(fullRedrawNeeded);
if(usingAsyncReport && ! canUseAsync) { mAttachInfo.mThreadedRenderer.setFrameCompleteCallback(null);
usingAsyncReport = false; }}finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
// Omit some code
}
Copy the code
**draw(Canvas Canvas)**
// frameworks/base/core/java/android/view/ViewRootImpl.java
@CallSuper
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
int saveCount;
// Step 1: Paint the background if necessary
drawBackground(canvas);
// Generally, steps 2 and 5 will be skipped if possible
final int viewFlags = mViewFlags;
booleanhorizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) ! =0;
booleanverticalEdges = (viewFlags & FADING_EDGE_VERTICAL) ! =0;
if(! verticalEdges && ! horizontalEdges) {// Step 3: Draw the View contents
onDraw(canvas);
// Step 4: Draw child elements
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
An overlay is a part of the content that is drawn below the foreground
if(mOverlay ! =null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6: Draw decorations (foreground, scrollbar)
onDrawForeground(canvas);
// Step 7: Draw default focus highlights
drawDefaultFocusHighlight(canvas);
if (debugDraw()) {
debugDrawFocus(canvas);
}
// The entire drawing process is completed
return;
}
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0 f;
float bottomFadeStrength = 0.0 f;
float leftFadeStrength = 0.0 f;
float rightFadeStrength = 0.0 f;
// Step 2: Save the canvas layers if necessary and prepare to fade (fading)
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// Clip fade length, if the top and bottom fade overlap will produce strange artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// You can also clip the horizontal gradient if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0 f;
bottomFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0 f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0 f;
rightFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0 f;
}
saveCount = canvas.getSaveCount();
int topSaveCount = -1;
int bottomSaveCount = -1;
int leftSaveCount = -1;
int rightSaveCount = -1;
int solidColor = getSolidColor();
if (solidColor == 0) {
if (drawTop) {
topSaveCount = canvas.saveUnclippedLayer(left, top, right, top + length);
}
if (drawBottom) {
bottomSaveCount = canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
}
if (drawLeft) {
leftSaveCount = canvas.saveUnclippedLayer(left, top, left + length, bottom);
}
if(drawRight) { rightSaveCount = canvas.saveUnclippedLayer(right - length, top, right, bottom); }}else {
scrollabilityCache.setFadeColor(solidColor);
}
// Step 3: Draw the View contents
onDraw(canvas);
// Step 4: Draw child elements
dispatchDraw(canvas);
// Step 5: If necessary, paint faded edges and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
// The restoration must be done in the order in which it was saved
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(rightSaveCount, p);
} else{ canvas.drawRect(right - length, top, right, bottom, p); }}if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(leftSaveCount, p);
} else{ canvas.drawRect(left, top, left + length, bottom, p); }}if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(bottomSaveCount, p);
} else{ canvas.drawRect(left, bottom - length, right, bottom, p); }}if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(topSaveCount, p);
} else {
canvas.drawRect(left, top, right, top + length, p);
}
}
canvas.restoreToCount(saveCount);
drawAutofilledHighlight(canvas);
An overlay is a part of the content that is drawn below the foreground
if(mOverlay ! =null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6: Draw decorations (foreground and scrollbar)
onDrawForeground(canvas);
if(debugDraw()) { debugDrawFocus(canvas); }}Copy the code
View **dispatchDraw(Canvas Canvas)**
// View.java
protected void dispatchDraw(Canvas canvas) {}Copy the code
ViewGroup () {ViewGroup () {ViewGroup () {ViewGroup () {ViewGroup () {ViewGroup () {ViewGroup ();
// ViewGroup.java
@Override
protected void dispatchDraw(Canvas canvas) {
boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
// Get the number of child elements
final int childrenCount = mChildrenCount;
// Get an array of child elements, which is the View array
final View[] children = mChildren;
int flags = mGroupFlags;
if((flags & FLAG_RUN_ANIMATION) ! =0 && canAnimate()) {
final booleanbuildCache = ! isHardwareAccelerated();// Execute the loop
for (int i = 0; i < childrenCount; i++) {
// Get the child element
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
// If the View is visible, animate it
finalLayoutParams params = child.getLayoutParams(); attachLayoutAnimationParameters(child, params, i, childrenCount); bindLayoutAnimation(child); }}final LayoutAnimationController controller = mLayoutAnimationController;
if (controller.willOverlap()) {
mGroupFlags |= FLAG_OPTIMIZE_INVALIDATE;
}
controller.start();
mGroupFlags &= ~FLAG_RUN_ANIMATION;
mGroupFlags &= ~FLAG_ANIMATION_DONE;
if(mAnimationListener ! =null) { mAnimationListener.onAnimationStart(controller.getAnimation()); }}// Omit some code
final ArrayList<View> preorderedList = usingRenderNodeProperties
? null : buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
for (int i = 0; i < childrenCount; i++) {
// Omit some code
if((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! =null) {
Call drawChild(Canvas Canvas, View Child, long drawingTime)more |= drawChild(canvas, child, drawingTime); }}// Omit some code
}
Copy the code
**drawChild(Canvas, View child, long drawingTime)**
// ViewGroup.java
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
Copy the code
This method calls View’s **draw(Canvas Canvas)** method.
To summarize, the DRAW process is divided into the following seven steps:
- Draw the background.
- If necessary, save the layers of the canvas and prepare to fade.
- Draw the contents of the View.
- Let me draw the child element.
- If necessary, paint the fading edges and restore layers.
- Draw decorations (foreground and scroll bar).
- Draw default focus highlighting.
example
Finally look at the next example: LinearLayout, which rewrites the **onDraw(Canvas Canvas)** method, source code as follows:
// LinearLayout.java
@Override
protected void onDraw(Canvas canvas) {
if (mDivider == null) {
// If there is no divider, end the method
return;
}
if (mOrientation == VERTICAL) {
// If it is vertical, call the drawDividersVertical(Canvas Canvas) method
drawDividersVertical(canvas);
} else {
// If it is horizontal, call the drawDividersHorizontal(Canvas Canvas) methoddrawDividersHorizontal(canvas); }}Copy the code
In this case, look at the ** dividersvertical (Canvas Canvas)** method, the source code is as follows:
// LinearLayout.java
void drawDividersVertical(Canvas canvas) {
// Get the virtual number of child elements
final int count = getVirtualChildCount();
// Execute the loop
for (int i = 0; i < count; i++) {
// Get the child element
final View child = getVirtualChildAt(i);
if(child ! =null&& child.getVisibility() ! = GONE) {if (hasDividerBeforeChildAt(i)) {
// If the child is VISIBLE or INVISIBLE and the View has a partition line, draw a horizontal partition line
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final inttop = child.getTop() - lp.topMargin - mDividerHeight; drawHorizontalDivider(canvas, top); }}}if (hasDividerBeforeChildAt(count)) {
// Get the last View that is not VISIBLE or INVISIBLE
final View child = getLastNonGoneChild();
int bottom = 0;
if (child == null) {
bottom = getHeight() - getPaddingBottom() - mDividerHeight;
} else {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
bottom = child.getBottom() + lp.bottomMargin;
}
// Draw a horizontal dividing linedrawHorizontalDivider(canvas, bottom); }}Copy the code
What this method does is draw horizontal dividing lines.
Issue with updating UI in child thread
Take a look at the first example, which looks like this:
package com.tanjiajun.viewdemo
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
/** * Created by TanJiaJun on 2020/10/8. */
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Create a child thread and start it
Thread {
// Update the UI in the child thread and set the text of the TextView with id tv_content to Tan Jiajun
findViewById<TextView>(R.id.tv_content).text = "Tan Ka Chun"
}.start()
}
}
Copy the code
This code is updating the UI in the child thread, and the results execute smoothly and as expected.
I modify the first example to get the second example, which looks like this:
package com.tanjiajun.viewdemo
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
/** * Created by TanJiaJun on 2020/10/8. */
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?). {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Create a child thread and start it
Thread {
// Let the child thread sleep for a second
Thread.sleep(1000)
// Update the UI in the child thread and set the text of the TextView with id tv_content to Tan Jiajun
findViewById<TextView>(R.id.tv_content).text = "Tan Ka Chun"
}.start()
}
}
Copy the code
This code also updates the UI on the child thread and puts the child thread to sleep for a second, raising the following exception:
The 2020-10-08 17:02:25. 544, 8619-8665 / com. Tanjiajun. Viewdemo E/AndroidRuntime: FATAL EXCEPTION: Thread - 2 Process: com.tanjiajun.viewdemo, PID: 8619 android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7753) at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1225) at android.view.View.requestLayout(View.java:23093) at android.view.View.requestLayout(View.java:23093) at android.view.View.requestLayout(View.java:23093) at android.view.View.requestLayout(View.java:23093) at android.view.View.requestLayout(View.java:23093) at android.view.View.requestLayout(View.java:23093) at androidx.constraintlayout.widget.ConstraintLayout.requestLayout(ConstraintLayout.java:3172) at android.view.View.requestLayout(View.java:23093) at android.widget.TextView.checkForRelayout(TextView.java:8908) at android.widget.TextView.setText(TextView.java:5730) at android.widget.TextView.setText(TextView.java:5571) at android.widget.TextView.setText(TextView.java:5528) at com.tanjiajun.viewdemo.MainActivity$onCreate$1.run(MainActivity.kt:20) at java.lang.Thread.run(Thread.java:764)Copy the code
Throws CalledFromWrongThreadException is unusual, in the previous explanation checkThread also mentioned () method, this method is to examine the effect of the current thread is created ViewRootImpl thread, the View will be informed if it is executed drawing process, Otherwise, throw CalledFromWrongThreadException abnormalities, sample code ViewRootImpl is created in the main thread, which is judge whether to give priority to the thread, First example didn’t sell them CalledFromWrongThreadException is the cause of abnormal, because call setText (CharSequence text) method didn’t create ViewRootImpl when, The View drawing process is executed after the Activity’s onResume method. The ViewRootImpl is created after the onResume method, so the checkThread() method has not yet been called. This time so notify the UI refresh wouldn’t throw CalledFromWrongThreadException abnormalities, the second example thrown CalledFromWrongThreadException is the cause of abnormal, because let the thread to sleep for a second, The onResume method has already been executed and ViewRootImpl has been created on the main thread. The checkThread() method will be called after setText(CharSequence text) is called to tell the UI to refresh, and the current thread is a child thread. And the main thread is not the same thread, so throw CalledFromWrongThreadException anomalies.
Google actually says this:
The Android UI toolkit is not thread-safe.
Google is saying that the Android UI Toolkit is not thread-safe, and Google is not saying that UI updates are not allowed in worker threads (not the main thread).
digression
Android open Source project (AOSP) code search tool
Android Open Source Project (AOSP) code search tool
My GitHub: TanJiaJunBeyond
Common Android Framework: Common Android framework
My nuggets: Tan Jiajun
My simple book: Tan Jiajun
My CSDN: Tan Jiajun