This article mainly refers to the book “Android Development Art Exploration” (ren Yugang), and the main differences with the book:
- The source code is based on Android 19, and changes resulting from Android version changes will be noted
- Add a large number of flow charts, source code to add more detailed notes to facilitate understanding and memory
- Extended some source code and process instructions not explained in books
preface
View’s three main processes: measurement process (Mesure), layout process (Layout), draw process (draw)
directory
- First read ViewRootImpl and phonewindow.decorView
- MeasureSpec.MeasureSpec
- A small summary
- MeasureSpec format
- MeasureSpec and ViewGroup.layoutParams
- MeasureSpec calculation flow and results
- DecorView’s MeasureSpec computes source code analysis
- View MeasureSpec calculation source code analysis
- 3. Measure process of View
- A small summary
- The relevant API
- Measure principle analysis from DecorView to View
- Measure principle analysis of ViewGroup
- Measure principle analysis of View
- Get the measurement result solution in the Activity lifecycle
- 4. View layout process
- A small summary
- The relevant API
- View layout from DecorView to View
- View layout principle analysis
- 5, View draw process
- A small summary
- The relevant API
- DecorView to View draw principle analysis
- View draw principle analysis
- ViewGroup draw principle analysis
First read ViewRootImpl and phonewindow.decorView
The DecorView, as the top-level View, inherits from FrameLayout, and events at the View layer are recorded in the DecorView before being passed to the View
ViewRootImpl: The highest level of the View class, which implements all three of View’s processes, is the link between Windows Manager and DecorView. It implements the protocol between View and Windows Manager (see Windows ManagerGlobal). ViewRootImpl:
/**
* The top of a view hierarchy, implementing the needed protocol between View
* and the WindowManager. This is for the most part an internal implementation
* detail of {@link WindowManagerGlobal}.
*/
public final class ViewRootImpl implements.Copy the code
In ActivityThread, when the Activity is created, the DecorView is added to the Window, the ViewRootImpl object is created, and the ViewRootImpl object is associated with the DecorView
/** * WindowManagerGlobal class */
public void addView(View, ViewGroup.LayoutParams, Display, Window) {... root =newViewRootImpl(view.getContext(), display); . root.setView(view, wparams, panelParentView); . }Copy the code
View map from ViewRootImpl. PerformTraversals (), in turn, the measurement process (mesure), layout, process (layout), drawing process (draw). The flow chart is as follows:
/** * ViewRootImpl class */
private void performTraversals(a) {
/**
* 参数说明
* mWidth, mHeight: 窗口有效尺寸
* lp: 窗口布局参数
* desiredWindowWidth: 窗口尺寸
* desiredWindowHeight: 窗口尺寸
*/.// Get the root View's MeasureSpec and execute the measurement process
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
intchildHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); .// Layout processperformLayout(lp, desiredWindowWidth, desiredWindowHeight); .// Draw the processperformDraw(); . }Copy the code
- Once the [performMeasure()] process is complete, the View height and width are measured, which in almost all cases is equal to the View’s final height and width
- The layout process [performLayout()] determines the coordinates of the View’s four vertices and the View’s actual height and width
- The drawing process [performDraw()] determines the content display of the View
MeasureSpec.MeasureSpec
A small summary
- MeasureSpec is a measure of the height and width of a View
- For a DecorView, its MeasureSpec is determined by the size of the window and its own LayoutParams
- For a normal View, its MeasureSpec is determined by the parent container’s MeasureSpec and its own LayoutParams
1. The relevant API
Obtain the calculation results:
- int getMode(int)
- int getSize(int)
2. The MeasureSpec format
MeasureSpec is a 32-bit int value, with the higher 2 bits representing SpecMode and the lower 30 bits representing SpecSize
- SpecMode: Indicates the measurement mode, which has three types of UNSPECIFIED, EXACTLY, and AT_MOST
- SpecSize: Specifies the size of the value in a measurement mode
- UNSPECIFIED, this means that the parent container has no restrictions on the View, as UNSPECIFIED, and is used internally
- MATCH_PARENT = layoutParams.match_parent = layoutparams.match_parent = layoutparams.match_parent = layoutparams.match_parent = layoutparams.match_parent
- AT_MOST: at most, meaning that the size of the View depends on the implementation, but cannot exceed this value, corresponding to layoutparams.wrap_content
/** * MeasureSpec class */
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
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;
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return(size & ~MODE_MASK) | (mode & MODE_MASK); }}public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
Copy the code
3. MeasureSpec and ViewGroup.layoutParams
When a View is measured, the system converts the View’s LayoutParams into a MeasureSpec based on the constraints imposed by the parent container, and then measures the View’s height and width based on that MeasureSpec
MeasureSpec = MeasureSpec = MeasureSpec
DecorView = MeasureSpec = MeasureSpec = MeasureSpec
DecorView = MeasureSpec ();
DecorView = MeasureSpec
/** * ViewRootImpl class */
/ * * *@paramLp WindowManager. LayoutParams, * window layout parameters@paramDesiredWindowWidth int, window size *@paramDesiredWindowHeight int window size */
private boolean measureHierarchy(...). {... childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); . }/ * * *@paramWindowSize valid windowSize *@paramRootDimension Window layout parameter size *@returnMeasureSpec */ of the root View(DecorView)
/** ** *@param windowSize The available width or height of the window
* @param rootDimension The layout params for one dimension (width or height) of the window
* @return The measure spec to use to measure the root view
*/
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
Copy the code
6. View MeasureSpec source code analysis
/** * ViewGroup class */
/ * * *@paramThe child View *@paramParentWidthMeasureSpec Width of the parent container *@paramWidthUsed Extra size *@paramParentHeightMeasureSpec Height of the parent container *@paramHeightUsed Extra size */
protected void measureChildWithMargins(View, int.int.int.int) {
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);
}
/ * * *@paramSpec parent container size *@paramPadding Size *@paramChildDimension View dimensions */
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) {
case MeasureSpec.EXACTLY:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
Copy the code
3. Measure process of View
A small summary
- The measure is ()finalModified, so neither View nor ViewGroup can rewrite this method, but measure() calls onMeasure(), so you can rewrite onMeasure() to complete the measurement of custom controls
- Custom controls that inherit directly from a View need to override onMeasure() and calculate the height and width of WRAP_CONTENT, otherwise WRAP_CONTENT and MATCH_PARENT have the same effect
- The ViewGroup itself does not override onMeasure() because different ViewGroup subclasses have different layout features. So custom controls that directly inherit from ViewGroup need to override onMeasure()
1. The relevant API
Obtain measurement results:
- int getMeasuredWidth()
- int getMeasuredHeight()
Measurement methods:
-
void setMeasuredDimension(int, int)
-
int getPaddingLeft()
-
int getPaddingTop()
-
int getPaddingRight()
-
int getPaddingBottom()
-
int getPaddingStart()
-
int getPaddingEnd()
-
setPadding(int left, int top, int right, int bottom)
-
ViewParent getParent()
2. Measure principle analysis from DecorView to View
/** * ViewRootImpl class */
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally{ Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}/** * View class */
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {... onMeasure(widthMeasureSpec, heightMeasureSpec); . }/** * ViewGroup class, see MeasureSpec */
protected void measureChildWithMargins(View, int.int.int.int) {... }Copy the code
- Measure ()finalSo neither View nor ViewGroup can override this method
3. Measure principle analysis of ViewGroup
- The ViewGroup itself does not override onMeasure() because different ViewGroup subclasses have different layout features
/** * ViewGroup class */
/ * * *@paramWidthMeasureSpec ViewGroup width *@paramHeightMeasureSpec ViewGroup Height */
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
final int size = mChildrenCount;
final View[] children = mChildren;
for (int i = 0; i < size; ++i) {
final View child = children[i];
if((child.mViewFlags & VISIBILITY_MASK) ! = GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); }}}/ * * *@paramChild child View's MeasureSpec *@paramParentWidthMeasureSpec ViewGroup width *@paramParentHeightMeasureSpec ViewGroup Height */
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);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
/** * see title [2.5. MeasureSpec] */
protected void measureChildWithMargins(View, int.int.int.int) {... }Copy the code
4. Analysis of the principle of View measure
/** * View class */
/ * * *@paramWidthMeasureSpec widthMeasureSpec *@paramMeasureSpec heightMeasureSpec */
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
/ * * *@paramSize Indicates the default size *@paramMeasureSpec Parent container constraint *@returnView MeasureSpec */
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;
}
/** * mBackground: Drawable object * mMinWidth: corresponding to android.minWidth property * getMinimumWidth(): Drawable object's original width */
protected int getSuggestedMinimumWidth(a) {
return (mBackground == null)? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }protected int getSuggestedMinimumHeight(a) {
return (mBackground == null)? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }/** * Drawable */
public int getMinimumWidth(a) {
final int intrinsicWidth = getIntrinsicWidth();
return intrinsicWidth > 0 ? intrinsicWidth : 0;
}
public int getMinimumHeight(a) {
final int intrinsicHeight = getIntrinsicHeight();
return intrinsicHeight > 0 ? intrinsicHeight : 0;
}
Copy the code
5. Get the measurement result solution in the Activity lifecycle
Since the measure process of a View is not synchronized with the Activity life cycle, there will be problems in obtaining the measurement results directly in the Activity life cycle. The solutions are as follows:
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
int width = view.getMeasuredWidth();
intheight = view.getMeasuredHeight(); }}Copy the code
Method two: Post the message to the end of the message queue, and when Looper calls the message, the View is already initialized
@Override
protected void onStart(a) {
super.onStart();
view.post(new Runnable() {
@Override
public void run(a) {
int width = view.getMeasuredWidth();
intheight = view.getMeasuredHeight(); }}); }Copy the code
Method 3:
@Override
protected void onStart(a) {
super.onStart();
ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalFocusChangeListener(new ViewTreeObserver.OnGlobalFocusChangeListener() {
@Override
public void onGlobalFocusChanged(View oldFocus, View newFocus) {
view.getViewTreeObserver().removeOnGlobalFocusChangeListener(this);
int width = view.getMeasuredWidth();
intheight = view.getMeasuredHeight(); }}); }Copy the code
Method 4: Not recommended, because the View is MATCH_PARENT is unavailable
@Override
protected void onStart(a) {
super.onStart();
// View is WRAP_CONTENT
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST); // take the maximum value
int heghtMeasureSpec = widthMeasureSpec;
view.measure(widthMeasureSpec, getMinimumHeight());
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
// When View is dp/px, assume the height and width are 100px and 200px respectively
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heghtMeasureSpec = View.MeasureSpec.makeMeasureSpec(200, View.MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec, getMinimumHeight());
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
}
Copy the code
4. View layout process
A small summary
- Similar to measure procedure reasons, custom controls that directly inherit from ViewGroup need to override onLayout()
1. The relevant API
Get layout results:
- getTop()
- getBottom()
- getLeft()
- getRight()
- getWidth()
- getHeight()
2. Analyze the layout principle from DecorView to View
/** * ViewRootImpl class */
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
mLayoutRequested = false;
mScrollMayChange = true;
mInLayout = true;
finalView host = mView; . Trace.traceBegin(Trace.TRACE_TAG_VIEW,"layout");
try {
host.layout(0.0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// If some requests are still pending, then we need to clear those flags and do
// a full request/measure/layout pass to handle this situation.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, false);
if(validLayoutRequesters ! =null) {... host.layout(0.0, host.getMeasuredWidth(), host.getMeasuredHeight()); . }}}finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
/** * ViewGroup class */
public final void layout(int l, int t, int r, int b) {
if(! mSuppressLayout && (mTransition ==null| |! mTransition.isChangingLayout())) {if(mTransition ! =null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true; }}/** * View class */
/** * View layout */
public void layout(int l, int t, int r, int b) {... }/** * FrameLayout class */
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {...for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if(child.getVisibility() ! = GONE) { ... child.layout(childLeft, childTop, childLeft + width, childTop + height); }}}Copy the code
3. View layout principle analysis
- OnLayout () is undefined for either View or ViewGroup, so custom controls that directly inherit from ViewGroup need to override onLayout().
/** * View class */
/ * * *@paramLeft/Top: relative offset from the parent container *@paramRight/Bottom: relative offset from the parent container after measure() */
public void layout(int l, int t, int r, int b) {...// Determine the size of the current View relative to the parent container
boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
/ / call onLayout (...).
if(changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); . } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; }/**
* true: 当前 View(注意此处为父容器)是 ViewGroup,且模式为 LAYOUT_MODE_OPTICAL_BOUNDS
* ViewGroup 模式有两种:
* LAYOUT_MODE_CLIP_BOUNDS:默认模式,表示边界未加工的
* LAYOUT_MODE_OPTICAL_BOUNDS:大体含义是支持特效,如阴影、暖色、冷色
*/
public static boolean isLayoutModeOptical(Object o) {
return o instanceof ViewGroup && ((ViewGroup) o).isLayoutModeOptical();
}
/** * The current ViewGroup position */ when setting ViewGroup to LAYOUT_MODE_OPTICAL_BOUNDS
private boolean setOpticalFrame(int left, int top, int right, int bottom) {...returnsetFrame(...) ; }/** * Assign a size and position to this view. */ Assign a size and position to this view
protected boolean setFrame(int left, int top, int right, int bottom) {... }protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
Copy the code
5, View draw process
A small summary
- Custom controls with display content need to override onDraw(Canvas) to draw their own display content
1. The relevant API
There is no
2. Draw from DecorView to View
- Because the source code is too complex, and designed to hardware rendering, so the above process is for reference only, the following post part of the source code, detailed process see Android source code
/** * ViewRootImpl class */
private void performDraw(a) {
if(! mAttachInfo.mScreenOn && ! mReportNextDraw) {return;
}
final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); }... }private void draw(boolean fullRedrawNeeded) {...if(! sFirstDrawComplete) {synchronized (sFirstDrawHandlers) {
sFirstDrawComplete = true;
final int count = sFirstDrawHandlers.size();
for (int i = 0; i< count; i++) {
mHandler.post(sFirstDrawHandlers.get(i));
}
}
}
scrollToRectOrFocus(null.false); .finalRect dirty = mDirty; .if(! dirty.isEmpty() || mIsAnimating) {if(attachInfo.mHardwareRenderer ! =null && attachInfo.mHardwareRenderer.isEnabled()) {
// Draw with hardware renderer.. attachInfo.mHardwareRenderer.draw(mView, attachInfo,this, animating ? null : mCurrentDirty);
} else {
// If we get here with a disabled & requested hardware renderer, something went
// wrong (an invalidate posted right before we destroyed the hardware surface
// for instance) so we should just bail out. Locking the surface with software
// rendering at this point would lock it forever and prevent hardware renderer
// from doing its job when it comes back.
// Before we request a new frame we must however attempt to reinitiliaze the
// hardware renderer if it's in requested state. This would happen after an
// eglTerminate() for instance..if(! drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {return; }}}if (animating) {
mFullRedrawNeeded = true; scheduleTraversals(); }}private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff, boolean scalingRequired, Rect dirty) {
// Draw with software renderer.Canvas canvas; .try{...try{... mView.draw(canvas); drawAccessibilityFocusedDrawableIfNeeded(canvas); }finally {
if(! attachInfo.mSetIgnoreDirtyState) {// Only clear the flag if it was not set during the mView.draw() call
attachInfo.mIgnoreDirtyState = false; }}}finally{... }return true;
}
/** * DecorView class */
private Drawable mMenuBackground;
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if(mMenuBackground ! =null) { mMenuBackground.draw(canvas); }}/** * ViewGroup class */
@Override
protected void dispatchDraw(Canvas canvas) {...for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() ! =null) {
more |= drawChild(canvas, transientChild, drawingTime);
}
transientIndex++;
if (transientIndex >= transientCount) {
transientIndex = -1; }}final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
if((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! =null) { more |= drawChild(canvas, child, drawingTime); }}... }protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
/** * View class */
/** ** see below */
public void draw(Canvas canvas) {... }Copy the code
3. Draw principle analysis of View
- Draw the background: Android 19 is implemented using the code block from the source code above. Android 21 uses drawBackground(Canvas)
/** * Draw steps: * 1. Draw the background * 2. If necessary, save the canvas layer before fade-in. (????? Draw the contents of the View. Draw the contents of the child View. If necessary, draw the fade-in boundary and restore layers. * 6. Draw additional content, such as a scroll wheel * 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) */
public void draw(Canvas canvas) {
// Adjust the boundaries of the current canvas.
if(mClipBounds ! =null) {
canvas.clipRect(mClipBounds);
}
// dirtyOpaque: true
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null| |! mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;// Step 1, draw the background, if needed
int saveCount;
if(! dirtyOpaque) {final Drawable background = mBackground;
if(background ! =null) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if (mBackgroundSizeChanged) {
background.setBounds(0.0, mRight - mLeft, mBottom - mTop);
mBackgroundSizeChanged = false;
}
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else{ canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); }}}// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
booleanhorizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) ! =0;
booleanverticalEdges = (viewFlags & FADING_EDGE_VERTICAL) ! =0;
if(! verticalEdges && ! horizontalEdges) {// Step 3, draw the content
if(! dirtyOpaque) onDraw(canvas);// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
if(mOverlay ! =null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// we're done...
return;
}
// Transparent layout processing, seems to have done some optimization??. }Copy the code
4. ViewGroup draw principle analysis
- Due to the high complexity of the source code involved in this part, the above process is only for reference. Part of the source code is posted below, and the detailed process is shown in the Android source code
- Due to the high complexity of the source code involved in this part, it basically only reflects the key methods, others are briefly explained, please check the Android source code
/** * ViewGroup */
@Override
protected void dispatchDraw(Canvas canvas) {
final int count = mChildrenCount;
final View[] children = mChildren;
int flags = mGroupFlags;
// Animation preparation: create a draw content cache for all child views and trigger the animation start event
if((flags & FLAG_RUN_ANIMATION) ! =0 && canAnimate()) {
...
for (int i = 0; i < count; i++) {
finalView child = children[i]; . child.buildDrawingCache(true); }...if(mAnimationListener ! =null) { mAnimationListener.onAnimationStart(controller.getAnimation()); }}// Adjust the padding: field protection
int saveCount = 0;
final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
if (clipToPadding) {
saveCount = canvas.save();
canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
mScrollX + mRight - mLeft - mPaddingRight,
mScrollY + mBottom - mTop - mPaddingBottom);
}
// Draw the child View content and animate it
// We will draw our child's animation, let's reset the flag
mPrivateFlags &= ~PFLAG_DRAW_ANIMATION;
mGroupFlags &= ~FLAG_INVALIDATE_REQUIRED;
boolean more = false;
final long drawingTime = getDrawingTime();
/* Draws the contents of the child views in the specified order */
if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {
for (int i = 0; i < count; i++) {
final View child = children[i];
if((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! =null) { more |= drawChild(canvas, child, drawingTime); }}}else {
for (int i = 0; i < count; i++) {
final View child = children[getChildDrawingOrder(count, i)];
if((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! =null) { more |= drawChild(canvas, child, drawingTime); }}}// Draw the contents of the hidden child View
// Draw any disappearing views that have animations
if(mDisappearingChildren ! =null) {
final ArrayList<View> disappearingChildren = mDisappearingChildren;
final int disappearingCount = disappearingChildren.size() - 1;
// Go backwards -- we may delete as animations finish
for (int i = disappearingCount; i >= 0; i--) {
finalView child = disappearingChildren.get(i); more |= drawChild(canvas, child, drawingTime); }}...// Adjust the padding: live restore
if (clipToPadding) {
canvas.restoreToCount(saveCount);
}
// Update the status flag and trigger the end of animation event
// mGroupFlags might have been updated by drawChild()
flags = mGroupFlags;
if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
invalidate(true);
}
if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0&& mLayoutAnimationController.isDone() && ! more) {// We want to erase the drawing cache and notify the listener after the
// next frame is drawn because one extra invalidate() is caused by
// drawChild() after the animation is over
mGroupFlags |= FLAG_NOTIFY_ANIMATION_LISTENER;
final Runnable end = new Runnable() {
public void run(a) { notifyAnimationListener(); }}; post(end); }}/** * Draw the View contents and perform the animation */
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
/** * View class */
/** * This method can only be called */ via viewgroup.drawChild ()
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {...// Animation execution
final Animation a = getAnimation();
if(a ! =null) { more = drawAnimation(parent, drawingTime, a, scalingRequired); . }else{... }...// Ready to display the content
DisplayList displayList = null;
Bitmap cache = null;
boolean hasDisplayList = false;
if(caching) { ... hasDisplayList = canHaveDisplayList(); . cache = getDrawingCache(true); . }... displayList = getDisplayList(); .final boolean hasNoCache = cache == null || hasDisplayList;
final boolean offsetForScroll = cache == null&&! hasDisplayList && layerType ! = LAYER_TYPE_HARDWARE;// Canvas adjustment. restoreTo = canvas.save(); . canvas.translate(mLeft - sx, mTop - sy); . canvas.clipRect(0.0, mRight - mLeft, mBottom - mTop); .// Draw the View content
if (hasNoCache) {
boolean layerRendered = false; .if(! layerRendered) {if(! hasDisplayList) {// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else{ draw(canvas); }}else {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((HardwareCanvas) canvas).drawDisplayList(displayList, null, flags); }}}else if(cache ! =null) {... canvas.drawBitmap(cache,0.0 f.0.0 f, cachePaint);
}
// Canvas adjustment: live restore
if (restoreTo >= 0) {
canvas.restoreToCount(restoreTo);
}
// Update the status indicator to end the animation effect
if(a ! =null && !more) {
if(! hardwareAccelerated && ! a.getFillAfter()) { onSetAlpha(255);
}
parent.finishAnimatingView(this, a);
}
if (more && hardwareAccelerated) {
if (a.hasAlpha() && (mPrivateFlags & PFLAG_ALPHA_SET) == PFLAG_ALPHA_SET) {
// alpha animations should cause the child to recreate its display list
invalidate(true);
}
}
mRecreateDisplayList = false;
return more;
}
Copy the code
reference
- “Android Development art Exploration” (Ren Yugang) chapter 4 the working principle of View
- Android 19 Source code (main)
- Android 21 source code
The revision of time
- The first draft of the 2017-11-28 s:
- 2017-11-29: Add a directory
- 2018-12-06: I found that the flow chart was a little awkward, so I replaced the flow chart and added some content, mainly adding the source code of FrameLayout
The statement
Limited to the author level is limited, mistakes are unavoidable, please actively clap brick! Welcome any form of reprint, reprint please keep the link of the original article: juejin.cn/post/684490…
“Said
It took me a long time to finish writing this blog. It will also be easier to review or continue to understand how the View works.
This blog post explains how the View works with flow and source code comments. The detailed description of the paper is much more brief than Ren’s “Android Development Art Exploration”, which is not easy to read. Therefore, I recommend reading Ren Dada’s “Exploring the Art of Android Development”.
In addition, CSDN unexpectedly closed my account without any reason, speechless ah. I had no choice but to move my blog to rare earth mining