The custom View

In the process of actual use, we often receive such requirements, such as circular pedometer, bar chart, circular avatar, etc. The common idea is to Google github to see if there is a control we need, but what if there is no such control online? In this case, we often need to customize the View to meet the requirements.


Let’s start down the path of custom controls

About custom control, general hui follows a few routines

  • Start by overriding the onMeasure() method
  • Next, rewrite the onDraw() method
  • Known as onMeasure()

Method is used to remeasure and resize the control. We know that the control is resized using the width and height tags. There are usually three types of assignment:

  • First, assign a value directly, such as the exact size of 15dp
  • Secondly match_parent
  • And of course wrap_parent

If you already have these properties, why rewrite the onMeasure? Just call the View method. But imagine if you designed a circular control, and the user set wrap_parent to width and height, and sent you a rectangular image. You have to “square” ah. So you need to rewrite the onMeasure method to set its width and height equal.


So how do you override the onMeasure() method?

First type the onMeasure()

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

At this time, you will wonder, it is clearly redraw the size, so give me the width and height of the line? Int widthMeasureSpec, int heightMeasureSpec, int widthMeasureSpec It’s easy to understand. We all know that data in a computer is stored in binary. And, as I said before, there are three ways to assign the size of a View, so how many bits do you need to store binary numbers on a computer? The answer is clear -> two. And as you can see, both of these arguments are int. Int data is stored in 32 bits on a computer. So Google was smart enough to divide the 30 into two parts. The first two bits store the type, and the next 28 bits store the size.


Start overwriting the onMeasure() method

First of all, we need to determine the type and then calculate the size, so let’s write a method to calculate and return the size.

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
   private int getMySize(int defaultSize, int measureSpec) {
        // set a defaultSize defaultSize
        int mySize = defaultSize;
        // Get the type
        int mode = MeasureSpec.getMode(measureSpec);
        // Get the size
        int size = MeasureSpec.getSize(measureSpec);
        
        switch (mode) {
            case MeasureSpec.UNSPECIFIED: {// If no size is specified, the default size is used
                mySize = defaultSize;
                break;
            }
            case MeasureSpec.AT_MOST: {// If the measurement mode is size
                // We will take the maximum size, you can take other values
                mySize = size;
                break;
            }
            case MeasureSpec.EXACTLY: {// If the size is fixed, do not change it
                mySize = size;
                break; }}return mySize;
    }
Copy the code

Then we call it from onMeasure()

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // Get the length and width respectively
        int width = getMySize(100, widthMeasureSpec);
        int height = getMySize(100, heightMeasureSpec);
 
        // Here I have a circular control example
        // So the length and width are equal
        if (width < height) {
            height = width;
        } else {
            width = height;
        }
        // Set the size
        setMeasuredDimension(width, height);
    }
Copy the code

Apply trial effects in XML

<?xml version="1.0" encoding="utf-8"? >
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    tools:context=".activities.MainActivity">
 
    <com.entry.android_view_user_defined_first.views.MyView
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:default_size="@drawable/ic_launcher_background"/>
 
</LinearLayout>
Copy the code

So now that I’ve redrawn it, let’s run it for a second

We were shocked, said the good control? ! Don’t worry, we haven’t painted it yet, so it’s transparent. So I’m going to rewrite the onDraw() method, in the onDraw() method. (I’m going to create a new object in the onDraw() method for the sake of writing.

We draw the graphics on canvas.

    @Override
    protected void onDraw(Canvas canvas) {
        // Call the parent View's onDraw function, because the View class does something for us
        // Basic drawing functions, such as drawing background colors, background images, etc
        super.onDraw(canvas);
        int r = getMeasuredWidth() / 2;// It can also be getMeasuredHeight()/2, in this case we have set the width and height to be equal
        Log.d(TAG, r + "");
        // The abscissa of the center of the circle is the starting left position of the current View + the radius
        int centerX = r;
        // The vertical coordinate of the center of the circle is the top starting position of the current View + the radius
        int centerY = r;
        // Define a gray brush and draw a circle
        Paint bacPaint = new Paint();
        bacPaint.setColor(Color.GRAY);
        canvas.drawCircle(centerX, centerY, r, bacPaint);
        // Define the blue brush and draw the text
        Paint paint = new Paint();
        paint.setColor(Color.BLUE);
        paint.setTextSize(60);
        canvas.drawText("Greater fool".0, r+paint.getTextSize()/2, paint);
    }
Copy the code

Run the

And you’re done! But thinking may find: use this way, we can only use the parent class controls the attributes, but sometimes we need more features, such as: image controls need to change the transparency, card controls need to set the shadow value and so on, then the properties of the parent class controls obviously not enough, then we will begin to implement a custom layout.

Custom layout attributes XML attributes


start

Since custom layout properties generally only need to be operated on onDraw(). So onMeasure() and other methods to rewrite I will not be verbose, here I am going to inherit the word view to implement a textView-like control.

First, let’s now add a custom layout property to the res/values/styles file.

<resources>
 
    <! -- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <! -- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
 
    <! Define the attribute collection name -->
    <declare-styleable name="MyView">
        <! -- we define default_size property as index type pixel dp etc -->
        <attr name="text_size" format="dimension"/>
        <attr name="text_color" format="color"/>
        <attr name="text_text" format="string"/>
    </declare-styleable>
 
</resources>
Copy the code

What do these labels mean?

First of all:

MyView is the name of a custom layout properties, is also the label is also the entrance, in ontouch, use the context. ObtainStyledAttributes (attrs, R.s tyleable. MyView); Gets all the children of the custom layout property.

Second:

The name in attr is the name of your property, such as text_size, text_color, and text_text.

    <com.entry.android_view_user_defined_first.views.MyView
        android:layout_width="100dp"
        android:layout_height="100dp"
        app:text_text="hello world"
        app:text_size="20sp"
        app:text_color="@color/colorAccent"/>
Copy the code

Finally:

The format TAB, the format tag specifies the data type, specific can see this article, I will not repeat here – > blog.csdn.net/pgalxx/arti…


Parsing and referencing

We defined the property and assigned it a value in the layout, so how do we get its actual value in the custom control? Let’s first write down the constructor, where we get the size of these values:

    private int textSize;
    private String textText;
    private int textColor;
 
    public MyView(Context context) {
        super(context);
    }
 
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
 
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyView);
 
        textSize = array.getDimensionPixelSize(R.styleable.MyView_text_size, 15);
        textText = array.getString(R.styleable.MyView_text_text);
        textColor = array.getColor(R.styleable.MyView_text_color,Color.BLACK);
 
        array.recycle();
    }
Copy the code
  • Creates a TypeArray object that stores the values passed in for custom properties. The obtainStyledAttributes method takes two more parameters. The second parameter is the tag in the styles.xml file, the tag of the attribute collection, which is called R.styleable+name in the R file
  • We then get the value passed in, based on the array object. The first argument is the property in the property collection, R file name: r.styleable + property collection name + underscore + property name, and the second argument is the default value if the property is not set
  • Finally, remember to recycle the TypedArray object

Let’s rewrite the onDraw() method.

Since in the constructor we already have the basic values, so in onDraw() we can just draw these things and go straight to the code:

    @Override
    protected void onDraw(Canvas canvas) {
        // Call the parent View's onDraw function, because the View class does something for us
        // Basic drawing functions, such as drawing background colors, background images, etc
        super.onDraw(canvas);
        int r = getMeasuredWidth() / 2;// It can also be getMeasuredHeight()/2, in this case we have set the width and height to be equal
        // The abscissa of the center of the circle is the starting left position of the current View + the radius
        int centerX = r;
        // The vertical coordinate of the center of the circle is the top starting position of the current View + the radius
        int centerY = r;
        // Define a gray brush and draw a circle
        Paint bacPaint = new Paint();
        bacPaint.setColor(Color.GRAY);
        canvas.drawCircle(centerX, centerY, r, bacPaint);
        // Define the blue brush and draw the text
        Paint paint = new Paint();
        paint.setColor(textColor);
        paint.setTextSize(textSize);
        canvas.drawText(textText, 0, r+paint.getTextSize()/2, paint);
    }
Copy the code

Run it: Perfect!

Write in the last

This article is a summary of my personal learning process. If you find any mistakes, please point them out in the comments section. Thank you 🙏