preface

It’s the end of the year and I’m going to put my Android knowledge in order.

Android Skill Tree series:

Android Basics

Android Skill Tree – Animation summary

Android Skill Tree – View summary

Android Skill Tree – Activity summary

Android Skill Tree – View event system summary

Android Skill Tree – Summary of Android storage paths and I/O operations

Android Skill Tree – Multi-process related summary

Android skill Tree – Drawable summary

Android Skill Tree – Fragment overview

Basic knowledge of data structures

Android Skill Tree – Array, linked list, hash table basic summary

Android Skill Tree — Basic Knowledge of The Tree

Basic knowledge of algorithms

Android Skill Tree – Sorting algorithm basics summary

This is the relative View to do a summary, mainly View working principle, drawing process, etc.. Why to summarize this, because usually custom View more or less will encounter, if you can deeply understand this knowledge, to master the custom View can be more thorough. Some of you might say well I won’t, I don’t have to read this summary article, it doesn’t matter, I wrote this very simple, basically everyone can understand. And after that, you should be able to write your own custom views and custom Viewgroups that aren’t too complicated.

PS: Non-advertising. I also learned the knowledge of View from other places before. I recommend this section (Android development art exploration and drop line View). So some knowledge points in the article will also reference these two places.

As shown in the figure below, I mainly sorted out the relevant knowledge:

Brain map download link

The View summary


We can look at the big categories:

We know that to draw a View well, there are three steps (I estimate 99.9 percent of people know these three steps) : measure, layout determine position, and then draw. So I will explain these three steps this time. And you might see an additional view wroot here, just to supplement the previous three steps.

ViewRoot (Supplementary knowledge)

Ps: If you do not look at the problem, it is not a big problem. If you do not want to understand, you can directly look at the three steps of measure, layout and draw in this paper.

ViewRoot literally doesn’t feel like the root of the entire ViewTree. Wrong! ViewRoot is not a View, its implementation class is ViewRootImpl, which is the link between the DecorView and WindowManager. So ViewRoot is more appropriately the “manager” of DecorView.

(PS: The next time an interviewer asks you what a view wroot is, don’t say it’s the root of a ViewTree. Ha ha.

So now that we’re starting to draw the whole interface. Obviously ViewRoot started calling methods, after all “manager”. So the View’s rendering process starts with the View wroot’s performTraversals method. So performTraversals method calls in turn performMeasure performLayout and performDraw three methods. Because all three methods and subsequent method calls are similar, let’s take performMeasure, which calls measure methods, The measure method calls the onMeasure method. And then call all the child View’s measure procedures in the onMeasure method.

We can see in the Mind map that the top-level View is a DecorView. What is a DecorView? DecorView is a FrameLayout that contains a vertical LinearLayout. This LinearLayout has two parts:

Do you see the familiar name Content? Yes. When we set the layout setContentView in the Activity, we add our layout to the FrameLayout with the ID android.r.D.C. Tent.

We now enter the View drawing process:

The size of the View

As you can see, I’ve written two real life scenarios and stories for you to understand.

Story comparison <1>

We can see that when we put our balloon in the cabinet, there are two factors that determine the size of the balloon: the limitation given by the cabinet and its own factors (good quality, good blowing can be very large). A MeasureSpec is a balloon-size View. A MeasureSpec is a balloon-size View. A MeasureSpec is a balloon-size View.

  1. The influence of ViewGroup
  2. Its LayoutParams

When measuring a View’s LayoutParams, the system transforms the View’s LayoutParams into a MeasureSpec based on the rules imposed by the parent container ViewGroup, and then measures the View’s height/width according to this MeasureSpec.

MeasureSpec is a MeasureSpec, and we’ll talk about it in a minute

MeasureSpec knowledge

In fact, if you look directly at the brain map, you should be able to understand it. The main points are as follows:

  1. MeasureSpec is a combination of SpecMode and SpecSize.
  2. The SpecMode type is UNSPECIFIED,EXACTLY, and AT_MOST.
  3. Whereas a normal View is a Parent container with its own LayoutParams to generate a MeasureSpec, a DecorView is a top-level View. We can imagine that the parent container is the screen directly outside the layer, so it is determined by the size of the screen and its own LayoutParams.

Comparison story <2>

Yes, by comparison, we can see that the rule is simple. Because we can use this balloon in our head to better understand the story.

Let me make a summary table :(to understand the above analysis process, not memorize the table, it is not fun to memorize)

The View of measurement

MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec So where exactly should we create MeasureSpec? And then constrain the child View?

The secret lies in the onMeasure() method we usually override:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}        
Copy the code

OnMeasure (int widthMeasureSpec, int heightMeasureSpec) MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec

After we get these two parameters, the View still doesn’t know what width and height we gave it. We should be sure to call the type last:view.setMeasureWidth(XX),view.setMeasureHeight(XX)In this way, it can be set to the measured width and height. That’s right,setMeasuredDimension(int measuredWidth, int measuredHeight)The method is what we use to set the measurement width and height of the View.

Of course, if I call this method to set the width and height of the view, THEN I don’t need MeasureSpec. For example:

@override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// No MeasureSpec is usedsetMeasuredDimension(100,100);
}
Copy the code

Yes, we can determine the width and height of the measurement without going through the normal measurement process, we just arbitrarily set the width and height to be 100. But this is not in accordance with the regular process. And it’s not going to be particularly good. For example, if you set match_parent,wrap_content,200dp to your view in XML, it will not work because the code ends up using 100.

Components of the onMeasure() method

As mentioned earlier, custom views override onMeasure() methods.

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); // We usually write our own code........ . . }Copy the code

We can see that it is mainly divided into two parts:

  1. super.onMeasure(),
  2. Write your own code.

Let’s take a step-by-step look at what this code does in different situations.

Inherit view.java directly

Super.onmeasure () 1: For example, our custom View directly inherits view.java:

public class DemoView extends View { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec,heightMeasureSpec); }}Copy the code

We can look at the super.onMeasure method:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(
        getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),      
        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)
    );
}
Copy the code

We see that the setMeasureDimension method is called to set the width and height.


PS: the next source code this analysis can not see, directly look at the conclusion. Hey hey. Hey hey. I know a lot of people don’t want to see it.

We can see that there are three main methods (we are looking at the width measurement here) :

  1. The getSuggestedMinimumWidth method got a value.
  2. The getDefaultSize method is used to process the constraint with the value obtained in the first step to get the final value.
  3. Assign our final value to the setMeasuredDimension method.

So instead of doing 1 and 2, we at least know. We finally determined the measurement size of a View using setMeasuredDimension (actually I feel like I’m talking nonsense, given the name of the method).

Let’s go back to the method in 1:

protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
Copy the code

If our View has no background, the minimum value returned is mMinWidth(what is mMinWidth?????) Which is the android:minWidth value we set in XML. . If we set the background, obtain mBackground getMinimumWidth () (actually, this method is to return to the original width of Drawable). Finally return to the Max (mMinWidth, mBackground getMinimumWidth ()) the maximum of both.

Let’s look at the method in 2 again:

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

The MeasureSpec creation rule should make sense if it does. With specMode as UNSPECIFIED, the value obtained from our method getSuggestedMinimumWidth in 1 is returned, and with AT_MOST and EXACTLY, specSize is returned. (The width creation rules here differ from the measurement rules in that, while specMode was UNSPECIFIED, we returned the getSuggestedMinimumWidth value, whereas we returned 0.)

Conclusion 1: If you write a custom View that directly inherits from the View and write super.measure(), then by default the View is given a measurement width and height (what is the width and height? If there is no set the background, it is inside the XML set android: minWidth/minHeight (this attribute is the default value is 0), if the background, background Drawable original height to width value and android: the minWidth/minHeight both supplied.

Inheriting existing controls

Super.onmeasure () analysis 2: For example, our custom View inherits an existing control such as imageView.java:

public class Image2View extends ImageView { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); }}Copy the code

Our super.onmeasure () method calls the onMeasure method in the ImageView:

@override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { . . . // Of course, the calculated width and height must be told to the ViewsetMeasuredDimension(widthSize, heightSize);
}
Copy the code

We found out that if our View directly inherits from the ImageView, the ImageView is already running a bunch of code that we’ve written to measure the width and height. We can just change on top of it.

For example, our Image2View is a custom square ImageView:

@override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {Override protected void onMeasure(int widthMeasureSpec, int heightMeasure)setThe MeasuredDimension method is assigned. super.onMeasure(widthMeasureSpec, heightMeasureSpec); // We use the getMeasuredWidth/Height column to get the measured width and Height that we have already measured // And then use the ImageView to measure the measured width and Height as the smaller side of the square. // Then call againsetMeasuredDimension is assigned to override the ImageView assignment. // We didn't do complex measurement operations from scratch, just rely on ImageView. Int measuredWidth = measuredWidth (); int height = getMeasuredHeight();if (width < height) {
        setMeasuredDimension(width, width);
    } else {
        setMeasuredDimension(height, height); }}Copy the code

Conclusion 2: If you write a custom View that inherits from an existing control and writes super.measure(), the default width of the existing control will be used. You can modify the width of the existing control, or you can remeasure it all and change it.

The front and back position of your own code with Super.Measure

Super.onmeasure () Analysis 3: The position relationship between our own code and super.Measure

As you can see, whether you inherit a View or an existing control (such as ImageView), super.onMeasure() will default to its own logic of measuring the width and height, and then call setMeasuredDimension() to assign the values.

  1. If we write our own code in front of super.measure, then the measurement logic you write will eventually be overridden again by setMeasuredDimension() in super.Measure if the width is measured and the value is assigned.
  2. If our own code is written after super.measure, you can make changes based on your inherited parent’s measurements (although it’s ok if you don’t use the parent’s measurements) and call it againsetMeasuredDimension()The assignment.
  3. If your measurement logic is not based on the measurements you inherited from the control, and it is entirely up to you to remeasure, super.onMeasure() will not be written.

Concrete implementation of custom View measurement

1. For example, we directly inherit the existing control, such as ImageView, and implement a square ImageView (mentioned above) :
public class Image2View extends ImageView { @Override protected void onMeasure(int widthMeasureSpec, Int heightMeasureSpec) {super.onMeasure() already calls the ImageView onMeasure() method. // So measurements have already been made. And called at the end of this methodsetMeasuredDimension(widthSize, heightSize); super.onMeasure(widthMeasureSpec, heightMeasureSpec); // So you don't write anything, the measurement is already determined, because it has already been executedsetMeasuredDimension. // But let's say you want the ImageView to be a square ImageView based on the ImageView. // Because the measured width and height may be different, it is a rectangle. We need to manually set the width and height again. int width = getMeasuredWidth(); Int height = getMaxHeight(); // Get the measured height in ImageView source codeif (width < height) {
            setMeasuredDimension(width, width);
        } else {
            setMeasuredDimension(height, height); }}}Copy the code

We found that after we had already measured the width for us with an existing control that we had inherited, we could change it again based on the already measured width. We don’t use what we learned about MeasureSpec, because super.onMeasure() handles MeasureSpec for us.

2. For example, we inherit View directly ourselves:
public class CircleView extends View { public CircleView(Context context) { super(context); } public CircleView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } @override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) Set the default value, wrap_content in the case of the value. // Since wrap_content only says no more than a certain maximum value, if you leave the default value unset, it has the same effect as Match_parent. int defaultWidthSize = 200; int defaultHeightSize = 200; // call resolveSize(); MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec widthMeasureSpec); defaultHeightSize = resolveSize(defaultHeightSize, heightMeasureSpec); / / callsetThe MeasuredDimension method assigns the width and heightsetMeasuredDimension(defaultWidthSize, defaultHeightSize); }}Copy the code

Super, super, super easy. What is the resolveSize() method?

ResolveSize () : resolveSize() Ha ha, does not affect the use.

We can take a look at its source:

public static int resolveSize(int size, int measureSpec) {
    returnresolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK; } public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { //1. SpecMode = MeasureSpec (MeasureSpec); final int specSize = MeasureSpec.getSize(measureSpec); final int result; Switch (specMode) {caseMeasureSpec.AT_MOST: /* 2.1 If the specMode is AT_MOST, we should just be specSize but it would be awkward if our default value is larger than our specSize. The default size of the balloon doesn't fit in the cabinet. In this case, the size of our View should be set to specSize. If the default size is smaller than our specSize, it doesn't matter. * /if (specSize < size) {
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break; /* EXACTLY, specSize */case MeasureSpec.EXACTLY:
            result = specSize;
            break; /* 2.3 With UNSPECIFIED mode, the default value */ is UNSPECIFIEDcase MeasureSpec.UNSPECIFIED:
        default:
            result = size;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}
Copy the code
3. Measurement of ViewGroup

A MeasureSpec of a View is passed as a measure of the onMeasure() method. We just used it. That’s where the onMeasure() method is called to help bring these two parameters in. Where are these two parameters generated?

The answer is what the parent of the child View gave it. The parent container generates childMeasureSpec in its own onMeasure() method based on the MeasureSpec passed in by its onMeasure() and the LayoutParams of the child View. Then call the child’s measure() passed in. (As mentioned earlier, the measure() method calls the onMeasure() method.)

For example, let’s write a ViewGroup with a circular layout.

public class CircleLayout extends ViewGroup { @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); WidthMeasureSpec and heightMeasureSpec are widthMeasureSpec and heightMeasureSpec. The LayoutParams of the child View //3. Generate a new MeasureSpec from both and feed it to the child View. //4. The resulting new ChildMeasureSpec rule is the one summarized in the previous table. /* the parent container may contain multiple child views, so the size given to the child should be the remaining space of the parent container. Therefore, the space of the parent container should be constantly reduced. The padding is also subtracted. I just want to show you how it works. */ // Determine the initial size of the parent container, because the parent container is also a View, so there are three steps. Int defaultWidthSize = 500; // Set the default value (0, because the parent container does not occupy space by default) int defaultHeightSize = 500; ResultWidthSize = resolveSize(defaultWidthSize, widthMeasureSpec); resultWidthSize = resolveSize(defaultWidthSize, widthMeasureSpec); int resultHeightSize = resolveSize(defaultHeightSize, heightMeasureSpec); // The parent View's MeasureSpec and the child View's LayoutParam are the parent View's MeasureSpec and child View's LayoutParam. Size int specMode = MeasureSpec. GetMode (widthMeasureSpec); int specSize = MeasureSpec.getSize(widthMeasureSpec); // create a new ChildMeasureSpec based on the different SpecMode and the child View's LayoutParams.for(int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); LayoutParams params = view.getLayoutParams(); int childWidthSpec, childHeightSpec; Switch (specMode) {// The parent View's MeasureSpec is as follows: switch (specMode) {caseMeasureSpec.EXACTLY: //if (params.width >= 0) {
                        resultSize = params.width;
                        resultMode = MeasureSpec.EXACTLY;
                    } else if (childDimension == LayoutParams.MATCH_PARENT) {
                        
                        resultSize = specSize;
                        resultMode = MeasureSpec.EXACTLY;
                    } else if (childDimension == LayoutParams.WRAP_CONTENT) {
              
                        resultSize = specSize;
                        resultMode = MeasureSpec.AT_MOST;
                    }
                
                    break;
                
                caseMeasureSpec.AT_MOST: ..... .break;
                    
                caseMeasureSpec.UNSPECIFIED: ..... .break; } childWidthSpec = MeasureSpec.makeMeasureSpec(resultWidthSize, MeasureSpec.EXACTLY); getChildAt(i).measure(childWidthSpec, childHeightSpec); } /* I don't want to write a custom ViewGroup. I'm going to give up, but don't worry, you can see that the rules are fixed. Is there a resolveSize method like the one we used to set our width and height above? If there are no specific requirements, we really don't need to write a large paragraph above. There are two ways. */ / Method 1: measureChildren(widthMeasureSpec, heightMeasureSpec) can be measured at once by calling measureChildren(); // Method 2: Measure one by one by measureChild().for(int i = 0; i < getChildCount(); i++) { View view = getChildAt(i); measureChild(view , widthMeasureSpec,heightMeasureSpec); } // Set the size of the parent containersetMeasuredDimension(XXXX,XXXX)
    
    }
}
Copy the code

Yes, and finally, measureChildren(widthMeasureSpec); And measureChild (view, widthMeasureSpec, heightMeasureSpec); Method, we also know that it must generate the corresponding childMeasure method according to the corresponding rules, and then call the child measure method.

We can take a look at the source code (PS: don’t want to see it or not, can skip) :

//measureChildren simply iterates through all views, calling the measureChild method separately for visible views. protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren;for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if((child.mViewFlags & VISIBILITY_MASK) ! = GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); ChildMeasureSpec = childMeasureSpec = childMeasureSpec = childMeasureSpec = childMeasureSpec = childMeasureSpec; It then passes to the child.measure method. protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }Copy the code

If you want to see exactly what getChildMeasureSpec does, go back to the source code, but the rules they generate are the same. I won’t go into that here.

Get the width and height of the View after measuring

This is pretty simple. Just look at the brain map.

The position of the View

This is the easy one, so I won’t talk about it. Don’t tease me. There are too many articles. Write too much and no one will read it.)

The View of the drawing

We all know that the size and position of the View are determined, but the painting is definitely missing.

The View of painting the draw ()

We all know that it is drawn using the draw() method.

Draw () : draw() : draw() : draw() : draw()

/* * Draw traversal performs several drawing stepswhich 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)
 */
Copy the code

First draw the background, then draw your own content, then draw the content of the child View, and finally draw the decoration and foreground.

I recommend you to see the article of throw line big guy, speak very clearly, I will not spend a lot of space to write the basis.

HenCoder Android custom View 1-5: Draw sequence

The use of Canvas

We know that no matter onDraw(Canvas Canvas),dispatchDraw(Canvas Canvas),onDrawForeground(Canvas Canvas), etc. are all parameters. So we know that Canvas is used for painting.

Here is also the recommendation of throwing line leader related articles, very detailed, I will not write a large length of basic knowledge.

HenCoder Android development advanced: custom View 1-1 drawing basis

HenCoder Android development advanced: custom View 1-4 Canvas for drawing assistance

How to use Canvas: It is mainly divided into two parts:

Canvas draws class methods

Canvas helper class methods

Geometric changes are divided into two-dimensional transformation and three-dimensional transformation:

2 d transformation

3 d transform

Paint related

We know Paint is a brush, we can set the color, the brush thickness and so on.

Continue to recommend the throw line leader related articles (I will not write the basic) :

HenCoder Android development advanced: custom View 1-2 Paint details

Color related

The effect

Draw text correlation

Paint initialization is partially related


conclusion

There are mistakes, please gently spray, I dare very small…