My simple book synchronous release: custom View, have this one is enough

In order to eliminate blind spots in learning, as much as possible coverage of the Android knowledge of fringes, decided to a custom View to be a slightly more comprehensive usage summary, above content is not, what are the unique place, other basically have great god the blog in this aspect of the content, if you to customize the View very well, Then you don’t have to look down. If you are not familiar with custom View, or you have forgotten a lot of content and want to review it, or you have never used it before, welcome to review this knowledge with me, maybe my blog is more in line with your appetite…

First of all, why should WE customize the View? The main reason is that the built-in View of Android system cannot meet our requirements. We need to customize the View we want according to our business needs. Most of the time we just need to rewrite two functions: onMeasure() and onDraw(). OnMeasure is responsible for measuring the size of the current View and onDraw is responsible for drawing the current View. Of course, you have to write at least two constructors:

    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs); 
    }Copy the code

1.1. OnMeasure

For our custom View, we first have to measure the width and height. Why measure width and height? When I first learned to customize View, I couldn’t understand it! Because I thought, I already specified the width and height in the XML file, do I need to get the width and height again in my custom View and set the width and height? Since my custom View is inherited from the View class, the Google team directly in the View class directly get the width and height of the XML Settings, and set in the good? So why does Google make us do this “repetitive work”? Don’t worry, I’ll bring you tea at once

In Android, we learned that in XML layout files, we can write wrap_content or match_parent instead of layout_width and layout_height. The meaning, as we all know, is to set the size to “cover the content” and “fill all the space the parent layout gives us.” These two Settings do not specify the actual size, but the View we draw on the screen must have a specific width and height, and for this reason we have to handle and set the size ourselves. Of course, the View class provides the default processing, but if the default processing of the View class does not meet our requirements, we will have to rewrite the onMeasure function. For example, if we want our View to be a square, if we specify wrap_content as the width and height in XML, if we use the measure method provided by the View class, it will not meet our requirements.

Take a look at the onMeasure function prototype:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) Copy the code

WidthMeasureSpec and heightMeasureSpec are widthMeasureSpec and heightMeasureSpec. It looks a lot like width and height, and yes, those are the parameters that contain the width and height. What? Contain? Do you need more information? Yes! It also contains the measurement mode, that is, an int integer that contains the measurement mode and size. So how do you put two pieces of information in one number? We know that we have three options for setting the width and height: wrap_content, match_parent, and specifying a fixed size, and there are three measurement modes: UNSPECIFIED, EXACTLY, and AT_MOST, of course, are not mutually exclusive. I’ll cover the three modes in detail later, but the measurement mode is still UNSPECIFIED. With binary, we only need two bits. Because two bits are in the range [0,3] and there’s enough room for four bits. So how does Google put an int with both measurement mode and size information? We know that ints take up 32 bits, but what Google does is to use the first two bits of ints to distinguish between different layout patterns, and the last 30 bits to store size data.

So how do we extract the measurement mode and size from int data? The Android built-in MeasureSpec class MeasureSpec does not need to write the shift << and fetch & operation every time.

int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);Copy the code

Why do we measure patterns when we can get width dimensions from widthMeasureSpec? Would the measurement model be redundant? Note: The size here is not the final size of our View, but the reference size provided by the parent View. Let’s look at the measurement model. What does the measurement model do?

Measurement model Said mean
UNSPECIFIED The parent container has no restrictions on the current View, which can take any size
EXACTLY The current size is the size that the current View should take
AT_MOST The current size is the maximum size that the current View can take

What is the relationship between the above measurement mode and wrap_content, match_parent and fixed size in our layout?

Match_parent – > EXACTLY. How do you understand that? Match_parent is using all the free space that the parent View gives us, and the free space that the parent View gives us is determined, which is the size of the integer in this measurement mode.

Wrap_content – > AT_MOST. We want to set the size to wrap our view content, so the size is the parent view gives us as a reference size, as long as it does not exceed this size, the specific size according to our needs to set.

Fixed size (e.g. 100DP) — >EXACTLY. Users specify their own size, we do not have to interfere, of course, with the specified size mainly.

1.2. Rewrite the onMeasure function

This is a lot of theory, but let’s see how onMeasure works. Suppose we want to display the current View as a square with the same width and height, and the default width and height is 100 pixels. You can write like this:

 private int getMySize(int defaultSize, int measureSpec) {
        int mySize = defaultSize;

        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);

        switch (mode) {
            case MeasureSpec.UNSPECIFIED: {
                mySize = defaultSize;
                break;
            }
            case MeasureSpec.AT_MOST: {
                
                mySize = size;
                break;
            }
            case MeasureSpec.EXACTLY: {
                mySize = size;
                break;
            }
        }
        return mySize;
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = getMySize(100, widthMeasureSpec);
        int height = getMySize(100, heightMeasureSpec);

        if (width < height) {
            height = width;
        } else {
            width = height;
        }

        setMeasuredDimension(width, height);
}
Copy the code

Let’s set up the layout

  
Copy the code

See the effect of using our own onMeasure function:

If we did not override onMeasure, the effect would be as follows:

1.3. Rewrite ontouch

It is easy to draw the desired effect. We can draw the desired effect directly on the Canvas. It is too simple. Suppose we need to implement that our View displays a circle. We have already implemented the same width, height and size above, and proceed:

  @Override
    protected void onDraw(Canvas canvas) {
        
        
        super.onDraw(canvas);
        int r = getMeasuredWidth() / 2;
        
        int centerX = getLeft() + r;
        
        int centerY = getTop() + r;

        Paint paint = new Paint();
        paint.setColor(Color.GREEN);
        
        canvas.drawCircle(centerX, centerY, r, paint);


    }
Copy the code

1.4. Customize layout properties

If there are properties that we want to specify by the user, and we use hard-coded values only when the user doesn’t specify them, such as the default size above, what if we want the user to specify them in the layout file? That is of course through our custom properties, let the user use our defined properties

First we need to declare a custom property in the res/values/styles. XML file (if not, please create your own) :



    
    
        
        
    

Copy the code

The next step is to use our custom properties in the layout file




    

        

        

        Copy the code


    


Copy the code

Note: need to be set inside the root tag (LinearLayout) namespace, the namespace name can literally take, such as hc, values are fixed behind the namespace: “http://schemas.android.com/apk/res-auto”

And then the last thing we do is we pull out the value of our custom property in our custom View, and in the constructor, remember there’s an AttributeSet property? It is used to extract properties from the layout:

 private int defalutSize;
  public MyView(Context context, AttributeSet attrs) {
      super(context, attrs);
      
        
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView);

        
        
        defalutSize = a.getDimensionPixelSize(R.styleable.MyView_default_size, 100);

        
        a.recycle();
   }

Copy the code

Finally, attach the complete code for MyView:

package com.hc.studyview; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; /** * Package com.hc.studyview * Created by HuaChao on 2016/6/3. */ public class MyView extends View { private int defalutSize; public MyView(Context context) { super(context); } public MyView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView); defalutSize = a.getDimensionPixelSize(R.styleable.MyView_default_size, 100); a.recycle(); } private int getMySize(int defaultSize, int measureSpec) { int mySize = defaultSize; int mode = MeasureSpec.getMode(measureSpec); int size = MeasureSpec.getSize(measureSpec); switch (mode) { case MeasureSpec.UNSPECIFIED: { mySize = defaultSize; break; } case MeasureSpec.AT_MOST: { mySize = size; break; } case MeasureSpec.EXACTLY: { mySize = size; break; } } return mySize; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int width = getMySize(defalutSize, widthMeasureSpec); int height = getMySize(defalutSize, heightMeasureSpec); if (width < height) { height = width; } else { width = height; } setMeasuredDimension(width, height); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int r = getMeasuredWidth() / 2; int centerX = getLeft() + r; int centerY = getTop() + r; Paint paint = new Paint(); paint.setColor(Color.GREEN); canvas.drawCircle(centerX, centerY, r, paint); }}Copy the code

The process of customizing a View is very simple, but the process of customizing a ViewGroup is not so simple ~ because it not only needs to manage its own, but also take care of its child views. We all know that a ViewGroup is a View container that holds the Child View and is responsible for putting the Child View into the specified location. Imagine for a moment, if you were in charge of designing a ViewGroup, how would you design it?

1. First of all, we need to know the size of each child View. Only when we know the size of each child View, we know how big the current ViewGroup should be set to accommodate them.

2. Determine the size of the ViewGroup according to the size of the subview and the function our ViewGroup wants to achieve

3.ViewGroup and subview size calculated, the next is to put it, specific how to put it? It’s up to you to customize it. For example, if you want the child views to be placed next to each other in vertical order, or one on top of the other in chronological order, it’s up to you.

4 already know how to put ah, decided how to put is equivalent to the existing space “divided” into large and small space, each space corresponding to a sub-view, we next is to put the sub-view into the seat, put them in their place.

Now that we have completed the design of the ViewGroup, let’s take a concrete example: put the sub-views side by side in a vertical order from top to bottom, which mimics the vertical layout of the LinearLayout.

First rewrite onMeasure to measure sub-view size and set ViewGroup size:

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); measureChildren(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int childCount = getChildCount(); if (childCount == 0) { setMeasuredDimension(0, 0); } else { if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { int height = getTotleHeight(); int width = getMaxChildWidth(); setMeasuredDimension(width, height); } else if (heightMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSize, getTotleHeight()); } else if (widthMode == MeasureSpec.AT_MOST) { setMeasuredDimension(getMaxChildWidth(), heightSize); */ private int getMaxChildWidth() {int childCount = getChildCount(); int maxWidth = 0; for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); if (childView.getMeasuredWidth() > maxWidth) maxWidth = childView.getMeasuredWidth(); } return maxWidth; **/ private int getTotleHeight() {int childCount = getChildCount(); int height = 0; for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); height += childView.getMeasuredHeight(); } return height; }Copy the code

The comments in the code have been written in detail and I won’t go through each line of code. The onMeasure above has measured the sub-view and set its own size. Now let’s put the sub-view ~

@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); int curHeight = t; for (int i = 0; i < count; i++) { View child = getChildAt(i); int height = child.getMeasuredHeight(); int width = child.getMeasuredWidth(); child.layout(l, curHeight, l + width, curHeight + height); curHeight += height; }}Copy the code

Set the width and height of our ViewGroup to wrap_content. Add a background to the ViewGroup to make it more visible:




    

        

        

        


    



Copy the code

Take a look at the final result

We can also achieve the effect of the LinearLayout ~~~~

MyViewGroup MyViewGroup MyViewGroup

package com.hc.studyview; import android.content.Context; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; /** * Package com.hc.studyview * Created by HuaChao on 2016/6/3. */ public class MyViewGroup extends ViewGroup { public MyViewGroup(Context context) { super(context); } public MyViewGroup(Context context, AttributeSet attrs) { super(context, attrs); */ private int getMaxChildWidth() {int childCount = getChildCount(); int maxWidth = 0; for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); if (childView.getMeasuredWidth() > maxWidth) maxWidth = childView.getMeasuredWidth(); } return maxWidth; **/ private int getTotleHeight() {int childCount = getChildCount(); int height = 0; for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); height += childView.getMeasuredHeight(); } return height; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); measureChildren(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int childCount = getChildCount(); if (childCount == 0) { setMeasuredDimension(0, 0); } else { if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) { int height = getTotleHeight(); int width = getMaxChildWidth(); setMeasuredDimension(width, height); } else if (heightMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSize, getTotleHeight()); } else if (widthMode == MeasureSpec.AT_MOST) { setMeasuredDimension(getMaxChildWidth(), heightSize); } } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int count = getChildCount(); int curHeight = t; for (int i = 0; i < count; i++) { View child = getChildAt(i); int height = child.getMeasuredHeight(); int width = child.getMeasuredWidth(); child.layout(l, curHeight, l + width, curHeight + height); curHeight += height; }}}Copy the code

This is the end of the study of custom View. Is it so easy to find a custom View?