- An Android screen hierarchy
- 1 the Window concept
- 11 PhoneWindow
- 1 the Window concept
- 2 the setContentView process
- 3. Process before View drawing
- 1 ViewRoot
- 2 View drawing starting point
- Four View map
- 1 Measure phase
- Measur process for rewriting views and Viewgroups
- 111 Planned width and height generation rule for onMeasure parameters
- 112 setMeasuredDimension sets its own actual measured width and height
- 12 measure process
- 121 measureChildWithMargin method
- 121 The resolveSizeAndState method selects the desired width of the child and the planned width of the father
- 123 Default implementation of ViewonMeasure
- Measur process for rewriting views and Viewgroups
- 2 Layout Stage
- 21 Rewrite the onLayout of a View or ViewGroup
- 22 layout process
- 3 draw draw stage
- 31 Override View or ViewGroup onDraw
- 32 the draw process
- 321 Canvas generation process
- 322 Viewdraw iterative process
- 1 Measure phase
- 5. View life cycle
- 1 Display Process
- He was with requestFocus invalidaterequsetLaytout
- 3 requestLayout process
- 31 PFLAG_FORCE_LAYOUT labeled
- 4 Invalidate initialization process
- 5 postInvalidate process
- reference
Let’s get started with a big trick to memorize the whole process:
To view the original image
This article is actually the illustration of the above process, focusing on the analysis of the overall process, the purpose is to grasp the whole process of View drawing, and for specific implementation details can be read on the corresponding source code in the future, the overall steps can be divided into:
- The setContentView process
- Process before View drawing: DectorView is associated with ViewRoot and dector is put into window
- View drawing process
- Measure: Determine whether the View size needs to be recalculated or calculated if necessary. The recursion measures the actual size of the entire ViewTree to make suggestions for the formal layout. (Note, it is only suggested, as for use, to see onLayout);
- Layout: Determine whether the position of the View needs to be recalculated, if necessary; Use layout() function for all the child control layout, get each view of the relative drawing position matrix (relative to the parent layout of the top,left,bottom,right), and finally constitute the global relative position drawing tree;
- Draw: Determines whether the View needs to be redrawn or redrawn if necessary.
Android screen hierarchy
- PhoneWindow: a specific implementation of a window, such as an Activity, a Dialog, a Toast, a Menu Menu, etc
- DecorView(FrameLayout): The root container of an application window
- mContentRoot (LinearLayout):Is the DecorView itself, or a child of the DecorView, by loading a default layout instance in PhoneWindow#generateLayoutContains two child elements, TitleView and ContentView
- TitleView: Container for ActionBar
- ContentView (which happens FrameLayout contentParent, android. R.i, dc ontent) : window contents of container, we use the setContentView is usually set the View
1.1 the Window concept
Window refers to the Window, this concept in the Android Framework to achieve the android.view.Window this abstract class, the abstract class is the Android system in the Window abstraction. Before introducing this class, let’s take a look at what a window really is.
In fact, a window is a macro idea, a rectangular area of the screen used to draw various UI elements and respond to user input events. It usually has the following two characteristics:
- Independent drawing, do not interact with other interfaces;
- Does not trigger input events of other interfaces;
In Android, Windows are the display area of a single Surface instance, and the Surface of each window is assigned by WindowManagerService. We can think of the Surface as a Canvas on which applications can draw using Canvas or OpenGL. Once drawn, the surfaces are mixed using SurfaceFlinger in a specific order (z-order) and output to the FrameBuffer so that the user interface can be displayed.
This abstract class contains three core components:
- The layout of the WindowManager. LayoutParams: window parameters;
- Callback: The window’s Callback interface, usually implemented by an Activity;
- ViewTree: Tree of controls hosted by a window.
public abstract class Window { //... Public static final int FEATURE_NO_TITLE = 1; public static final int FEATURE_INDETERMINATE_PROGRESS = 5; Public abstract void setContentView(int layoutResID); public abstract void setContentView(View view); Public Boolean requestFeature(int featureId) {final int flag = 1<<featureId; mFeatures |= flag; mLocalFeatures |= mContainer ! = null ? (flag&~mContainer.mFeatures) : flag; return (mFeatures&flag) ! = 0; } / /... }Copy the code
The abstract class Android.view. Window can be seen as the convention of the macro concept of Windows in Android, and the class PhoneWindow is the concrete realization of the concept of Android Windows provided by the Framework
1.1.1 PhoneWindow
The PhoneWindow class is a concrete implementation of the Android Window provided by the Framework. This class inherits from the Window class and is the concrete implementation of the Window class, that is, we can draw the Window concretely through this class.
Furthermore, the class contains a DecorView object that is the root View of all application Windows (Activity interfaces).
- When we call the setContentView() method to set up the Activity’s user interface, we are actually setting the PhoneWindow ViewTree associated with it
- We can also customize the appearance of an Activity associated with a PhoneWindow using the requestWindowFeature() method of the Activity class, What this method actually does is store the requested window appearance features into the mFeatures member of the PhoneWindow, which draws the specific appearance based on the value of mFeatures when the appearance template is generated during the window drawing phase
In short, the Main function of the PhoneWindow class is to wrap a FrameLayout class, a DecorView object, as the root View of the application window and provide a common set of interfaces for manipulating Windows.
public class PhoneWindow extends Window implements MenuBuilder.Callback { //... // This is the top-level view of the window, containing the window decor. private DecorView mDecor; // This object is the root view of all application Windows, a subclass of FrameLayout. // This object is the parent view of the Activity layout file, Private ViewGroup mContentParent; private ViewGroup mContentParent; private ViewGroup mContentParent; @override public void setTitle(CharSequence title) {if (mTitleView! = null) { mTitleView.setText(title); } mTitle = title; } @override public final void setBackgroundDrawable(Drawable Drawable) {if (Drawable Drawable! = mBackgroundDrawable || mBackgroundResource ! = 0) { mBackgroundResource = 0; mBackgroundDrawable = drawable; if (mDecor ! = null) { mDecor.setWindowBackground(drawable); }}} / /... }Copy the code
SetContentView procedure
Before analyzing the setContentView() method, we need to be clear: this method only completes the creation of the Activity’s ContentView, and does not perform the View’s drawing process
[1] When we inherit our custom Activity from Android.app.activity, the setContentView() method is called from the Activity class, the source code is as follows:
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); ...}Copy the code
[2] The getWindow() method returns the PhoneWindow associated with the Activity, that is, it actually calls the PhoneWindow setContentView() method.
@Override public void setContentView(int layoutResID) { if (mContentParent == null) { // [2.1] mContentParent is the ContentView mentioned above. If it is empty, call installDecor() to generate installDecor(). } else if (! HasFeature (FEATURE_CONTENT_TRANSITIONS)) {FEATURE_CONTENT_TRANSITIONS is feature_transitions. The remove decorView of all child View mContentParent. RemoveAllViews (); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS) { } else {// [2.2] The general case will come here, The mLayOutInflater.inflate () method is called to fill the layout, which is to add the ContentView we set to the mContentParent. Mlayoutinflater.inflate (layoutResID, mContentParent); }.. // cb is the Activity final Callback(); cb = getCallback(); if (cb ! = null && ! Cb.oncontentchanged (); isDestroyed()) {// Call the onContentChanged() callback method to notify the Activity that the content of the window has changed. }..}Copy the code
[3] The setContentView() method of PhoneWindow calls the LayoutInflater’s inflate() method to fill the layout. And into the actual layout the parent container ContentView (FrameLayout contentParent, android. R.i, dc ontent)
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
. . .
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
Copy the code
[4] In the PhoneWindow setContentView() method, we pass the ContentView as the root argument to layoutinflater.inflate (). The inflate(XmlPullParser, ViewGroup, Boolean) method is eventually called to populate the layout. The source code for this method is as follows:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { . . . final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; View result = root; try { // Look for the root node. int type; // [4.1] Read the XML file until the start tag is encountered while ((type = parser.next())! = XmlPullParser.START_TAG && type ! = xmlpullParser.end_document) {// Empty} // [4.2] = XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!" ); } final String name = parser.getName(); If (tag_merge.equals (name)) {// If (tag_merge.equals (name)) {// If (tag_merge.equals (name)) {// If (tag_merge.equals (name)) {// If (tag_merge.equals (name)) {// If (tag_merge.equals (name)) {// If (tag_merge.equals (name)) { The parent container (i.e. the root parameters) are empty and attachRoot must be true, otherwise an error if (root = = null | |! attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } // [4.3.1] recursively populates the layout rInflate(parser, root, inflaterContext, attrs, false); } else {// temp is the root View of the XML layout file. Final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root ! = null) {.... / / get the parent container layout parameters (LayoutParams) params = root. GenerateLayoutParams (attrs); if (! AttachToRoot (attachToRoot) {// If attachToRoot is false, we will only set the parent's layout parameters to the root View temp.setLayoutParams(params); }} // [4.4] rInflateChildren(Parser, temp, attrs, true); . . . if (root ! = null && attachToRoot) {// If the parent container is not empty and attachToRoot is true, wrap the parent container as the parent View of the root View. } / / root is empty or if attachToRoot is false, with the root View as a return value if (root = = null | |! attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { . . . } catch (Exception e) { . . . } finally { . . . } return result; }}Copy the code
[5] The rInflateChildren() method is called in the inflate() and rInflate() methods, and the rInflateChildren() method actually calls the rInflate() method. The source code for this method is as follows:
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}Copy the code
Call the rInflate() method to recursively populate the layout:
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, Boolean finishInflate) throws XmlPullParserException, IOException {// Get the depth of the current tag, Final int depth = parser.getDepth(); int type; while (((type = parser.next()) ! = XmlPullParser.END_TAG || parser.getDepth() > depth) && type ! = xmlpullParser.end_document) {// Proceed with the next iteration if (type! = XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); If (tag request_focus. Equals (name)) {parserfocus (parser, parent); } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); } else {final View View = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params=viewGroup.generateLayoutParams(attrs); // Recursively load child Views rInflateChildren(Parser, View, attrs, true); viewGroup.addView(view, params); } } if (finishInflate) { parent.onFinishInflate(); }}Copy the code
At this point, the setContentView is almost complete, but at this point, our View is still not visible because we just loaded the layout and didn’t do any measuring, layout, or drawing of the View.
Here is the branching logic
Here, we also study the mDecor and mContentParent instantiation process of PhoneWindow, that is, the [2.1] PhoneWindow#installDecor process. Convenient for everyone to have an intuitive understanding of mDecor and mContentParent
private void installDecor() { if (mDecor == null) { mDecor = generateDecor(); / / generated 2.1.1 】 【 mDecor mDecor. SetDescendantFocusability (ViewGroup. FOCUS_AFTER_DESCENDANTS); mDecor.setIsRootNamespace(true); if (! mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures ! = 0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); } } if (mContentParent == null) { mContentParent = generateLayout(mDecor); //【2.1.2】 Passed in mDecor and generated mContentParent... }}}Copy the code
First, the mDecor code is generated by using [2.1.1] and the PhoneWindow#generateDecor method is called to instantiate the DecorView, which is the topmost View of the entire ViewTree. It is a FrameLayout layout that represents the entire application interface:
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}Copy the code
After that, mDecor will be passed in by [2.1.2] and mContentParent will be generated
- Set the DecorView style according to the theme style, such as titlebar or not
- [2.1.2.1] Instantiate the preset layout of the system as mContentRoot and add it into mDecor
- 【2.1.2.2】 Find ID_ANDROID_CONTENT in mContentRoot and instance it as mContentParent
And from that, we go to theta
Protected ViewGroup generateLayout(DecorView decor) {// Apply data from current theme. // Obtain style information from theme files TypedArray A = getWindowStyle(); . // [2.1.2.0] Set the DecorView style according to the theme style. If (a.getBoolean(r.tyleable.window_windownotitle, false)) {requestFeature(FEATURE_NO_TITLE); } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) { // Don't allow an action bar if there is no title. requestFeature(FEATURE_ACTION_BAR); } if(...) {... } // Inflate the window decor. // Load window layout int layoutResource; int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features)); if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) ! = 0) { layoutResource = R.layout.screen_swipe_dismiss; } else if(...) {... } // [2.1.2.1] instantiate the system-default layout as mContentRoot and add it to the mDecor View in = mLayoutinflater.inflate (layoutResource, null); AddView (in, new viewgroup.layoutparams (MATCH_PARENT, MATCH_PARENT)); // Add child View to DecorView, mContentParent mContentRoot = (ViewGroup) in; //【2.1.2.2】 find ID_ANDROID_CONTENT in mContentRoot MContentParent ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); } if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) ! = 0) { ProgressBar progress = getCircularProgressBar(false); if (progress ! = null) { progress.setIndeterminate(true); } } if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) ! = 0) { registerSwipeCallbacks(); } // Remaining setup -- of background and title -- that only applies // to top-level windows. ... return contentParent; }Copy the code
3. Process before View drawing
Using the setContentView method, we create a DecorView and load our supplied layout, but at this point, our View is still not visible because we only loaded the layout and did no measurement, layout, or drawing of the View. One more step is to add the DecorView to the window before the View can proceed with the measurement process, and then proceed through a sequence of steps to trigger the Viewrotimpl #performTraversals method, within which the measurement, layout, and rendering processes begin
3.1 ViewRoot
Before we get into drawing a View, we need to know who is responsible for performing the entire process of drawing a View. In fact, View wroot is responsible for drawing the View. Each application window’s decorView has a ViewRoot object associated with it, and this association is maintained by WindowManager.
So when is the association between a decorView and ViewRoot established? . The answer is the Activity starts, ActivityThread handleResumeActivity () method to establish the relationship in both.
First, let’s take a quick look at the Activity creation process:
【 step 1】 start the Activity in ActivityThread#handleLaunchActivity, which calls the Activity#onCreate method with the SetContentView() procedure. This completes the DecorView creation action described above
When onCreate() completes, the handleLaunchActivity method continues to call the ActivityThread#handleResumeActivity method
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) { //... ActivityClientRecord r = performResumeActivity(token, clearHide); // the onResume() method is called ** if (r! = null) { final Activity a = r.activity; / /... if (r.window == null && ! a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); // Get window object View decor = R.window.getDecorView (); // Get DecorView object decor. SetVisibility (view.invisible); ViewManager wm = a.getWindowManager(); / / get the windowManager object windowManager. LayoutParams l = truly indow. GetAttributes (); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { a.mWindowAdded = true; wm.addView(decor, l); // call addView method} //... }}}Copy the code
Inside the method, get the Window object, DecorView object, and windowManager object associated with the activity, and call the addView method of windowManager. Note that before addView, performResumeActivity is executed, which calls the Activity’s onResume() lifecycle function
WindowManager is an abstract class whose implementation class is WindowManagerImpl, so the WindowManagerImpl#addView method is called
public final class WindowManagerImpl implements WindowManager { private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); . @Override public void addView(View view, ViewGroup.LayoutParams params) { mGlobal.addView(view, params, mDisplay, mParentWindow); }}Copy the code
We actually call a member function of mGlobal, which is an instance of WindowManagerGlobal, so we move on to the WindowManagerGlobal#addView method
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ... ViewRootImpl root; View panelParentView = null; synchronized (mLock) { ... root = new ViewRootImpl(view.getContext(), display); // [4.1] instantiate ViewRootImpl class View.setLayoutParams (wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); // [4.2] call ViewRootImpl#setView } Catch (RuntimeException e) {BadTokenException or InvalidDisplayException, clean up. synchronized (mLock) { final int index = findViewLocked(view, false); if (index >= 0) { removeViewLocked(index, true); } } throw e; }}Copy the code
In the [4.2] ViewRootImpl. SetView () function, the ViewRootImpl, DecorView, and WMS are associated with each other. Finally, WMS calls the ViewRootImpl#requestLayout method to start the View measurement, layout, and drawing process
3.2 Starting point of View drawing
When the decorView is associated with ViewRoot, the requestLayout() method of the ViewRoot class is called to complete the initial layout of the application’s user interface. The requestLayout() method of the ViewRootImpl class is called. The source code for this method is as follows:
@Override public void requestLayout() { if (! MHandlingLayoutInLayoutRequest) {/ / check if initiated layout request thread is given priority to the thread checkThread (); mLayoutRequested = true; scheduleTraversals(); }}Copy the code
The scheduleTraversals() method is called in the above method to schedule a once completed drawing, which sends a “traversals” message to the main thread, eventually causing the ViewRootImpl#performTraversals() method to be called.
We analyze the entire rendering process of a View using View script #performTraversals() as a starting point.
private void performTraversals() { ... 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) { if (mPendingTransitions ! = null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } performDraw(); }}... }Copy the code
The method is very long, which is simplified here. We can see that there are three methods implemented in it, namely performMeasure, performLayout and performDraw. Within these three methods, measure, layout and draw will be called respectively to carry out different processes, and onMeasure, onLayout and onDraw, which are most commonly contacted by us, will be called
Let’s analyze them one by one:
4. View rendering
The entire drawing process of a View can be divided into the following three stages:
- Measure: Determine whether the View size needs to be recalculated or calculated if necessary.
- Calculate the actual size of each control for the entire ViewTree (the actual size generally meets the planned width and height, otherwise the draw will not be displayed), in order to carry out global placement in the Layout stage and draw in the Draw stage
- Layout: Determine whether the position of the View needs to be recalculated, if necessary;
- Place each View according to the actual size measured by the measure
- Draw: Determines whether the View needs to be redrawn or redrawn if necessary.
4.1 Measure Phase
- Understand the concept of the planned width and height given by the parent and the actual measured width and height required by the parent
- There is a recursive measurement concept for ViewTree: initiate the recursive process by calling child.measure in onMeasure
In this phase, the actual size of each control is calculated for the entire ViewTree for global placement in the Layout phase and drawing in the Draw phase, that is, setting the actual height (mMeasuredHeight) and width (mMeasureWidth), The actual measuring width and height of each View control is determined by the parent View and its own View
Before we start to explain this stage, we still have a preliminary understanding of the relevant knowledge points in rewriting controls (View or ViewGroup). It may be clearer for us to understand the overall drawing process of Android with questions. Of course, if you do not know the control Measure process now, you can also read on. Answer any questions later
Measur process 4.1.1 Measur process when rewriting View and ViewGroup
When we overwrite the View or ViewGroup to implement a custom control, the first concept we are told is that the actual measurement width and height of the current control is determined by the parent control and its own configuration parameters. This mechanism is mainly realized by overwriting the OnMeasure method:
- Rewrite the onMeasure(int widthMeasureSpec, int heightMeasureSpec) method with the planned width and height generated by the parent layout combined with the parent layout condition and the current control’s own configured parameters
- This method will have a default implementation of View#onMeasure, which directly returns the setMeasuredDimension of the parent layout’s planned attribute, as explained in 4.1.2
- Overriding the View function will not recreate this function, but will use the default match_parent effect, as explained below. In fact, it should also be rewritten and come up with the actual measured width you expect from the planned width
- [1] call setMeasuredDimension(int measuredWidth, int measuredHeight) in onMeasure to inform the parent layout and globally store the measuredWidth and height required by the parent
- In onMeasure(int, int), ** must call **setMeasuredDimension(int Width, int height) to store measured width and height values. Failure to do so raises an IllegalStateException
- The final value of setMeasuredDimension should take into account: The padding of the current layout and the margin of the child control.
- And for the ViewGroup,Measure (int widthMeasureSpec, int heightMeasureSpec) on all child controls. So as to realize the iterative measurement process of the whole ViewTree
- The view. measure calls the View’s onMeasure method, and the View’s measurement is done in onMeasure(int, int)
- Measure of the planned width passed to the child control = (current padding + marging of the child View + width occupied by other child controls) and ViewGroup#measureChildWithMargins is the implementation of the current layout’s parent layout to the planned width and height of the current layout and the child controls to configure the android:widht,height property
- ViewGroup#getChildMeasureSpec is a concrete algorithm implementation of 4.1.1.1
@override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Declare a temporary variable to store the expected value of the child control int childDesireWidth = 0; int childDesireHeight = 0; If (getChildCount() > 0) {// for (int I = 0; i < getChildCount(); I ++) {// Get each child View object reference View Child = getChildAt(I); int childWidthSpec, childHeightSpec; int childWidthSize, childWidthMode, childHeightSize, childHeightMode; CLP = (CustomLayoutParams) child.getLayOutParams (); CLP = (CustomLayoutParams) child.getLayOutParams (); // Calculate the planned width and height of the subview and pass in the actual measurement; Plan width = (current padding + marging of child View + width occupied by other child controls) and (plan width and height of the parent layout of the current layout) and (Android :widht,height property configured by the child control). 4.1.2.1 measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); // In fact, the best way for our self-written application is to remove the method in the framework and call view.measure() directly. We need to pay special attention to the calculateChildState. The first key is that the above elements should also be considered, and the second key is the choice. ChildWidthSpec = calculateChildState(widthMeasureSpec, CLP); //childHeightSpec = calculateHeightState(heightMeasureSpec,clp); ParentDesireWidth += child.getMeasuredWidth() + clp.leftMargin + clp.rightMargin; parentDesireWidth = child.getMeasuredWidth() + clp.leftMargin + clp.rightMargin; parentDesireHeight += child.getMeasuredHeight() + clp.topMargin + clp.bottomMargin; ParentDesireWidth += getPaddingLeft() + getPaddingRight(); parentDesireHeight += getPaddingTop() + getPaddingBottom(); ParentDesireWidth = math.max (parentDesireWidth, getSuggestedMinimumWidth())); // [1.3] Try to compare the recommended minimum and expected value and get a high value. parentDesireHeight = Math.max(parentDesireHeight, getSuggestedMinimumHeight()); } // [2] Set the final measurement value, The view's actual width (mMeasuredWidth) High (mMeasuredHeight) setMeasuredDimension(resolveSize(parentDesireWidth, widthMeasureSpec), resolveSize(parentDesireHeight, heightMeasureSpec)); } // This method is implemented in viewgroup.java. protected void measureChildWithMargins(View v, int height , int width){ v.measure(h,l) }Copy the code
4.1.1.1 Parameters in onMeasure: Planned width and height generation rules
The MeasureSpec object contains a size and a mode, where mode can take one of three values:
- UNSPECIFIED, 1073741824 [0x40000000] Indicates that no rules are added to the child View.
- EXACTLY, 0 [0x0], EXACTLY
- AT_MOST, -2147483648 [0x80000000], the subview can be as large as possible within the specified size
- void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
The parent layout tells the current control the maximum or exact width that the parent can support. SetMeasuredDimension determines how much width you want. In my limit, I certainly grant money to you, but exceed the limit THAT I give, show not to show that is hard to say
When overriding this method, we should generate the planned width for the child controls
One point to note:
- ViewGroup#getChildMeasureSpec ViewGroup#getChildMeasureSpec ViewGroup#getChildMeasureSpec Of course, the specific export results can be completely redefined according to our own business rules, but we generally follow the literal meaning of variables, so there will be a general generation method.
- The following table does not consider the occupied parts: padding, child-margin, other child controls occupied. This table is mainly used as a logical illustration
- The following table shows the planned width and height of the child layout from the parent layout perspective
Child control configuration android:width, Android :height | Suitable solution | The planned width and height set by the parent layout for the current layout | schematic | WidthMeasureSpec, heightMeasureSpec |
---|---|---|---|---|
A specific value (Android :width = 500 dp) | EXACTLY + childvalue | |||
MATCH_PARENT | The child control should be the exact size +EXACTLY of the parent layout, but due to the uncertainty of the current layout plan width and height, the values passed in can be one of the following | A certain value is EXACTLY | The size of the current layout is determined, so the size of the child elements can also be determined | Enter EXACTLY + parentValue as the planned width and height of the current layout |
General compliance,What about the current plan width and height, what about the child controls | The value of a limiting value AT_MOST | The size of the current layout is limited by the limit value, so the size of the child elements should also be limited by the current container | Use the planned width and height of the current layout as a reference AT_MOST + parentValue | |
The current layout size is unrestricted and undefined | The size of the current layout is unlimited, and it can be any size for child elements, so the size of child elements is not specified or limited | UNSPECIFIED size UNSPECIFIED + 0 | ||
WRAP_CONTENT | The current control should be <= the exact size of the parent layout At_Most, but because of the parent control’s width and height uncertainty, the values passed in are in one of the following cases | A certain value is EXACTLY | The size of a child element that wraps its content cannot exceed the current layout | Use the planned width and height of the current layout as a reference AT_MOST + parentValue |
General compliance, the width and height of the current layout plan,Child <= current layout width and height | The value of a limiting value AT_MOST | The size of the current layout is limited by the limit value, so the size of the child elements should also be limited by the current layout | Use the planned width and height of the current layout as a reference AT_MOST + parentValue | |
The current layout size is unrestricted and undefined | The size of the current layout is unlimited and it can be any size for the child elements so the size of the child elements is not specified or limited | UNSPECIFIED size UNSPECIFIED + 0 |
WidthMeasureSpec, heightMeasureSpec, widthMeasureSpec, widthMeasureSpec, heightMeasureSpec Be sure to discard wrap_content and match_parent, and retain only Exact, At_Most, and UNSPECIFIED
4.1.1.2 setMeasuredDimension Setting: Actual measurement width and height itself
void android.view.View.setMeasuredDimension(int measuredWidth, int measuredHeight)Copy the code
- For the View
- For example, ImageView returns the required width and height of the image, and TextView returns the width and height of the text
- For the ViewGroup
- Generally, it is necessary to traverse the width and height requirements of the call child control first, and cumulative calculation, in combination with their own business requirements, set the required size (can meet the planned width and height, can also be completely ignored, the latter may lead to no display)
4.1.2 measure process
The entire measure call process is a recursive tree
We start with view script #performTraversals() :
Private void performTraversals() {//... Omit the code of so much cosmic dust... if (! MStopped) {/ /... Int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); / /... PerformMeasure (childWidthMeasureSpec); } / /...... Leaving out code with so many human cells... }Copy the code
[Step 1] You can see that in the performMeasure method the original measure is obtained by getRootMeasureSpec and passed as a parameter to the performMeasure method to obtain a full-screen and traversal measure. The input parameters of the lp. Width and lp. Height are MATCH_PARENT, its mWindowAttributes (WindowManager. LayoutParams type) will value given to the lp has been identified, MWidth and mHeight indicate the size of the current window:
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: / / Window can't adjust its size, force the root view size is in line with the Window measureSpec = measureSpec. MakeMeasureSpec (windowSize, measureSpec. EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: / / Window can adjust its size, set a maximum value for the root view measureSpec = measureSpec. MakeMeasureSpec (windowSize, measureSpec. AT_MOST); break; default: / / Window to a certain size, the size of the force the root view as its size measureSpec = measureSpec. MakeMeasureSpec (rootDimension, measureSpec. EXACTLY); break; } return measureSpec; }Copy the code
As we can see from the code, our root view must be full-screen, so we are really in touch with the root view’s measurement size, which is then passed down from top to bottom and determined by the interaction of the current view and its parent
Start from mView(DecorView) and recursively call measure() to measure the whole ViewTree.
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
Since PhoneWindow#DecorView inherits from FrameLayout(unoverwritten measure()), inherits from ViewGroupt(unoverwritten measure()), inherits from View, The View#measure(int widthMeasureSpec, int heightMeasureSpec) method is called. Let’s turn to the method, where the current parameter is the global screen size specification:
Public final void measure(int widthMeasureSpec, int heightMeasureSpec) { /* * Determine if the current mPrivateFlags has the PFLAG_FORCE_LAYOUT mandatory layout flag ** for example, calling View.requestLayout() will add this flag ** * to mPrivateFlags Determine whether the current widthMeasureSpec and heightMeasureSpec changed * / if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) = = PFLAG_FORCE_LAYOUT | | widthMeasureSpec ! = mOldWidthMeasureSpec || heightMeasureSpec ! PFLAG_MEASURED_DIMENSION_SET mPrivateFlags &= If mPrivateFlags is measured again, the measured flag bit PFLAG_MEASURED_DIMENSION_SET mPrivateFlags &= is cleared ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : mMeasureCache.indexOfKey(key); If (cacheIndex < 0 | | sIgnoreMeasureCache) {/ / 【 】 2.1 measuring the size of the View onMeasure (widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } else { long value = mMeasureCache.valueAt(cacheIndex); setMeasuredDimension((int) (value >> 32), (int) value); mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } /* * If mPrivateFlags does not contain the measured flag bit PFLAG_MEASURED_DIMENSION_SET, an exception will be raised if ((mPrivateFlags &) PFLAG_MEASURED_DIMENSION_SET) ! = PFLAG_MEASURED_DIMENSION_SET) { throw new IllegalStateException("onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } / / if the measurement of the View so you can add logo mPrivateFlags PFLAG_LAYOUT_REQUIRED said to layout the mPrivateFlags | = PFLAG_LAYOUT_REQUIRED; MOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension }Copy the code
The onMeasure method is called inside View#measure(). Since the DecorView overrides the method, it actually calls the DecorView#onMeasure method. Inside this method, it’s basically making some judgments, which I won’t expand here, and eventually calls the super.onMeasure method, which is FrameLayout#onMeasure method.
Since different viewgroups have different properties, their ONmeasures must be different. Therefore, it is impossible to analyze all the onMeasure methods of all layout methods. Therefore, the onMeasure method of FrameLayout involved in the current progress will be analyzed here. Other layout methods can be analyzed by the reader
@Override protected void onMeasure(int widthMeasureSpec, Int heightMeasureSpec) {// getChildCount = getChildCount(); int maxHeight = 0; int maxWidth = 0; int childState = 0; // View for (int I = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() ! MeasureChildWithMargins (Child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); //【 Step 3.2】 Find the largest piece in the View, Because if FrameLayout is wrap_content then its size depends on the largest subview maxWidth = math.max (maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); // If FrameLayout is wrap_content, add a child View with the width or height of match_parent to mMatchParentChildren. Because the final measuring the size of the child View will be affected by the FrameLayout final measurement size if (measureMatchParentChildren) {if (lp) width = = LayoutParams) MATCH_PARENT | | lp.height == LayoutParams.MATCH_PARENT) { mMatchParentChildren.add(child); } } } } // Account for padding too maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable ! = null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); SetMeasuredDimension (resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); } private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }Copy the code
- FrameLayout measures each child View according to its MeasureSpec, calling measureChildWithMargin, which calls the child control’s Measure
- For each measured subview, getMeasureWidthHeight() is used to find the maximum measurewidthheight, and FrameLayout is measured by the maximum measurewidth of the subview (wrAP_content mode).
- [Step 3.3] Call the setMeasureDimension method to save FrameLayout measurement width and height
- Call View#resolveSizeAndState to analyze the expected width of the child and the planned width of the father.
- [Step 3.4] (not listed in the code, see here). Finally, the special case is handled, that is, when FrameLayout is wrap_content and its child View is match_parent, the FrameLayout measurement should be reset. Then measure that part of View again
So far, the whole measurement process has actually been shown in front of us:
- Get the full screen size of the current window from viewrotimpl #performTraversals as the top planned width and height specification
- The viewrotimpl #performMeasure calls the DectorView measure(the planned width and height as the parameter) to start the whole global ViewTree measurement process
- The final measure() function will not be overridden, the final call will be View#measure() which will call onMeasure.And read the calculated mMeasuredWidth and mMeasuredHeight into the memory
- ViewGroup will be in OnMeasure() :
- (1) Traverse the child control, taking the padding, child.margin, planned width and its occupied part as parameters, generate the planned width and height of the child control. (2) call the measure() of the child control, and start the iteration step 3. This is also the process for ViewGroup#measureChildWithMargins
- Obtain the measured width and height of the child control, accumulate the required width and height of the child control according to certain business requirements, and update the value of the planned width and height occupied by other child controls as the input of the next traversal
- At the end of the traversal, make a choice between the required width and planned width of the child control based on certain business requirements, and set its own width by calling setMeasuredDimension()
- The View will have its own width and height in OnMesure by calling setMeasuredDimension()
- ViewGroup will be in OnMeasure() :
The following is the analysis of the functions involved in the above steps
4.1.2.1 measureChildWithMargin method
We mentioned above within the FrameLayout measurement measureChildWithMargin method, it receives the main parameter is the child View as well as the parent container MeasureSpec, so it is for the View to measure, so we see this method directly, ViewGroup#measureChildWithMargins:
ParentWidthMeasureSpec: The width and height of the parent layout for the current layout */ Protected void measureChildWithMargins(View Child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); / / childWidthMeasureSpec, ChildWidthMeasureSpec final int childWidthMeasureSpec = getChildChildMeasurespec (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); / / 1}Copy the code
【 Step 1】childWidthMeasureSpec, childHeightMeasureSpec is the planned width and height of the current layout mixed with its children. The ViewGroup#getChildMeasureSpec method is an implementation of the onMeasure parameters described in 4.1.1.1:
/** *spec: The width of the parent layout for the current layout *padding: the padding of the current layout + the margin of the child controls + the width occupied by other child controls *childDimension: Public static int getChildChildMeasurespec (int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); //size indicates the available space of the child View: parent container size minus the padding int size = math.max (0, specsie-padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has a maximum size on us case MeasureSpec.AT_MOST: // Specific can refer to the source code break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: // Omitted... Specific can refer to the source code break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }Copy the code
The child View’s LayoutParams\ parent container SpecMode | EXACTLY | AT_MOST | UNSPECIFIED |
---|---|---|---|
Accurate value (dp) | EXACTLY childSize | EXACTLY childSize | EXACTLY childSize |
match_parent | EXACTLY parentSize | AT_MOST parentSize | UNSPECIFIED 0 |
wrap_content | AT_MOST parentSize | AT_MOST parentSize | UNSPECIFIED 0 |
When the Child View’s MeasureSpec is fetched, we return measureChildWithMargins and execute the code: The child-measure method, which means that the drawing process has been moved from the ViewGroup to the child View, and you can see that the parameters passed are the child View’s MeasureSpec. Then View# Measure is called, which calls OnMeasure again
4.1.2.1 resolveSizeAndState method To choose the child’s expected width and the father’s planned width
/** * View#resolveSizeAndState 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) {// According to the maximum limit result = specSize | MEASURED_STATE_TOO_SMALL; } else {result = size; } break; Case MeasureSpec.EXACTLY:// EXACTLY break; case MeasureSpec.UNSPECIFIED: default: result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }Copy the code
4.1.2.3 default implementation of View#onMeasure
What happens if we don’t review the onMeasure and call its default implementation? Again, look at the code:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } /** * When the View has no background set, returns mMinWidth, which corresponds to the Android :minWidth property; If I have a background, Then the return mMinWidth and mBackground getMinimumWidth (maximum in the background of the original width) * / protected int getSuggestedMinimumWidth () {return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }Copy the code
The default implementation still calls setMeasuredDimension, passing in the return value of getDefaultSize, whose input parameters are: minimum width, planned width of the parent layout
Let’s look at View#getDefaultSize, :
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
If the planned width is EXACTLY the exact value or AT_MOST, then the actual measured width of the current view is that value; UNSPECIFIED, it is the smallest element
It also illustrates a familiar truth: For a custom View that directly inherits from the View, its wrap_content and match_parent property have the same effect, covering the parent layout width and height. Therefore, if you want to implement the custom View’s WRap_content, you need to override the onMeasure method. The wrAP_content property is processed
4.2 Layout stage
- Understand the concept of actually measuring and plotting width heights, the difference between getMeasuredHeight() and getHeight
- After entering the measure phase, all views already have their actual measurement width and height stored
- There is a recursive measurement concept for ViewTree: initiate the recursive process by calling child.layout in onLayout
In this stage, the relative drawing position matrix of each view (top,left,bottom and right of the relative parent layout) is obtained, and the global relative position drawing tree is finally constructed
Before we start to explain this stage, we still follow the previous routine, first we have a preliminary understanding of the rewrite control (View or ViewGroup) related knowledge points
4.2.1 Rewrite View/ViewGroup onLayout()
/** * @param changed Its specific value in View layout method through setFrame and other methods to determine * @param L four parameters is the relative distance between the current View and the parent container * @param t four parameters are the relative distance between the current View and the parent container * @param r */ void onLayout(Boolean changed, int l, int t, int r, int b) {Copy the code
The purpose of onLayoutLayout is to determine the position of the child elements in the parent container, so this procedure should be determined by the parent container, not the child elements. Therefore, We can assume that the onLayout method in a View should be an empty implementation, but it is important to note that both views and viewgroups should consider their own padding in a Layout, and viewgroups should also consider child.margin
Let’s discuss the onLayout rewrite method of ViewGroup. Let’s give a simple example:
protected void onLayout(boolean changed, int l, int t, int r, Int parentPaddingLeft = getPaddingLeft(); int parentPaddingLeft = getPaddingLeft(); int parentPaddingTop = getPaddingTop(); If (getChildCount() > 0) {// Declare a temporary variable storage hypermutilheight, of the occupied height int mutilHeight = 0; For (int I = 0; i < getChildCount(); i++) { View child = getChildAt(i); // The layout of each child view is based on the bottom of the current layout. The layout of each child view is the bottom of the current layout. // Child.layout (parentPaddingLeft, mutilHeight + parentPaddingTop, getMeasuredWidth() + parentPaddingLeft, getMeasuredHeight() + mutilHeight + parentPaddingTop); // Consider its own padding, according to the child control width and height requirements, Vertically distribute all child controls // Child.layout (parentPaddingLeft, mutilHeight + parentPaddingTop,. child.getMeasuredWidth() + parentPaddingLeft, child.getMeasuredHeight() + mutilHeight + parentPaddingTop); CLP = (CustomLayoutParams) child.getLayOutParams (); CLP = (CustomLayoutParams) child.getLayOutParams (); child.layout(parentPaddingLeft + clp.leftMargin, mutilHeight + parentPaddingTop + clp.topMargin, child.getMeasuredWidth() + parentPaddingLeft + clp.leftMargin, child.getMeasuredHeight() + mutilHeight + parentPaddingTop + clp.topMargin); // Change highly mutilHeight += child.getMeasuredheight (); // mutilHeight += child.getMeasuredheight (); }}}Copy the code
The Layout logic is very simple:
-
Iterate over all child controls:
- To calculate the drawing area for the currently traversed child control (coordinates relative to the upper left point of the current layout), consider: current padding+child.margin+ accumulated portion
- Call child.layout() to assign the drawing area to the child control currently traversed, starting the iteration process
- Update the accumulated part
The onLayout example given above is actually very simple, but there is one thing we must note: In essence, the current layout of onLayout parameter is the current layout of the relative position in the layout of his father, the left | top | | bottom right generally won’t affect the current layout of child controls, Meaning of these parameters on the logic is actually should not affect the child. The layout () in the calculation of parameters, it also illustrates the left | top | | bottom right: the isolation effect of child controls relative to the parent layout, the parent layout just direct child controls
If you say, “If these four parameters are useless, why pass them in?” Note that what we said above is that there is no logical correlation, mainly for ease of understanding; In fact, these parameters are required when undertaking some business logic. For example, if you calculate the layout of child elements from the top left corner, it is not necessary; But what if you start at the top right? To do this, you need to know the location properties of the current layout. This is also the FrameLayout process, see below
4.2.2 layout process
VieRootImpl#performLayout is the starting point of the layout process.
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) { mLayoutRequested = false; mScrollMayChange = true; mInLayout = true; final View host = mView; if (DEBUG_ORIENTATION || DEBUG_LAYOUT) { Log.v(TAG, "Laying out " + host + " to (" + host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")"); } Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout"); try { host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); // 1 // omit... } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } mInLayout = false; }Copy the code
Host. layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); Call the DecorView layout method and pass in the actual width and height of the DecorView. In the measurement phase we already know that DecorView inherits from FrameLayout inherits from ViewGroup inherits from View, and actually calls View# Layout, which is final:
public void layout(int l, int t, int r, int b) { if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) ! = 0) { onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } 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); / / 2.1 if (changed | | (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) = = PFLAG_LAYOUT_REQUIRED) {onLayout (changed, l, t, r, b); // 2.2 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); } } } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; }Copy the code
See first calls the step 2.1 】 【 setFrame method, and the four locations in information transmission, the method is used to determine the position of the current control of four vertices, namely the initialization mLeft, mRight, mTop, mBottom these four values, when after the initialization, the current control itself location can be determined
Protected Boolean setFrame(int left, int top, int right, int bottom) { mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); boolean sizeChanged = (newWidth ! = oldWidth) || (newHeight ! = oldHeight); if (sizeChanged) { if ((mPrivateFlags & PIVOT_EXPLICITLY_SET) == 0) { // A change in dimension means an auto-centered pivot point changes, too if (mTransformationInfo ! = null) { mTransformationInfo.mMatrixDirty = true; } } onSizeChanged(newWidth, newHeight, oldWidth, oldHeight); } // omit... return changed; }Copy the code
For each View, including the ViewGroup, the above four values hold the position of the Viwe, so these four values are the final width and height.
GetLeft (), getTop() and other methods should be called after the layout method is completed to obtain the final width and height of the View. If we call the corresponding method before this, we will only get 0, so we usually get the width and height information of the View in the onLayout method
Note that while setFrame is set, onSizeChanged () will be called if the layout position changes, so we can see:
- The callback time for onSizeChanged is during the Layout phase
- It only fires when something changes
Therefore, we usually determine the paint width and height of the control in onSizeChanged
[Step 2.2] The onLayout() method is called, which is an empty implementation in the View and is used to locate the child View in the ViewGroup. That is, within the method, the child View will call its own Layout method to further complete its layout process. FrameLayout#onMeasure: FrameLayout#onMeasure: FrameLayout#onMeasure: FrameLayout#onMeasure: FrameLayout#onMeasure
@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) { final int count = getChildCount(); / / the following four values will affect the child / / parentLeft View layout parameters determined by the padding and the Foreground of the parent container final int parentLeft = getPaddingLeftWithForeground (); / / parentRight determined by the width and the padding and the Foreground of the parent container final int parentRight = right - left - getPaddingRightWithForeground (); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() ! = GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); Final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop; int gravity = lp.gravity; if (gravity == -1) { gravity = DEFAULT_CHILD_GRAVITY; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; // When the child View sets the horizontal layout_gravity property, ChildLeft: X switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {absoluteGravity & Gravity. Since the child View is to be displayed in the middle of the horizontal, we need to calculate the following: * (parentRight - parentleft-width)/2 * (parentRight - parentleft-width)/2 * (parentRight - parentLeft)/2 * If the child View is also constrained by margin, since leftMargin makes the child View right-biased and rightMargin makes the child View left-biased, + leftmargin-rightmargin. */ case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; Margin-case Gravity.RIGHT: if (! forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; } // If layout_gravity is not set to the horizontal direction, it defaults to the horizontal LEFT. // If layout_gravity is set to the horizontal LEFT, the upper LEFT abscis of the child View equals parentLeft plus the magin value of the child View. childLeft = parentLeft + lp.leftMargin; } // When the child View sets the vertical layout_gravity, Switch (verticalGravity) {case Gravity.TOP: childTop = childTop; childTop = parentTop + lp.topMargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; } // Layout the child elements with the upper-left coordinate (childLeft, childTop), ChildLeft +width,childTop+height (childLeft, childTop, childTop+height); }}}Copy the code
See from the source, onLayout method inside the direct call layoutChildren method, and layoutChildren is a concrete implementation. The entire process is essentially the same as the example shown in 4.2.1
- Iterate over child controls
- The layout parameters of the child View are determined by the layout_gravity property of the child View, the measuring width and height of the child View, the padding value of the parent container, child.margin, and the accumulated space occupied by other child controls
- Call the child.layout method to pass the layout flow from the parent container to the child elements, starting the iterative process
- Update accumulated width and height
4.3 Draw draw stage
- Need to know about graphics: Canvas,Paint, etc
- The canvas is the position of the drawing matrix defined in the layout stage, which is generally calculated according to the measurement value in the measure stage
- When you draw, you have to think about your own padding
The measurement process determines the size of the View, the layout process determines the position of the View, so the drawing process will determine the appearance of the View, and what a View should display is completed by the drawing process.
Before we start to explain this stage, we still follow the previous routine, first we have a preliminary understanding of the rewrite control (View or ViewGroup) related knowledge points
4.3.1 Overwrite View/ViewGroup onDraw()
Take a look at a simple example, above image and below text:
protected void onDraw(Canvas canvas) { canvas.drawColor(Color.GRAY); */ canvas. DrawBitmap (mBitmap, getWidth() / 2 - mbitmap.getwidth () / 2, getHeight() / 2 - mBitmap.getHeight() / 2, null); canvas.drawText(mStr, getWidth() / 2, mBitmap.getHeight() + getHeight() / 2 - mBitmap.getHeight() / 2 - mPaint.ascent(), mPaint); }Copy the code
4.3.2 the draw process
4.3.2.1 Canvas generation process
View view script #performDraw as the starting point of the draw process:
private void performDraw() { //... final boolean fullRedrawNeeded = mFullRedrawNeeded; try { draw(fullRedrawNeeded); } finally { mIsDrawing = false; Trace.traceEnd(Trace.TRACE_TAG_VIEW); } // omit... }Copy the code
[Step 1] The ViewRootImpl#draw method is actually called and the fullRedrawNeeded parameter is passed. This parameter is obtained by the mFullRedrawNeeded member variable to determine whether the entire view needs to be redrawn. If the view is drawn for the first time, So obviously all views should be drawn, and if for some reason the view is redrawn, then it’s not necessary to draw all views.
Let’s take a look at [Step 2] View script #draw:
private void draw(boolean fullRedrawNeeded) { ... // [Step 2.1] get mDirty. Final Rect dirty = mDirty; if (mSurfaceHolder ! = null) { // The app owns the surface, we won't draw. dirty.setEmpty(); if (animating) { if (mScroller ! = null) { mScroller.abortAnimation(); } disposeResizeBuffer(); } return; } // [Step 2.2] If fullRedrawNeeded is true, set the dirty area to the entire screen, indicating that the entire view needs to be drawn. The first drawing process, need to draw all views the if (fullRedrawNeeded) {mAttachInfo. MIgnoreDirtyState = true; Dirty. Set (0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); } // omit... [Step 2.3] The viewrotimpl #drawSoftware method is called and the parameters are passed to if (! drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) { return; }}Copy the code
[Step 2.1] get mDirty, which indicates the area to be redrawn. [Step 2.2] If fullRedrawNeeded is true, set the dirty area to the entire screen, indicating that the entire view needs to be drawn. We call the viewrotimpl #drawSoftware method and pass in the parameters. Let’s look at this function:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { // Draw with software renderer. final Canvas canvas; try { final int left = dirty.left; final int top = dirty.top; final int right = dirty.right; final int bottom = dirty.bottom; // [Step 2.3.1] instantiate the Canvas object and lock the Canvas area. Canvas = msurface.lockCanvas (dirty); // The dirty rectangle can be modified by Surface.lockCanvas() //noinspection ConstantConditions if (left ! = dirty.left || top ! = dirty.top || right ! = dirty.right || bottom ! = dirty.bottom) { attachInfo.mIgnoreDirtyState = true; } // [Step 2.3.2] assign a series of canvas attributes to canvas. SetDensity (mDensity); } try { if (! canvas.isOpaque() || yoff ! = 0 || xoff ! = 0) { canvas.drawColor(0, PorterDuff.Mode.CLEAR); } dirty.setEmpty(); mIsAnimating = false; attachInfo.mDrawingTime = SystemClock.uptimeMillis(); mView.mPrivateFlags |= View.PFLAG_DRAWN; try { canvas.translate(-xoff, -yoff); if (mTranslator ! = null) { mTranslator.translateCanvas(canvas); } canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); attachInfo.mSetIgnoreDirtyState = false; // call decorview.draw to start the recursive drawing process mview.draw (canvas); } } return true; }Copy the code
[Step 2.3.1] instantiate the Canvas object and lock the Canvas area, which is determined by the dirty area. [Step 2.3.2] Assign a series of attributes to the Canvas. [Step 2.3.3] mview.draw (Canvas), Call decorView.draw to formally begin the recursive drawing process
4.3.2.2 View#draw iteration process
ViewGroup does not override the draw method, so all views call View#draw.
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; /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */ // Step 1, draw the background, if needed int saveCount; if (! dirtyOpaque) { drawBackground(canvas); } // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) ! = 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) ! = 0; if (! verticalEdges && ! horizontalEdges) { // Step 3, draw the content if (! dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Overlay is part of the content and draws beneath Foreground if (mOverlay ! = null && ! mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } // Step 6, draw decorations (foreground, scrollbars) onDrawForeground(canvas); // we're done... return; }... }Copy the code
As you can see, the draw process is complex, but the logic is clear, and the official notes clearly explain how to do each step. The dirtyOpaque bit is used to determine whether the current View is transparent. If the View is transparent, some steps such as background drawing and content drawing will not be performed according to the following logic. This is easy to understand, since a View is transparent, there is no need to draw it.
Then there are the six steps of the drawing process. Here we summarize what these six steps are respectively and then expand them:
- Draw the background of the View
- Save the current layer information (skip)
- Draw the contents of the View, where the View#onDraw method is called
- Each View needs to override this method; Viewgroups generally do not need to implement this method unless there is a specific effect
- To draw the children of the View (if any), we call dispatchDraw(Canvas) for the iterative drawing process
- The dispatchDraw() method internally iterates through each subview, calling drawChild() to re-call the draw() method of each subview.
- It is worth noting that the ViewGroup class has already overridden the dispatchDraw () implementation for us. Applications generally do not need to override this method, but they can override the parent class function for specific functionality
- Draw the faded edges of the View, similar to the shadow effect (skip)
- Draw the View’s decorations (e.g. scroll bars)
Here we only talk about 3 and 4. For more information, check out the Android View Draw process (Draw)
Step 3: Draw content
Here we call the View#onDraw method, which is an empty implementation in the View, because different views have different content, and we need to implement it ourselves, that is, rewrite the method in the custom View to implement.
Step 4: Draw a subview
If the current View is a ViewGroup, then we need to draw its child View, and we call dispatchDraw, and the View has an empty implementation, and the ViewGroup overrides this method, so let’s see, ViewGroup#dispatchDraw:
protected void dispatchDraw(Canvas canvas) { boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode); final int childrenCount = mChildrenCount; final View[] children = mChildren; int flags = mGroupFlags; 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; } } int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() ! = null) { more |= drawChild(canvas, child, drawingTime); }} // omit... }Copy the code
It’s a long source code, but just to make it simple, it basically iterates through all the child views, and for each child View we call drawChild, and we find this method, ViewGroup#drawChild:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }Copy the code
View#draw = View#draw = View#draw = View#draw
Boolean draw(Canvas Canvas, ViewGroup parent, long drawingTime) {// omit... if (! drawingWithDrawingCache) { if (drawingWithRenderNode) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; ((DisplayListCanvas) canvas).drawRenderNode(renderNode); } else { // 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 if (cache ! = null) { mPrivateFlags &= ~PFLAG_DIRTY_MASK; if (layerType == LAYER_TYPE_NONE) { // no layer paint, use temporary paint to draw bitmap Paint cachePaint = parent.mCachePaint; if (cachePaint == null) { cachePaint = new Paint(); cachePaint.setDither(false); parent.mCachePaint = cachePaint; } cachePaint.setAlpha((int) (alpha * 255)); Canvas. DrawBitmap (cache, 0.0 f, 0.0 f, cachePaint); } else { // use layer paint to draw the bitmap, merging the two alphas, but also restore int layerPaintAlpha = mLayerPaint.getAlpha(); mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha)); Canvas. DrawBitmap (cache, 0.0 f, 0.0 f, mLayerPaint); mLayerPaint.setAlpha(layerPaintAlpha); }}}Copy the code
We will focus on the core part, first determine whether there is a cache, that is, whether it has been drawn once before, if not, we will call draw(canvas) method and start normal drawing, that is, the six steps mentioned above, otherwise use cache to display
At this point we basically understand the iteration process of View Draw.
5. View lifecycle
-
The location and use of onAttachedToWindow() throughout the Activity lifecycle
-
OnFinishInflate [setContentView stage completed] is triggered when all child controls in the View are mapped to XML
- OnAttachedToWindow () is triggered when a View is attached to a window and is called only once, after OnResume () and before PhoneWindow#addView, which can be used to change the window size
- OnMeasure (int, int) determines the size of all child elements
- OnSizeChanged (int, int, int, int) trigger when view size changes, during setFrame in Layout stage, used to determine view size (record width and height of current view)
- OnLayout (Boolean, int, int, int, int) Triggered when the View assigns the size and position of all child elements
- OnDraw (Canvas) View renders the details of the content
-
OnDetachedFromWindow () is triggered when the view leaves the attached window. Android123 suggests that this method is the opposite of onAttachedToWindow(). ActivityThread. HandleDestroyActivity (), will be called only once. At this point we do some finishing work in this method, such as: unregister broadcast and so on.
-
OnKeyDown (int, KeyEvent) is triggered when a key is pressed
- OnKeyUp (int, KeyEvent) is triggered when a button is pressed and then bounced
- OnTrackballEvent (MotionEvent) Trackball event
- OnTouchEvent (MotionEvent) Indicates the touch screen event
- OnFocusChanged (Boolean, int, Rect) Triggered when the View gains or loses focus
- OnWindowFocusChanged (Boolean) Triggered when a view containing a window gains or loses focus
- OnWindowVisibilityChanged (int) when the window is contained in visible view changes triggered
5.1 Display Process
7-12 13:44:45. 413, 23734-23734 /? D/------: --onFinanshInflate 07-12 13:44:45.443 23734-23734/? D / -- -- -- -- -- - : - onMeasure 07-12 13:44:45. 493, 23734-23734 /? D / -- -- -- -- -- - : - onSizeChanged 07-12 13:44:45. 493, 23734-23734 /? D / -- -- -- -- -- - : - onLayout 07-12 13:44:45. 503, 23734-23734 /? D / -- -- -- -- -- - : - onMeasure 07-12 13:44:45. 503, 23734-23734 /? D / -- -- -- -- -- - : - onLayout 07-12 13:44:45. 503, 23734-23734 /? D / -- -- -- -- -- - : - ontouchCopy the code
5.2 Invalidate (), requsetLaytout(), and requestFocus
Invalidate (), requsetLaytout(), and requestFocus() will eventually call the Schedulale traversale () method in ViewRoot, which then initiates an asynchronous message, The performTraverser() method is called in message processing to traverse the View, which triggers the View rendering process, but there is a difference: some processes must be repainted with variable markers, and some may not
- RequestLayout ()
- This must result in calls to measure() and layout() procedures, but not necessarily draw procedures
- The process of rearranging the entire View tree includes the measure() and layout() procedures. The draw() procedure is generally not called and no View is redrawn, including the caller itself
- Invalidate () method
- Request a redraw of the View tree, the draw() procedure.
- If the View does not change in size, the layout() procedure is not called and only views that “need to be redrawn” are drawn. That is, those views that request the invalidate() method are drawn. ViewGroup, draw the entire ViewGroup).
- requestFocus()
- Request the draw() process of the View tree, but only draw views that “need redrawing.
Typically, requestLayout is used when a View determines that it is no longer suitable for the current area, such as when its LayoutParams change and requires the parent layout to remeasure, lay out, and draw it. Invalidate refreshes the current View so that it is redrawn without measurement or layout. If the View is redrawn without measurement, invalidate is often more efficient than requestLayout
5.3 requestLayout() raising process
The child View calls the requestLayout method, which marks the current View and parent container, and submits layer by layer until the ViewRootImpl handles the event. The ViewRootImpl calls three processes, starting with measure, For each view with a marker bit and its child view will be measured, laid out, drawn.
- This must result in calls to measure() and layout() procedures, but not necessarily draw procedures
- The process of rearranging the entire View tree includes the measure() and layout() procedures. The draw() procedure is generally not called and no View is redrawn, including the caller itself
RequestLayout () normally raises the following:
- SetVisibility () method: requestLayout() and invalidate methods will be called indirectly when the View’s VISIBLE state changes to GONE. At the same time, since the entire View tree size changes, the measure() and draw() processes are called, and again, only views that need to be “redrawn” are drawn
View#requestLayout ();
/** * Call this when something has changed which has invalidated the * layout of this view. This will schedule a layout pass of the view * tree. This should not be called while the view hierarchy is currently in a layout * pass ({@link #isInLayout()}. If layout is happening, the request may be honored at the * end of the current layout pass (and then layout will run again) or after the current * frame is drawn and the next layout occurs. * * <p>Subclasses which override this method should call the superclass Method to * handle possible request-during layout errors correctly.</p> */ / // This request is deferred until the layout process is complete or until the drawing process is complete and the next layout is discovered. @CallSuper public void requestLayout() { if (mMeasureCache ! = null) mMeasureCache.clear(); if (mAttachInfo ! = null && mAttachInfo.mViewRequestingLayout == null) { // Only trigger request-during-layout logic if this is the view Requesting it, // Not the views in its parent hierarchy // [Step 1] Determine whether the current View tree is in the layout process, If it is, the request is deferred until the layout process is complete or until the drawing process is complete and the next layout is discovered. ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot ! = null && viewRoot.isInLayout()) { if (! viewRoot.requestLayoutDuringLayout(this)) { return; } } mAttachInfo.mViewRequestingLayout = this; } / / [2] for the current view tag set a PFLAG_FORCE_LAYOUT mPrivateFlags | = PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; if (mParent ! = null && ! MParent. IsLayoutRequested ()) {/ / ask the parent container layout step 3 】 【 mParent. RequestLayout (). } if (mAttachInfo ! = null && mAttachInfo.mViewRequestingLayout == this) { mAttachInfo.mViewRequestingLayout = null; }}Copy the code
[Step 1] Determine whether the current View tree is laying out the process. If so, the request will be delayed until the layout process is complete or the drawing process is complete and the next layout is discovered. [Step 2] Set the flag bit PFLAG_FORCE_LAYOUT for the current View. The function of this bit is to indicate that the current View needs to be rearranged [Step 3]. Call the mparent. requestLayout method and iteratively request the layout from the parent container. Since the parent is requesting a layout, the parent’s requestLayout method is called, adding the PFLAG_FORCE_LAYOUT flag bit to the parent, and the parent will call its parent’s requestLayout method, which is requestLayout event layer passed up. Until the DecorView, the root View, is passed to the ViewRootImpl, the requestLayout event of the child View is received and processed by the ViewRootImpl.
Looking at the up-passing process, the chain of responsibility pattern is used, where the event is passed up until a superior can handle the event. In this case, only ViewRootImpl can handle requestLayout events.
Let’s look at the final [step 4] ViewRootImpl#requestLayout:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}Copy the code
Does that look familiar? Yes! This is the process in <3.2 View rendering starting point >, where the scheduleTraversals method is called, which is an asynchronous method that eventually invokes the ViewRootImpl#performTraversals method to start the entire View rendering process.
5.3.1 PFLAG_FORCE_LAYOUT labeled
The above explanation is basically enough to continue the whole View drawing process, here, we add some variable markers in the drawing process
- PFLAG_FORCE_LAYOUT,, the function of this flag bit is in the View’s measure process, if the current View set this flag bit, the mapping process will be carried out
-
PFLAG_LAYOUT_REQUIRED, this flag bit is used in the View layout process, if the current View has this flag bit set, the layout process
View# measure methods: Response PFLAG_FORCE_LAYOUT — If the current View has a flag bit of PFLAG_FORCE_LAYOUT, the measurement process is performed, calling onMeasure to measure the View, and then finally setting the flag bit to PFLAG_LAYOUT_REQUIRED
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 back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
...
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
}Copy the code
View#layout source: responds to PFLAG_LAYOUT_REQUIRED, if the current View has this flag bit set, the layout process will take place
public void layout(int l, int t, int r, int b) { ... // Check whether the flag bit is PFLAG_LAYOUT_REQUIRED, if so, The layout of 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); }}} mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; }Copy the code
5.4 Invalidate () raising process
- Request a redraw of the View tree, the draw() procedure.
- If the View does not change in size, the layout() procedure is not called and only views that “need to be redrawn” are drawn. That is, those views that request the invalidate() method are drawn. ViewGroup, draw the entire ViewGroup.
When a child View calls the invalidate method, a marker bit is added to the View and the parent container is constantly asked to refresh. The parent container calculates the area it needs to redraw until it is passed to the ViewRootImpl and finally the performTraversals method is triggered. Start the View tree redraw process (draw only views that need to be redrawn)
Invalidate () generally raises the following:
- Calling the invalidate() method directly, asking to redraw (), only draws the caller.
- SetSelection () method: requests redraw (), but only draws the caller itself.
- SetVisibility () method: When the View is VISIBLE in INVISIBLE, invalidate() will be called indirectly and the View will be drawn.
- SetEnabled () method: requests a redraw (), but does not redraw any view including the caller itself.
View# invalidate method:
public void invalidate() { invalidate(true); } void invalidate(Boolean invalidateCache) {// Generate the matrix to be drawn: (0,0) to (current control size). This value is passed to the parent layout using 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; } if (skipInvalidate()) {return; } // [step 1] Determine whether the child View needs to be redrawn according to the mark bit of the View. If the View has no change, So there is no need to redraw the 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)) { if (fullInvalidate) { mLastIsOpaque = isOpaque(); mPrivateFlags &= ~PFLAG_DRAWN; } / / [2] set PFLAG_DIRTY flag bit mPrivateFlags | = PFLAG_DIRTY; if (invalidateCache) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; // Propagate the damage rectangle to the parent view. // Propagate the damage rectangle to the parent view. 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's method, passing up the event p.invalidatechild (this, damage); }... }}Copy the code
[Step 0] Generate the region matrix to be drawn: (0,0) to (current control size), this value is passed to the parent layout used [step 1] according to the View marker bit to determine whether the child View needs to be redrawn, if there is no change in the View, P.invalidatechild (this, damage); PFLAG_DIRTY (this, damage); Pass the areas that need to be redrawn to the parent container
ViewGroup#invalidateChild:
/** * Don't call or override this method. It is used for the implementation of * the view hierarchy. */ public final Void invalidateChild(View Child, final Rect dirty) {// Set parent = ViewParent parent = this; final AttachInfo attachInfo = mAttachInfo; if (attachInfo ! = null) { // If the child is drawing an animation, we want to copy this flag onto // ourselves and the parent to make sure the invalidate request goes // through final boolean drawAnimation = (child.mPrivateFlags & PFLAG_DRAW_ANIMATION) == PFLAG_DRAW_ANIMATION; // Check whether the child that requests the invalidate is fully opaque // Views being animated or transformed are not considered opaque because we may // be invalidating their old position and need the parent to paint behind them. Matrix childMatrix = child.getMatrix(); final boolean isOpaque = child.isOpaque() && ! drawAnimation && child.getAnimation() == null && childMatrix.isIdentity(); // Mark the child as dirty, using the appropriate flag // Make sure we do not set both flags at the same time int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY; if (child.mLayerType ! = LAYER_TYPE_NONE) { mPrivateFlags |= PFLAG_INVALIDATED; mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID; } / / store the child View mLeft and mTop value final int [] location = attachInfo. MInvalidateChildLocation; location[CHILD_LEFT_INDEX] = child.mLeft; location[CHILD_TOP_INDEX] = child.mTop; . do { View view = null; if (parent instanceof View) { view = (View) parent; } if (drawAnimation) { if (view ! = null) { view.mPrivateFlags |= PFLAG_DRAW_ANIMATION; } else if (parent instanceof ViewRootImpl) { ((ViewRootImpl) parent).mIsAnimating = true; } } // If the parent is dirty opaque or not dirty, mark it dirty with the opaque // flag coming from the child that initiated the invalidate if (view ! = null) { if ((view.mViewFlags & FADING_EDGE_MASK) ! = 0 && view.getSolidColor() == 0) { opaqueFlag = PFLAG_DIRTY; } if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) ! = PFLAG_DIRTY) {/ / 【 】 4.1 to set the current View of labeling the mPrivateFlags = (the mPrivateFlags & ~ PFLAG_DIRTY_MASK) | opaqueFlag; } // Call invalidateChildInParent of ViewGrup or invalidateChildInParent of ViewRootImpl // if the top-level view is reached. parent = parent.invalidateChildInParent(location, dirty); if (view ! = null) { // Account for transform on current parent Matrix m = view.getMatrix(); if (! m.isIdentity()) { RectF boundingRect = attachInfo.mTmpTransformRect; boundingRect.set(dirty); m.mapRect(boundingRect); Dirty. Set ((int) (boundingRect. left-0.5f), (int) (boundingRect. top-0.5f), (int) (boundingRect.right + 0.5f), (int) (boundingRect. Bottom + 0.5 f)); } } } while (parent ! = null); }}Copy the code
In ViewGroup#invalidateChild, set the current view’s flag bit, and then add a do… While… Loop, which is used to go back up the parent container and get the dirty union of the areas that the parent and child View need to redraw. When the parent container is not ViewRootImpl, the invalidateChildInParent method of the ViewGroup is called.
【 step 4.2.1】ViewGroup#invalidateChildInParent: call the offset method to convert the coordinates of the current dirty area into the coordinates of the parent container. Then call the union method to union the child dirty area with the parent container. The dirty area becomes the parent container area. Finally, the parent container of the current view is returned for the next loop
[Step 4.2.2] The parent container’s invalidateChildInParent method is called because the parent container’s methods are constantly called up. }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} The measure and layout processes will not be executed since no measure and layout markers are added, and will start directly from the DRAW process
@Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); if (DEBUG_DRAW) Log.v(TAG, "Invalidate child: " + dirty); if (dirty == null) { invalidate(); return null; } else if (dirty.isEmpty() && ! mIsAnimating) { return null; } if (mCurScrollY ! = 0 || mTranslator ! = null) { mTempRect.set(dirty); dirty = mTempRect; if (mCurScrollY ! = 0) { dirty.offset(0, -mCurScrollY); } if (mTranslator ! = null) { mTranslator.translateRectInAppWindowToScreen(dirty); } if (mAttachInfo.mScalingRequired) { dirty.inset(-1, -1); } } final Rect localDirty = mDirty; if (! localDirty.isEmpty() && ! localDirty.contains(dirty)) { mAttachInfo.mSetIgnoreDirtyState = true; mAttachInfo.mIgnoreDirtyState = true; } // Add the new dirty rect to the current one localDirty.union(dirty.left, dirty.top, dirty.right, dirty.bottom); // Intersect with the bounds of the window to skip // updates that lie outside of the visible region final float appScale = mAttachInfo.mApplicationScale; Final Boolean Intersected = localDirty. Intersect (0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); if (! intersected) { localDirty.setEmpty(); } if (! mWillDrawSoon && (intersected || mIsAnimating)) { scheduleTraversals(); } return null; }Copy the code
5.5 postInvalidate() raising process
PostInvalidate is called from a non-UI thread, while invalidate is called from a UI thread
View# postInvalidate:
public void postInvalidate() {
postInvalidateDelayed(0);
}
public void postInvalidateDelayed(long delayMilliseconds) {
// We try only with the AttachInfo because there's no point in invalidating
// if we are not attached to our window
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
attachInfo.mViewRootImpl.dispatchInvalidateDelayed(this, delayMilliseconds);
}
}Copy the code
As can be seen from the above code, the view tree will be redrawn only if attachInfo is not null, that is, the view tree will be redrawn only if the view is added to the window. Because this is an asynchronous method, an error will occur if the view is redrawn before being added to the window. So it is going to make a judgment call step 2 】 【 ViewRootImpl# dispatchInvalidateDelayed method: use the Handler, sent an asynchronous message to the main thread, apparently sent here is MSG_INVALIDATE, promptly notify the main thread to refresh the view
public void dispatchInvalidateDelayed(View view, long delayMilliseconds) {
Message msg = mHandler.obtainMessage(MSG_INVALIDATE, view);
mHandler.sendMessageDelayed(msg, delayMilliseconds);
}Copy the code
[Step 3] Concrete implementation logic we can see the implementation of the mHandler:
final ViewRootHandler mHandler = new ViewRootHandler(); final class ViewRootHandler extends Handler { @Override public String getMessageName(Message message) { .... } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_INVALIDATE: ((View) msg.obj).invalidate(); break; . }}}Copy the code
As you can see, the message argument is passed to an instance of the View, and the invalidate method is called directly, and the invalidate process continues
reference
-
- DecorView and ViewrotimPL
- Fully parse the Android View measurement process (Measure)
- The Android View Layout process is fully resolved
- Android View Drawing process (Draw) fully resolved
- Android View in-depth analysis of requestLayout, Invalidate and postInvalidate
-
In-depth understanding of Android View drawing process
-
Android View drawing process and invalidate() and other related methods analysis
- Analysis of the process of adding layout files/views to Windows on Android — start with setContentView()
- A brief analysis of measure process, WRAP_CONTENT and XML layout file parsing process in Android
- A brief analysis of measure process, WRAP_CONTENT and XML layout file parsing process in Android
-
Custom control trilogy View part 1 – Measurement and layout