This article is for the collation of the past notes, in this only for the record, do not do rigorous technical sharing.
The starting point for UI drawing
The Activity start
In the Activity loading process section, the handleResumeActivity adds a DecorView to the ViewRootImpl and calls the ViewRootImpl#requestLayout method, And finally call the viewrotimpl #performTraversals method. The final traversals of View#measure/layout/draw are called at performTraversals.
//ActivityThread
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
//UI is drawn after onResume
ActivityClientRecord r = performResumeActivity(token, clearHide);
if(r ! =null) {
final Activity a = r.activity;
/ /...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l); Call the addView method
}
/ /...}}}//WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {...synchronized (mLock) {
...
root = new ViewRootImpl(view.getContext(), display);
mViews.add(view);
mRoots.add(root);
}
try {
root.setView(view, wparams, panelParentView); // Call requestLayout of ViewRootImpl}}Copy the code
//ViewRootImpl
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}void scheduleTraversals(a) {
if(! mTraversalScheduled) { mTraversalScheduled =true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//TraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run(a) { doTraversal(); }}void doTraversal(a) {
if (mTraversalScheduled) {
mTraversalScheduled = false; performTraversals(); }}private void performTraversals(a) {...if(! mStopped) {int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); / / 1
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
if (didLayout) {
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
}
if(! cancelDraw && ! newSurface) {if(! skipDraw || mReportNextDraw) { performDraw(); }}}Copy the code
The requestLayout, invalidate
In-depth analysis of requestLayout, Invalidate, and postInvalidate
requestLayout
// If the current View is requesting a layout while the View tree is in the process of layout,
// This request is deferred until the layout process is complete or until the drawing process is complete and the next layout is discovered.
public void requestLayout(a) {
if(mAttachInfo ! =null && mAttachInfo.mViewRequestingLayout == null) {
ViewRootImpl viewRoot = getViewRootImpl();
if(viewRoot ! =null && viewRoot.isInLayout()) {
if(! viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
// Set the flag bit PFLAG_FORCE_LAYOUT for the current view
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if(mParent ! =null && !mParent.isLayoutRequested()) {
// Request a layout from the parent containermParent.requestLayout(); }}Copy the code
//ViewRootImpl
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}Copy the code
ScheduleTraversals () will call ViewRootImpl#performTraversals(), which calls View#measure/layout/draw.
The first step is to determine the flag bit. If the flag bit of the current View is PFLAG_FORCE_LAYOUT, the measurement process will be carried out and onMeasure will be called to measure the View. Then finally set the flag bit to PFLAG_LAYOUT_REQUIRED. This flag bit is used in the Layout flow of the View, and if the View has this flag bit set, the layout flow will take place.
//View
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {...if((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec ! = mOldWidthMeasureSpec || heightMeasureSpec ! = mOldHeightMeasureSpec) { ...if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag backonMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; }... mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; }}public void layout(int l, int t, int r, int b) {...// Determine if the flag bit is PFLAG_LAYOUT_REQUIRED, and if so, layout the View
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
onLayout(changed, l, t, r, b);
Once the onLayout method is complete, clear the PFLAG_LAYOUT_REQUIRED flag bit
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if(li ! =null&& li.mOnLayoutChangeListeners ! =null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); }}}// Finally clear the PFLAG_FORCE_LAYOUT flag bit
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
Copy the code
invalidate
This method does not measure or lay out the process and causes the View tree to be redrawn (only the views that need to be redrawn). It is often used for internal calls (such as setVisiblity()) or when the interface needs to be refreshed.
public void invalidate(a) {
invalidate(true);
}
void invalidate(boolean invalidateCache) {
invalidateInternal(0.0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
if(mGhostView ! =null) {
mGhostView.invalidate(true);
return;
}
// Check whether the child View is visible or animated
if (skipInvalidate()) {
return;
}
// Determine whether the child View needs to be redrawn based on the marker bits of the View. If there is no change in the View, then no need to redraw
if((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) || (mPrivateFlags & PFLAG_INVALIDATED) ! = PFLAG_INVALIDATED || (fullInvalidate && isOpaque() ! = mLastIsOpaque)) {// Set the PFLAG_DIRTY bit
mPrivateFlags |= PFLAG_DIRTY;
if (invalidateCache) {
mPrivateFlags |= PFLAG_INVALIDATED;
mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
}
// Pass the area to be redrawn to the parent container
final AttachInfo ai = mAttachInfo;
final ViewParent p = mParent;
if(p ! =null&& ai ! =null && l < r && t < b) {
final Rect damage = ai.mTmpInvalRect;
damage.set(l, t, r, b);
// Call the parent container's methods, passing events up
p.invalidateChild(this, damage); }... }}// Get the union of the areas that the parent container and child View need to redraw (dirty)
ViewGroup#invalidateChild -->
ViewGroup#invalidateChildInParent -->
ViewRootImpl#invalidateChildInParent -->
// Since no measure and layout markers are added to trigger the View workflow, the measure and layout processes will not be executed. Instead, they will start from the Draw process and only draw the View that needs to be drawn
ViewRootImpl#scheduleTraversals
Copy the code
postInvalidate
PostInvalidate is called from a non-UI thread, while invalidate is called from a UI thread.
//View
public void postInvalidate(a) {
postInvalidateDelayed(0);
}
public void postInvalidateDelayed(long delayMilliseconds) {
final AttachInfo attachInfo = mAttachInfo;
if(attachInfo ! =null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds); }}//ViewRootImpl
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}
final ViewRootHandler mHandler = new ViewRootHandler();
final class ViewRootHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_INVALIDATE:
((View) msg.obj).invalidate();
break; . }}}Copy the code
Measurement process
MeasureSpec
Material: HenCoder
A compressed data type, save mode and size View according to the mode, size, determine its own size (there are default rules, but can also customize rules to set the size)
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
Copy the code
specSize
The size of the View
- Exact number
- LayoutParams.MATCH_PARENT(-1)
- LayoutParams.WRAP_CONTENT(-2)
specMode
Parent View to child View size limit “suggested”
- UNSPECIFIED: The parent control has no restrictions on its child view, which can be set to any size
- EXACTLY: The parent control already specifies the exact size of the child View
- AT_MOST: There is no limit on the size of the child View, but there is an upper limit, which is usually the size of the parent View
synthetic
The parent View sets the Spec for the child View
Given that the parent container is 300DP by 300DP, here are some scenarios for the child View
<! - scenario 1 - >// The size of the subview is 300dp*300dp. android:layout_width="match_parent" android:layout_height="match_parent"<! 2 - scene -- -- >// The size of the child View is 100dp*100dp. android:layout_width="100dp" android:layout_height="100dp"<! 3 - scene -- -- >// We hope that the size of the child View can be determined according to their own requirements, but it is best not to exceed 300dp*300dp. android:layout_width="wrap_content" android:layout_height="wrap_content"Copy the code
How does the parent container tell the child View that it needs to set a Spec for its child View?
Note: The View’s Android :layout_xxx attribute (not limited to width and height attributes) is used by the parent View
- Parent View’s own spec. mode + remaining space
- Width and height in the layoutParams object of the child View
- Both combine the default MeasureSpec in the ViewGroup to create rules that synthesize specs for child views
<! --#ViewGroup-->protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
final LayoutParams lp = child.getLayoutParams();
// The parent container's MeasureSpec and the child view's params are synthesized for the child view's MeasureSpec
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom, lp.height);
// Synthesize new MeasureSpec measure methods for child views
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
Copy the code
Create the MeasureSpec rule:
// Parameters: 1: MeasureSpec of the parent container. 2. Remaining space of the parent container. 3. Width and height type of subview.
// The three combine to create a new MeasureSpec for the child View, which is done in the parent container.<! --#ViewGroup-->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) {
// The parent container determines its size
case MeasureSpec.EXACTLY:
// The width and height of the subview is set to an exact value, such as 200dp
if (childDimension >= 0) {
// Specify the size of the child View. Size is the size set in the child View.
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// The child View wants to be big enough. Size is the remaining size of the parent container
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// The child View is not sure of its size, size is the parent container size.
// Note: AT_MOST indicates that your maximum size should not exceed the resultSize, which does not mean that the size of the child View is the resultSize.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// The parent container does not know its own size
case MeasureSpec.AT_MOST:
if (childDimension >= 0) {
// As long as the child View is an exact value and the parent is any Mode, the child can determine its size. Size is a self-set value.
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// The parent is uncertain, and the child fills it, although the child is given the residual size of the parent.
// AT_MOST indicates that the maximum value cannot be exceeded, rather than setting the child View size to size.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Both father and son are not sure, the maximum can not exceed size.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// The parent layout does not impose any restrictions on the child View
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// If the child view has its own size, use its own size
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Because the parent layout does not limit the child View, when the child View is MATCH_PARENT, the size is 0
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Since the parent layout does not limit the child View, its size is 0 when the child View is WRAP_CONTENT
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
// Synthesize new specs
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
Copy the code
use
The child View accepts the Spec and sets its size
- The Spec received is the View’s own information, not the parent View’s information
- View is filled with parent controls if set to wrap_width/match_width
// The Spec information passed from the parent container
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;
// The View's onMeasure does not handle the suggestion of "cannot exceed size", but sets the size to the maximum.
// (AT_MOST does not have a size value for the subview)
// Therefore, when the custom View is set to match/wrap, the maximum width and height are displayed
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
Copy the code
Summary of MeasureSpec:
Is a parameter that the parent control provides to the child View as a reference for resizing itself. But it’s just a reference, it’s up to the View to decide how big it is.
- MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec
- The MeasureSpec received by the child View in its measure isInformation about yourselfThe parent View has nothing to do with it
- The child View gets its own information about how to set its size according to MeasureSpec
UNSPECIFIED
The parent has not imposed any constraint on the child. It can be whatever size it wants. The parent class imposes no constraints on its subclasses, and it can be any size you want (different viewgroups or views have different processing rules)
When the parent View is set:
//RecyclerView
public static int getChildMeasureSpec(int parentSize, int parentMode, int padding,
int childDimension, boolean canScroll) {
if (canScroll) {
if (childDimension >= 0) {}else if (childDimension == LayoutParams.MATCH_PARENT) {
// Slideable, with the child View as Wrap: Forcibly set the mode of the parent View to UNSPECIFIED
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; }}else{}return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
Copy the code
- UNSPECIFIED is mandatory regardless of the mode of the parent View
- If the child View is wrap, mode should be AT_MOST.
- UNSPECIFIED is set to UNSPECIFIED because the parent View can be slid, regardless of the size of its child views
Context: Child View can slide in parent View (ScrollView, LinearLayout, RecyclerView…)
How to handle sub-view reception:
Since it’s not restricted, it depends on the default behavior the View wants. You can set a default value for size, you can set it to 0, you can show it as large as it is
ViewRootImpl#performMeasure
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);
}
Copy the code
ViewGroup
ViewGroup measurement is different from View measurement. A View only considers its own size calculation, and only needs to measure itself. There are no fixed rules for viewgroups to measure themselves, and the container defines how to size itself and its child views. Therefore, the onMeasure method does not exist in a ViewGroup. We need to actively replicate the onMeasure method when we customize a ViewGroup.
ViewGroup different measurement strategies:
- Unfixed width and height: measure the child View first, and then determine the size of the parent View
So a LinearLayout, for example, has its own height equal to the sum of the heights of its subviews
- Fixed width and height: calculate the parent View first, and then determine the child View size
For example, the label class container is fixed in width and height, and the size of the child View is determined by the number of child views
Measure yourself
The ViewGroup does not provide a way to measure itself, but the system does provide a way to measure encapsulated child views
<! View-->// Traverses the sub-view to measure (the above different measurement strategy, such as 1 can be directly called)
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); }}}// Measure the subview separately (the above different measure strategy, such as 2 can be directly called, each time passing in the remaining size information Spec in the ViewGroup)
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);
}
// Separate measurement of subviews (above different measurement strategy, such as 2 can be directly called, each pass in the ViewGroup remaining available size)
// Calculate the margin information of the child View
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);
}
/ / create a MeasureSpec
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// Synthesize a MeasureSpec rule for child Views, as mentioned above when SHARING MeasureSpec} <! Measure yourself -->/** * 1. ViewGroup size * 2. Fixed width and height: Use the information passed in onMeasure */
Copy the code
Save your own size
- Fix dimensions: resolveSize, resolveSizeAndState
- Save dimensions: setMeasuredDimension, setMeasuredDimensionRaw
After calculating our own size, we get an exact value. It should be possible to save directly on setMeasuredDimension. But then you have to take into account the limitations that the parent view of the ViewGroup puts on itself, and maybe the maximum size that it gives doesn’t meet the size that we calculated? At this point, we need resolveSize to correct our results.
<! -- Correct the size -->public static int resolveSize(int size, int measureSpec) {
return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
}
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
final int specMode = MeasureSpec.getMode(measureSpec);
final int specSize = MeasureSpec.getSize(measureSpec);
final int result;
switch (specMode) {
case MeasureSpec.AT_MOST:
if (specSize < size) {
result = specSize | MEASURED_STATE_TOO_SMALL;
} else {
result = size;
}
break;
case MeasureSpec.EXACTLY:
result = specSize;
break;
case MeasureSpec.UNSPECIFIED:
default:
result = size;
}
returnresult | (childMeasuredState & MEASURED_STATE_MASK); } <! -- Save your own size, same as View-->protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
// Use visual boundary layout
boolean optical = isLayoutModeOptical(this);
if(optical ! = isLayoutModeOptical(mParent)) { ... } setMeasuredDimensionRaw(measuredWidth, measuredHeight); }private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
}
Copy the code
View
Measure yourself
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
} else {
long value = mMeasureCache.valueAt(cacheIndex);
setMeasuredDimensionRaw((int) (value >> 32), (int) value); }}protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
// Calculate the size
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
Save your own size
// Save the size
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
// Use visual boundary layout
boolean optical = isLayoutModeOptical(this);
if(optical ! = isLayoutModeOptical(mParent)) { ... } setMeasuredDimensionRaw(measuredWidth, measuredHeight); }private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
Copy the code
The layout process
HenCoder UI 2-3 custom Layout for the internal Layout
ViewRootImpl#performLayout
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0.0, host.getMeasuredWidth(), host.getMeasuredHeight());
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
if(validLayoutRequesters ! =null) {
host.layout(0.0, host.getMeasuredWidth(), host.getMeasuredHeight()); }}}finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
Copy the code
ViewGroup
The coordinates given by the parent View are already saved in its layout. But unlike a View, as a ViewGroup, you need to set parameters in your child view.layout. So, in the ViewGroup, duplicate the onLayout method and set the coordinates for the child View.
The default abstract method onLayout in ViewGroup is to duplicate onLayout. At this point, we already know the size of our own View and its child View. The placement of child views is determined by the ViewGroup functionality
<! For example, this implementation is described in the following custom View.@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
// When onMeasure calculates the size of the ViewGroup, the coordinates of the child View are recorded and used directly in onLayoutRect rect = childRects.get(i); getChildAt(i).layout(rect.left, rect.top, rect.right, rect.bottom); }}Copy the code
View
The coordinates given by the parent View are saved in layout. OnLayout can do nothing.
Drawing process
ViewRootImpl#performDraw
private void performDraw(a) {
/ /...
final boolean fullRedrawNeeded = mFullRedrawNeeded;
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
/ /...
}
private boolean draw(boolean fullRedrawNeeded) {
if(! dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {// If hardware acceleration is enabled, this method will be executed and the view's draw method will be executed internally
if(mAttachInfo.mThreadedRenderer ! =null &&
mAttachInfo.mThreadedRenderer.isEnabled()) {
// Draw with hardware renderer.
mIsAnimating = false;
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
// If hardware acceleration is not enabled, execute this method and call view's draw method directly
if(! drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) {return false; }}}if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
return useAsyncReport;
}
Copy the code
View#draw
View draw order -HenCoder
Since the ViewGroup does not override the draw method, all views call View#draw
//View.java
public void draw(Canvas canvas) {
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;/* * 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 lay * 6. Draw decorations * * 1. DrawBackgound (canvas) * 2. Save Canvas [Save the Canvas layer (canvas.savelayer) to prepare for fade-in or fade-out if needed] * 3. Canvas body [onDraw(canvas)] * 4. Restore canvas [if necessary, draw in and out of the relevant content and restore the previously saved canvas layer] * 6. Foreground [ornament, scroll bar, onDrawForeground] */
// Step 1, draw the background, if needed
if(! dirtyOpaque) { drawBackground(canvas); }// skip step 2 & 5 if possible (common case)
if(! verticalEdges && ! horizontalEdges) {// Step 3, draw the content
if(! dirtyOpaque) onDraw(canvas);// Step 4, draw the children
dispatchDraw(canvas);
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
// Highlight the default focus onto the canvas
drawDefaultFocusHighlight(canvas);
return;
}
// Step 2, save the canvas' layers
saveCount = canvas.getSaveCount();
canvas.save...
// 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.draw...
canvas.restoreToCount(saveCount);
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
Copy the code
A drawing of a View (ViewGroup) may not contain all of these items, but it must not escape them, and it must obey this order:
Drawing content written in different methods or before and after super. XXX will have different effects, as shown in the following table (you can compare the process above) :
ViewGroup#dispatchDraw
Call relationship
- View.draw(1) –> view.ondraw (x): only if draw(1) is called can onDraw be called
- ViewGroup.dispatchDraw –> View.draw(x,x,x) –> View.draw(x)
- View.draw(x) is called directly only in ViewRootImpl
- Draw (x,x,x), then draw(x)
//ViewGroup.java
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); }}...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);
}
Copy the code
//View.java
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {...if (drawingWithRenderNode) {
// Enable hardware acceleration
renderNode = updateDisplayListIfDirty();
if(! renderNode.hasDisplayList()) { renderNode =null;
drawingWithRenderNode = false; }}if(! drawingWithDrawingCache) {if (drawingWithRenderNode) {// Hardware acceleration
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
((RecordingCanvas) canvas).drawRenderNode(renderNode);
} else {
// Skip self drawing
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchDraw(canvas);
} else{ draw(canvas); }}}...return more;
}
// Hardware acceleration
public RenderNode updateDisplayListIfDirty(a) {
try {
if (layerType == LAYER_TYPE_SOFTWARE) {
buildDrawingCache(true);
Bitmap cache = getDrawingCache(true);
if(cache ! =null) {
canvas.drawBitmap(cache, 0.0, mLayerPaint); }}else {
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
// Skip self drawing
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
dispatchDraw(canvas);
} else{ draw(canvas); }}}}else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}
public void draw(Canvas canvas) {
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
// Step 2, save the canvas' layers
// 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
// Step 6, draw decorations (foreground, scrollbars)
}
Copy the code
Custom viewgroup.ondraw () not called?
Why is ViewGroup onDraw not called
ViewGroup draw and onDraw call timing
OnDraw is not called, so draw(x) is not called. As you can see from the figure above and the “call timing” logic, the conditions for drawing itself are the same whether hardware acceleration is enabled or not: PFLAG_SKIP_DRAW (skip drawing itself if this flag is present)
There are two things that affect whether PFLAG_SKIP_DRAW is flagged:
WILL_NOT_DRAW
- The ViewGroup defaults to WILL_NOT_DRAW when initialized, and sets the PFLAG_SKIP_DRAW flag with setFlags
- You can call setWillNotDraw(false) to clear the WILL_NOT_DRAW flag
private void initViewGroup(a) {
// ViewGroup doesn't draw by default
if (!debugDraw()) {
setFlags(WILL_NOT_DRAW, DRAW_MASK);
}
}
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
Copy the code
PFLAG_SKIP_DRAW
- If WILL_NOT_DRAW is set, continue to check mBackgroud, mDefaultFocusHighlight, and mForegroundInfo. If any of them has a value, clear the PFLAG_SKIP_DRAW flag, otherwise add it
- If not, the PFLAG_SKIP_DRAW flag is cleared directly
Vew. Java setFlags methodif((mViewFlags & WILL_NOT_DRAW) ! =0) {
if(mBackground ! =null|| mDefaultFocusHighlight ! =null|| (mForegroundInfo ! =null&& mForegroundInfo.mDrawable ! =null)) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;// Clear the flag
} else {
mPrivateFlags |= PFLAG_SKIP_DRAW;// Add tags}}else {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;// Clear the flag
}
requestLayout();
invalidate(true);
Copy the code
Setting the Drawable for background, foreground, and focus will clear the PFLAG_SKIP_DRAW flag directly:
view.java
public void setBackgroundDrawable(Drawable background) {
if(background ! =null) {
if((mPrivateFlags & PFLAG_SKIP_DRAW) ! =0) {
mPrivateFlags &= ~PFLAG_SKIP_DRAW;
requestLayout = true; }}}public void setForeground(Drawable foreground) {
if(foreground ! =null) {
if((mPrivateFlags & PFLAG_SKIP_DRAW) ! =0) { mPrivateFlags &= ~PFLAG_SKIP_DRAW; }}}private void setDefaultFocusHighlight(Drawable highlight) {
mDefaultFocusHighlight = highlight;
mDefaultFocusHighlightSizeChanged = true;
if(highlight ! =null) {
if((mPrivateFlags & PFLAG_SKIP_DRAW) ! =0) { mPrivateFlags &= ~PFLAG_SKIP_DRAW; }}}Copy the code
why
So far, viewgroup. onDraw is not executed because:
- The ViewGroup initializes the default WILL_NOT_DRAW, which in turn sets the PFLAG_SKIP_DRAW flag
- There is no setting background, foreground, or getting focus highlighting, any of these three backgrounds
Customize the View flow
Common Steps (ViewGroup)
There are no template steps for a custom View, but it’s basically the same idea: measurement > layout > drawing + animation + event handling
- onMeasure
- Measure the child View, calculate their own size; Or measure the subview to its own size
- Save the location of the child View
Need to rely on the sub-view to determine the size of the container itself, convenient to save the sub-view location information in the process of calculation. Of course, you can calculate position information in other steps as well.
- Correct and save your own size
- onLayout
- So if you walk through the child View, you pass in layout location information. Note: this is layout, not onLayout of the child View
- OnDraw, animation, touch events, etc., depending on the situation
Take customizing a layout container as an example:
- The container calculates its own size based on the size of each child View
- Fix and save your size
- The View layout son
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Measure the subview
measureChildren(widthMeasureSpec, heightMeasureSpec);
// Own width
int wSize = MeasureSpec.getSize(widthMeasureSpec);
int linesCount = 0;
int useWidth = getPaddingLeft();
int useHeight = getPaddingTop();
for (int i = 0; i < getChildCount(); i++) {
View child = getChildAt(i);
// Get the measured subview information
int cw = child.getMeasuredWidth();
int ch = child.getMeasuredHeight();
// Calculate your height
if((useWidth + cw + tableSpacingHorizontal) > wSize){
linesCount ++;
useWidth = getPaddingLeft();
useHeight += (ch + tableSpacingVertical);
}
useWidth += (cw + tableSpacingHorizontal);
// Save the child location information
Rect rect = new Rect();
childRects.add(rect);
rect.left = useWidth;
rect.top = useHeight;
rect.right = useWidth;
rect.bottom = useHeight + ch;
}
//setMeasuredDimension(wSize, useHeight);
// Modify and save the size
setMeasuredDimension(resolveSize(wSize, widthMeasureSpec),
resolveSize(useHeight, heightMeasureSpec));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
for (int i = 0; i < getChildCount(); i++) {
// The location of the child View recorded in onMeasureRect rect = childRects.get(i); getChildAt(i).layout(rect.left, rect.top, rect.right, rect.bottom); }}Copy the code
Custom attributes
Android XML namespace, custom attributes
Namespaces in Android
In Android, a namespace can be divided into three kinds: 1. The XMLNS: Android = “schemas.android.com/apk/res/and… 2. XMLNS: tools = schemas.android.com/tools “3.” XMLNS: app = “schemas.android.com/apk/res-aut…
(Red box 1 is the space name, but arbitrary string; Red box 2 is where the URI locates the resource.
Deposited in the namespace is a collection of specific attributes, when our custom attributes, can be directly write the namespace root node in the XML layout: * * XMLNS: app = “schemas.android.com/apk/res-aut…
However, when namespace conflicts occur:
- rename
- Use * * XMLNS: app = “schemas.android.com/apk/res/ package name”…
Other callback methods
View life cycle
The key lifecycle of a View is: Construct View –> onFinishInflate –> onAttachedToWindow –> onMeasure –> onSizeChanged –> onLayout –> onDraw –> onDetackedFromWindow
onFinishInflate
When a View tag in the XML is mapped to a View object, the onFinishInflate method of that View is called
- This method is only called when the View is loaded in the layout file, not when the new View instance is directly loaded
- In Activity in the process of initialization: load the XML is invoked in the setContent, by ActivityThread. Namely performLaunchActivity triggered
- So the timing of the onFinishInflate method is in onCreate –> onFinishInflate –> onResume
- At this time, the View has not been measured, layout, drawing, so can not get the width, height, coordinates and other information
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate){
while(((type = parser.next()) ! = XmlPullParser.END_TAG || parser.getDepth() > depth) && type ! = XmlPullParser.END_DOCUMENT) {final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
} else if (TAG_TAG.equals(name)) {
} else if (TAG_INCLUDE.equals(name)) {
} else if (TAG_MERGE.equals(name)) {
} else {
// Generate child View instance objects.
final View view = createViewFromTag(parent, name, context, attrs);
// Subview width LayoutParams
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//1: Recursively parses the View underneath the child View, noting that the fourth argument is true (finishInflate=true below)
rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); }}if (finishInflate) {
//2: notifies the View that loading is completeparent.onFinishInflate(); }}Copy the code
- Note 1: In the recursive +while loop, iterate through the child View until you reach the deepest View
- Note 2: In recursion, the View is called whether there are children or not; Trigger the onFinishInflate from the inside to the outside
onSizeChanged
Triggered when initialization or size changes
- setFrame
- setLeft/top/right/bottom
onFocusChanged
Triggered when the View gains or loses focus
attached/detached Window
onAttachedToWindow
Triggered when a view is attached to a window
onDetachedFromWindow
Triggered when the view leaves the attached window
State saving and recovery
How to save and restore the state of a custom View?
View onSaveInstanceState in depth
When the Activity after the save/InstoreInstanceState/method calls, will be the View of it to save a Tree, and further to each child View calls the save/InstoreInstanceState () method to save state.
onSaveInstanceState
The relationship between Activity save and View:
//Activity
protected void onSaveInstanceState(@NonNull Bundle outState) {
//mWindow saves View Hierarchy
outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
/ /...
dispatchActivitySaveInstanceState(outState);
}
//PhoneWindow
public Bundle saveHierarchyState(a) {
Bundle outState = new Bundle();
SparseArray<Parcelable> states = new SparseArray<Parcelable>();
// Trigger a child View with an ID to save its state
mContentParent.saveHierarchyState(states);
//states stores the child View with id in the previous line
outState.putSparseParcelableArray(VIEWS_TAG, states);
/ /...
return outState;
}
Copy the code
MContentParent. SaveHierarchyState (states) have id of the View and yourself: saving state, record id (recover)
//ViewGroup
@Override
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
// Save your state
super.dispatchSaveInstanceState(container);
for (int i = 0; i < count; i++) {
View c = children[i];
if((c.mViewFlags & PARENT_SAVE_DISABLED_MASK) ! = PARENT_SAVE_DISABLED) {// Recursive loop to save child View statec.dispatchSaveInstanceState(container); }}}//View
protected void dispatchSaveInstanceState(SparseArray<Parcelable> container) {
if(mID ! = NO_ID && (mViewFlags & SAVE_DISABLED_MASK) ==0) {
mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED;
/ / 2
Parcelable state = onSaveInstanceState();
if(state ! =null) {
//View will be saved only if it has an IDcontainer.put(mID, state); }}}Copy the code
Note 2: The View’s onSaveInstanceState is called
Custom View handles onSaveInstanceState:
//View
@Override
protected Parcelable onSaveInstanceState(a) {
// Get some parameters saved by default
Parcelable parcelable = super.onSaveInstanceState();
SavedState ss = new SavedState(parcelable);
// Save the current location to SavedState
ss.currentPosition = mCurrentPosition;
return ss;
}
// Inherit the View inner class BaseSavedState
static class SavedState extends BaseSavedState{
// The current ViewPager position
int currentPosition;
public SavedState(Parcel source) {
super(source);
currentPosition = source.readInt();
}
public SavedState(Parcelable superState) {
super(superState);
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(currentPosition);
}
public static final Parcelable.Creator<SavedState> CREATOR = new
Parcelable.Creator<SavedState>(){
@Override
public SavedState createFromParcel(Parcel source) {
return new SavedState(source);
}
@Override
public SavedState[] newArray(int size) {
return newSavedState[size]; }}; }Copy the code
onRestoreInstanceState
** The relationship between the Activity’s restore and View: **
//Activity
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
if(mWindow ! =null) {
Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
if(windowState ! =null) {
//mWindow restores View HierarchymWindow.restoreHierarchyState(windowState); }}}//PhoneWindow
public void restoreHierarchyState(Bundle savedInstanceState) {
SparseArray<Parcelable> savedStates =
savedInstanceState.getSparseParcelableArray(VIEWS_TAG);
if(savedStates ! =null) {
mContentParent.restoreHierarchyState(savedStates);
}
// restore the focused view
int focusedViewId = savedInstanceState.getInt(FOCUSED_ID_TAG, View.NO_ID);
if(focusedViewId ! = View.NO_ID) { View needsFocus = mContentParent.findViewById(focusedViewId);if(needsFocus ! =null) { needsFocus.requestFocus(); }}/ /...
}
//View
protected void dispatchRestoreInstanceState(SparseArray<Parcelable> container) {
if(mID ! = NO_ID) {// Restore status by ID
Parcelable state = container.get(mID);
if(state ! =null) { mPrivateFlags &= ~PFLAG_SAVE_STATE_CALLED; onRestoreInstanceState(state); }}}Copy the code
** Custom View handling onRestoreInstanceState: **
@Override
protected void onRestoreInstanceState(Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
// Call another method to reassign the saved data to the current custom View
mViewPager.setCurrentItem(ss.currentPosition);
}
Copy the code
Auxiliary class
Paint
Android Custom View Advanced – Paint
Canvas
Android custom View advanced -Canvas drawing graphics
Android custom View advanced -Canvas Canvas operation
Android custom View advanced -Canvas picture text
save
The save() method saves the current Canvas state to a private stack, and the restore() method is called to restore the Canvas to its previous state. The save() method can be called multiple times, each time the stack depth increases by 1, and the current stack depth can be obtained using the getSaveCount() method. Restore () and restoreToCount() can both return to the state of the Canvas before save(), and the difference is obvious. Restore () can only pop the save() stack one at a time. RestoreToCount (), however, can pop saveCount stacks at once, although saveCount is limited to no less than 1 and no greater than the depth of the saved stack.
The save and restore methods usually appear in pairs, or separately, but only if save is invoked more times than restore. If restore is invoked more times than save, an IllegalStateException will be raised
public MyCanvasView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
paint.setStrokeWidth(5);
paint.setStyle(Paint.Style.STROKE);
rect = new Rect(10.10.400.400);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(TAG, "onDraw: " + canvas.getSaveCount());/ / 1
canvas.drawRect(rect, paint);
int count = canvas.save();
Log.d(TAG, "onDraw count: " + count);/ / 1
canvas.translate(100.100);
paint.setColor(Color.BLUE);
canvas.drawRect(rect, paint);
Log.d(TAG, "onDraw: " + canvas.getSaveCount());/ / 2
canvas.restore();
canvas.translate(150.150);
paint.setColor(Color.GREEN);
canvas.drawRect(rect, paint);
Log.d(TAG, "onDraw: " + canvas.getSaveCount());/ / 1
}
Copy the code
Here are the differences between using save and restore methods and not using them:
saveLayer
In general, Canvas can be regarded as a Canvas on which all drawing operations take place. However, if you need to achieve some relatively complex drawing operations, such as multi-layer animation, map (map can have multiple map layers superimposed, such as: political district layer, road layer, interest point layer). Canvas provides Layer support, which can be regarded as only one Layer by default. If you need to draw layers, the Android Canvas can use saveLayer, Restore to create intermediate layers that are managed according to the stack structure:
SaveLayer () is similar to save(), but it generates an offscreen bitmap on which all subsequent operations are performed. Savelayer () this is a very performance-intensive method that results in twice as many resources being used to render the same content.
public MyCanvasView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(Color.RED);
rect = new RectF(0.0.400.400);
bm = BitmapFactory.decodeResource(context.getResources(), R.drawable.image);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d(TAG, "onDraw: " + canvas.getSaveCount());/ / 1
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
saveCount = canvas.saveLayer(rect, paint);
} else {
saveCount = canvas.saveLayer(rect, paint, Canvas.ALL_SAVE_FLAG);
}
Log.d(TAG, "onDraw: " + canvas.getSaveCount() + "" + saveCount);
canvas.drawColor(Color.RED);
canvas.drawBitmap(bm, 200.200, paint);
canvas.restore();
canvas.drawBitmap(bm, 400.400, paint);
Log.d(TAG, "onDraw: " + canvas.getSaveCount());/ / 1
}
Copy the code
Here are the differences between using the saveLayer method and not using it:
How saveLayer differs from Save
- SaveLayer () generates a separate layer, while save() simply saves the state of the canvas, which is similar to a restore point.
- SaveLayer () is more memory intensive because it generates a new layer and needs to be used with caution.
- SaveLayer () can save specific areas.
- The mixed mode setXfermode has different effects.
Path
Android custom View advanced – basic operation of Path
Android custom View progression -Path bezier curve
Android custom View advanced – the end of the Path
Android custom View advanced -PathMeasure
Matrix
Android custom View advanced -Matrix principle
Android custom View advanced -Matrix details
Android custom View Advanced -Matrix Camera
Tips
Customize the default width and height of the View
When you customize a View, if the onMeasure method is not copied, the size displayed is the fill parent control regardless of whether the width is MATCH_PARENT or WRAP_CONTENT
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
/ /...
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break; /... } = = >@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
/** If you are not sure that the following processing covers all cases, try to include super. What do you mean you handled everything? SetMeasuredDimension will be called even if it is not included in the if/else process below, otherwise an error will be reported. The following handles all cases where setMeasuredDimension is called regardless of the condition and has a default or correct value passed in, so removing super will work as well. * /
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
// The default value should not be larger than the recommended value given
int defaultWidth = mWidth >= widthSpecSize ? widthSpecSize : mWidth;
int defaultHeight = mHeight >= heightSpecSize ? heightSpecSize : mHeight;
int shouldWidth = widthSpecSize;
int shouldHeight = heightSpecSize;
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
shouldWidth = defaultWidth;
shouldHeight = defaultHeight;
}else if(widthSpecMode == MeasureSpec.AT_MOST){
shouldWidth = defaultWidth;
shouldHeight = heightSpecSize;
}else if(heightSpecMode == MeasureSpec.AT_MOST){
shouldWidth = widthSpecSize;
shouldHeight = defaultHeight;
}
// There is a default value even if it is not in if/else (no correction here, not required)
setMeasuredDimension(shouldWidth, shouldHeight);
}
Copy the code
Get width and height information/timing
GetMeasureWidth and getWidth
-
GetMeasureWidth and getWidth are equal in most cases
-
Unless the ViewGroup does not calculate the view.layout coordinates according to the measured width and height due to other functional limitations when laying out child views. As a result, the width and height of the layout (right-left) or (bottom-top) are inconsistent with that of the measurement
public final int getMeasuredWidth(a) {
return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getWidth(a) {
return mRight - mLeft;
}
/** * Layout calls setFrame * and the coordinate parameters passed in layout are usually derived from the measurement width information. Left-right == mMeasuredWidth */
protected boolean setFrame(int left, int top, int right, int bottom) {
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
}
Copy the code
When the width and height can be obtained
-
Inside View: After onMeasure is measured
-
The View outside:
-
View.post (): can be called at any time (e.g., when logic needs code to be written in onCreate) why can the view.post method be used to get the view size
-
OnWindowFocusChanged: Triggered when the current Activity gains/loses focus
Analysis of onWindowsFocusChanged() method
-
ViewTreeObserver listening
-
View.post Obtaining principle
Call view.post () to get the width and height of the View
Why does view.post() guarantee the view’s width and height?
Why can the view. post method be used to get View dimensions
- mAttachInfo ! = null: The width and height are drawn directly
- MAttachInfo == NULL: Queues the logic in the action until it is executed
//View
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
// Execute directly
if(attachInfo ! =null) {
return attachInfo.mHandler.post(action);
}
//1 is put into the queue
getRunQueue().post(action);
return true;
}
//ViewRootImpl
private void performTraversals(a) {
final View host = mView;
if (mFirst) {
// Assign a value to mAttachInfo in View
host.dispatchAttachedToWindow(mAttachInfo, 0);
}
//2 Executes the task in the queuegetRunQueue().executeActions(attachInfo.mHandler); . performMeasure(); . performLayout(); . performDraw(); }//HandlerActionQueue
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
finalHandlerAction handlerAction = actions[i]; handler.postDelayed(handlerAction.action, handlerAction.delay); }}}Copy the code
Comment 1 is queued, comment 2 is queued, but performMeasure is not queued.
Note:
- The actions in executeActions are put in
mHandler
Performed in the - The mTraversalRunnable that triggers performTraversals is also placed in the Ruunable
mHandler
Performed in the - So,
When getRunQueue().executeActions is executed, you are in the mTraversalRunnable execution message
- Therefore, it must be after performTraversals (measurement, layout, rendering) that they have the opportunity to take out the message and execute it
- At this point, when a message is executed in executeActions, the measurement must have been completed
- So view.post can get the width and height at any point in time (just not immediately)
//ViewRootImpl
void scheduleTraversals(a) {
if(! mTraversalScheduled) { ... mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable,null); . }}//Choreographer
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
synchronized (mLock) {
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); }}}Copy the code
RequestLayout, invalidate
Android View analyzes requestLayout, Invalidate and postInvalidate in depth
requestLayout
Trigger measurement, layout, and drawing operations
- Recursively calls requestLayout of the parent window until ViewRootImpl, then peformTraversals,
- OnMeasure and onLayout will be called
- It doesn’t have to trigger OnDraw
- The onDraw may be triggered because l, T, R, or B is not the same as before during the layout process, which triggers an invalidate, or because the mDirty is not empty (for example, while running an animation).
invalidate
Trigger draw View tree, no trigger measurement, layout
postInvalidate
Called in a non-UI thread
animation
- Property changes –> Call invalidate()/requestLayout
- Dynamic processing in onDraw(mostly)
- Enable hardware acceleration or off-screen buffering to improve performance
View to initialize
- Structure for function
- OnAttachedToWindow: initialization
- OnDetachedFromWindow: Reclaim resources
View is drawn after onResume
//ActivityThread
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
/ / calls onResume
ActivityClientRecord r = performResumeActivity(token, clearHide);
if(r ! =null) {
final Activity a = r.activity;
/ /...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l); // Trigger requestLayout-> performTraversals (render View)
}
/ /...}}}Copy the code
The child thread updates the UI
Child threads really can’t update the UI?
Child threads can update the UI, and even some main UI threads cannot. Whether the UI can be updated depends entirely on whether the View is created on the same thread as the current thread, regardless of whether it is the main thread
Why not?
RequestLayout, Invalidate, and postInvalidate all call checkThread to check whether the current thread is the same thread that created the ViewRootImpl. Because most of the time window creation adding viewrotimpl is done on the main thread.
//ViewRootImpl
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); . invalidateRectOnScreen(dirty);return null;
}
final Thread mThread = Thread.currentThread();
void checkThread(a) {
// Instead of checking whether the thread is the main thread, compare the thread of action when ViewRootImpl is generated
if(mThread ! = Thread.currentThread()) {throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views."); }}Copy the code
When will it be available?
Before onResume
We haven’t created the view wrootimPL yet; When requestLayout/ Invalidate is null, the parent will not go to the checkThread
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
/ /...
ActivityClientRecord r = performResumeActivity(token, clearHide);
if(r ! =null) {
final Activity a = r.activity;
/ /...
if (r.window == null && !a.mFinished && willBeVisible) {
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l); / / create ViewRootImpl
}
/ /...}}}public void requestLayout(a) {
if(mParent ! =null&&! mParent.isLayoutRequested()) { mParent.requestLayout(); }}Copy the code
Child threads play by themselves
Check whether the current thread is the same as the operating thread.
Normally we create the Window/View/ViewRootView in the UI thread, so we can only update the UI in the main thread, but if we do it in the child thread all the way through (creating the ViewRootView in the child thread and adding the View to the Window), that’s fine.
The following code should work perfectly:
new Thread(new Runnable() {
@Override
public void run(a) {
// Create ViewRootImpl with Handler instance generated
Looper.prepare();
MyDialog(MainActivity.this).show();
// or Toast.show()
Looper.loop();
}
Copy the code