preface
Interviewer: why wrap in custom View_ Will content fail?
To answer this question, we need to understand the past and present lives drawn by view
When is the view drawn?
In which life cycle of the Activity is the view drawn?
After onResume
Get to know viewrootimpl
We know that the onResume method is actually just a callback method. The previous call is
handleResumeActivity -> performResumeActivity -> onResume
After onResume, it will return to handleResumeActivity, and then execute addView to add the visible view to the window, where the window acts as a display,
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) { //Call onResume final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); ViewManager wm = a.getWindowManager(); if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; wm.addView(decor, l); // Add decor to wm } } ... // wm is a WindowManagerGlobal that holds viewrootimpl public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow, int userId) { root.setView(view, wparams, panelParentView, userId);// ViewRoootImpl }
The root here is ViewRootImpl. After entering setView, it is the work of ViewRootImpl. ViewRootImpl is the tool for adding views in window
The role of ViewRootImpl in drawing views
setView has a lot of code, mainly calling requestLayout()
requestLayout does two things
-
Check whether the current thread is the thread creating the View. If not, throw an exception
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
-
Execute scheduleTraversals
mTraversalBarrier means inserting a synchronization barrier into the handler, which means that the next task to be sent to the handler is the highest priority and needs to be processed immediately (screen refresh often needs to be the highest priority)
M choreographer is a thread that will execute the drawing task. The drawing task is in the incoming mTraversalRunnable
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // Note the mTraversalRunnable passed in mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); notifyRendererOfFramePending(); } }
Perform specific drawing
final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } void doTraversal() { if (mTraversalScheduled) { ... performTraversals(); ... } } private void performTraversals() {}
It will be executed to performTraversals. There are many calls to this method, which can be directly to ViewGroup and View, as shown below (the picture comes from the network)
performTraversals will call performMeasure, performLayout and performDraw respectively
These three methods, I think you can guess, will start the onMesure, onLayout and onDraw methods
Summary
Back to our question
When is the view drawn? viewRootImpl does it all after onResume, and the end point is the three drawing methods of the view
Therefore, the Activity before onResume cannot obtain the width and height of the view, because the width and height of the view are finally determined in onLayout
But in onCreate, you can get it through the View.post() method. Why?
In fact, it's also very simple. It's nothing more than blocking it and putting it in the queue. When it's drawn and notified, we can get the width and height. Let's take a look at the source code
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; //If it is not null here, it means that the view has been added to the window and the drawing has already been completed if (attachInfo != null) { return attachInfo.mHandler.post(action); } //If it is null, it means that the view is not ready and put it in the queue getRunQueue().post(action); return true; }
Do you think he can predict the future? In fact, he is either bragging or waiting until the future has happened
Next, we discuss the specific rendering process of view
Drawing process of View
If you don't know about View, you don't really get started with android
Both TextView gizmos and large containers such as LineLayout are evolved from View, and TextView also inherits from View
public class TextView extends View implements ViewTreeObserver.OnPreDrawListener {}
Layout controls such as LineLayout are special in that they come from ViewGroup, which inherits from View
public class RelativeLayout extends ViewGroup {} public abstract class ViewGroup extends View implements ViewParent, ViewManager {}
View can be compared to water. A lot of water together is a view group, but it is still water in essence
In addition to display, View must have perfect sliding and clicking strategies, which is the most frequent operation on the mobile phone. Next, we will learn more about the display, sliding, event and drawing principles of View.
Display method
If you want to display and know which control is placed, you need to accurately locate it. Here we use the coordinate system. There are two kinds
-
android coordinate absolute positioning
The simplest is to take the upper left corner as the coordinate origin, the right side is the positive direction of the x axis, and the lower side is the positive direction of the y axis
Use getRawX() and getRawY() methods to obtain X and Y coordinates, which is an absolute positioning method
-
view coordinate relative positioning
Because the space in android is nested layer by layer, a child control can see its position through its relative position to the parent control. The specific method is as follows (figure from the network)
Commonly used, such as obtaining the width and height of the view,
width = getRight() - getLeft(); height = getBottom() - getTop ();
Of course, the system already has getWidth and getHeight methods, and their internal logic is the same
/** * Return the width of your view. * * @return The width of your view, in pixels. */ @ViewDebug.ExportedProperty(category = "layout") public final int getWidth() { return mRight - mLeft; }
Sliding event
In terms of sliding, like the UI written in other languages, android records the Down coordinate when clicking, and then records the UP coordinate after the finger slides, calculates the offset, and modifies the View coordinate through the offset. When the finger slides on the mobile phone, onTouchEvent event will be triggered. If you want to customize the operation, you can override this method
public boolean onTouchEvent(MotionEvent event) { switch (action) { case MotionEvent.ACTION_UP: case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_CANCEL://? case MotionEvent.ACTION_MOVE: }
The other three are easy to understand. What is the situation of ACTION_CANCEL?
For example, if you slide a View in a LineLayout, but it slides to an area outside the LineLayout, the View cannot go out. At this time, ACTION_CANCEL can be triggered. You can set it back to its original position or leave the View at the edge
There is also an onInterceptTouchEvent method in the View Group, coupled with various nested views in android, which is also confusing to many people. Here is the issue of event consumption.
event processing
Why is there this problem?
Just imagine, there must be a palm sized place on the mobile phone everywhere
When I click the TextView in the blue area, I actually click RelativeLayout and LinearLayout
So, how does android know which control to click?
The method is event interception. Clicking is an event. Which layer intercepts this event and executes the corresponding logic is an event consumption. If it is intercepted and does not execute the logic, it will be released and intercepted by other controls and recurs in turn
The above bold interception and execution correspond to the two methods onintercerttouchevent and onTouchEvent in the view
Obviously, the first intercepted view is the outermost view, LinearLayout
-
onIntercerptTouchEvent
If you override the onintercerttouchevent method on the outer LinearLayout, the return value is false, which means that it releases the event and enters the internal RelativeLayout. Similarly, the onintercerttouchevent method of which control returns true, which control wants to intercept the event.
Note that in order to be efficient, android intercepts only down incoming events (refer to different case s of onTouchEvent above). After confirming that the return value of onintercerttouchevent is true, the move, up, etc. of the intercepted event will be executed directly with down to the onTouchEvent of the current control
If the return value is false, it means that the current control releases this event, then move and down will stay in onintercerttouchevent of the current control and pass in the next intercepting control together.
Note that onintercerttouchevent is only available in View Group. The reason is very simple, because only it can nest views, and the default return value is false. Generally, ViewGoup does not easily handle events, but gives them to child views, which is also in line with our intuitive feeling of its "container".
-
onTouchEvent
Suppose it is finally transmitted to TextView, and he has no way to transmit it. Does he have to consume this event?
No, his onTouchEvent also has a return value. return false indicates that he is unwilling to consume this event. In this way, the packaged events (down, up, move, etc.) will be returned to RelativeLaout
As shown in the figure
In addition, there is also a dispatchTouchEvent() method, which is responsible for distributing events. Here alone, it is convenient for everyone to disassemble and understand. It is simpler. Although the above logic is closed-loop, one is still missing. How can the control be intercepted when the user clicks the control?
The dispatchTouchEvent contains an ontuchlistener, which is placed in the activity or framework. First, get the event, give it to the dispatchTouchEvent for unified management, and then distribute it to the onintercerttouchevent of the corresponding ViewGroup. You can follow the above logic. Since this article is more about understanding the principle, it does not do specific implementation.
draw
The above explanations serve this process and are scattered knowledge points. Next, we will string them together
The workflow of View is to measure, layout and draw through three methods respectively. If you want to customize the View, you need to rewrite it
measure
The measure in the View must measure the View itself, but if the View is a ViewGroup, it will traverse the views inside and call their own measure to measure themselves. This is a recursive process
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // Note the widthMeasureSpec and getDefaultSize here setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
There is only one line of source code here, that is, get the default width and height and measure it
I believe you must have used wrap_content is to make the control size just wrap the content. If you set the width and height as a fixed value in xml, you don't need measure, just because we set wrap_content or match_parent, the onMeasure() method of view will be called
-
match_parent
For match_parent, you only need to know the parent control of the current View and assign its Size to the current View, so we need to do two things: 1. Find the original ViewGroup control measurement, 2. Pass the measurement data down to the smallest View
-
wrap_content
wrap_content is the minimum value that just wraps the internal content, so on the contrary, it is to calculate the size of the child control
What is the role of widthMeasureSpec?
How does the View Group pass information to child views?
MeasureSpec class, which holds two data
- The specific size of the parent control of the child View
- The restriction type of the parent control on the child View
The first one is easy to understand. After all, match_ The parent is passed by this, and the child view cannot exceed this size
There are three types of restrictions for the second
private static final int MODE_SHIFT = 30; public static final int UNSPECIFIED = 0 << MODE_SHIFT; //Unlimited size public static final int EXACTLY = 1 << MODE_SHIFT; //For match_parent public static final int AT_MOST = 2 << MODE_SHIFT; //Represents wrap_content
So the whole measurement method is:
The parent layout first measures itself, then calls child.measure in its own onMeasure, and then the child View will comprehensively measure its own size according to the restriction information of the parent layout and its own content size, and then save the data through the setMeasuredDimension method,
wrap_content invalidation
There is also a getDefaultSize method in onMesure, which is the real method to obtain the size of the view
Let's see, interviewer, this is the answer you want!
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; // The specsize here represents match_parent break; } return result; }
As you can see, there are three default mode s, but at here_ Most and actual are treated as the same case, so why wrap_ What about content invalidation?
It's written in the note. The key is how specSize comes from?
Since the parent view and child view must be involved here, obviously, the answer is in ViewGroup. We found a getChildMeasureSpec method. Here I retain the English explanation, which is easier to understand. Let's see why others do this
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { switch (specMode) { //When the parent view is in the active mode, the match of the child view_ Parent and wrap_ The correspondence of content is ok 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 view at_ Match of sub view in most mode_ Parent and WRAP_CONTENT has become AT_MOST mode case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break;
Therefore, the core is that the child view cannot bigger than the parent view, so it is the at of the parent view by default_ Most, that is, the maximum space left
So why wrap_content invalid? Because it needs to fill the remaining maximum space of the parent view, it just matches the match of the child view_ Parent attribute effect
How to solve this problem?
In fact, it's very simple. No matter how complex the above things are, they are Default. We just need to customize our width and height in onMeasure in the custom View, and then write back through setMeasuredDimension
layout
layout is used to confirm the position of ViewGroup child elements,
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); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); ... }
As you can see, first initialize the left, top, bottom and right coordinates, and then set setFrame. When the four vertices are determined, the position of the view in its parent container is determined. Even if it is strange, it is locked in the rectangle composed of these four coordinates. Next, call the onLayout() method
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }
onLayout() in View is empty, which means that we need to rewrite it ourselves. We can see the rewriting in RelativeLayout
protected void onLayout(boolean changed, int l, int t, int r, int b) { // The layout has actually already been performed and the positions // cached. Apply the cached values to the children. final int count = getChildCount(); for (int i = 0; i < count; i++) { View child = getChildAt(i); if (child.getVisibility() != GONE) { RelativeLayout.LayoutParams st = (RelativeLayout.LayoutParams) child.getLayoutParams(); child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom); } } }
Here we get all the child controls and call the layout method of the child controls. Because RelativeLayout may nest other layouts, getLayoutParams here is used to obtain specific location parameters. Obviously, if you want to modify the location of the view, you can directly call setLayoutParams
To sum up, the Layout method determines its own coordinates, then calls onLayout and executes the child control Layout() method to get the coordinates of the child controls.
draw
Measure is to measure the size of the View, and layout is to determine the position of the View. Everything is ready. Only the View is drawn. There are six steps in the draw source code
/* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1\. Draw the background Draw background * 2\. If necessary, save the canvas' layers to prepare for fading * 3\. Draw view's content Draw content * 4\. Draw children Draw child controls * 5\. If necessary, draw the fading edges and restore layers * 6\. Draw decorations (scrollbars for instance) Draw decoration */
Steps 2 and 5 are layer related operations, but we generally don't use them in normal development, so we can skip them. In the draw() source code, the above steps correspond to the following source code
// Step 1, draw the background drawBackground(canvas); // Step 3, drawing content onDraw(canvas); // Step 4, drawing child controls dispatchDraw(canvas); // Step 6, drawing decoration onDrawForeground(canvas);
Of course, we don't need to rewrite all of our custom view s. Generally, we can draw the contents with onDraw() method, and others can use draw() by default. The simplest onDraw is to draw a circle and draw with Canvas
protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawCircle(width, height, radius, mPaint) }
The parameters passed in above only need to be self-contained, so that a View can be drawn. For more complex and interesting views, we will implement them in the user-defined View. Here we only need to understand the principle.
Summary
For View, we need to master its display, sliding, event handling mechanism and rendering mechanism, among which
The presentation needs to understand the representation of relative position and absolute position
Sliding and event handling need to understand the process of down, up and move events from user click to final consumption.
Drawing needs to understand how the view knows its width, height, position and image
last
Interviewer: I'll just ask wrap_content, why don't you start with Pangu?
Me: ah, I accidentally talked too much. In fact, there is another sliding conflict about the sliding event,
Interviewer: the meeting room I booked has exceeded the time limit. People outside will rush in and cut me. Go back and wait for the notice
In order to help you learn better, I have sorted out a learning roadmap and learning documents for Android system learning. You can look down. If you need to refer to the full version of the learning roadmap or materials, you can click the small card below to access them.