Custom View for Android development

Table of contents

1. Introduction to View

1.1 View's constructor

1.2 View drawing flow chart

Two, custom View

2.1 onMeasure() method

2.2 OnDraw() method


1. Introduction to View

The View class is the base class of various components in Android. For example, View is the base class of ViewGroup, which is represented as various views displayed on the screen. UI components in Android are composed of View and ViewGroup.

1.1 View's constructor

    //如果View在Java代码中是new出来的,就会调用第一个构造函数
    public View(Context context) {
        throw new RuntimeException("Stub!");
    }

    //如果View是在.xml里面声明的就会调用第二个构造函数
    public View(Context context, @Nullable AttributeSet attrs) {
        throw new RuntimeException("Stub!");
    }
    
    
    public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        throw new RuntimeException("Stub!");
    }

    public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int         defStyleRes) {
        throw new RuntimeException("Stub!");
    }

AttributeSet and custom attributes: The View that comes with the system can configure attributes in xml. For the custom View that has been written, attributes can also be configured in xml. In order to make the attributes of custom Views configurable in xml, you need to Four steps:

  1. Add attributes to custom View via <declare-styleable>
  2. In the xml for the corresponding attribute life attribute value
  3. Get property value at runtime
  4. Apply the obtained attribute value to the View

1.2 View drawing flow chart

Two, custom View

The most basic way to customize View is:

onMeasure(): Measure to determine the size of the View;

onLayout(): Layout, determines the position of the View in the ViewGroup

onDraw(): Draw, decide to draw this View;

2.1 onMeasure() method

 @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
...
}

First look at the two parameters of the onMeasure function, widthMeasureSpec and heightMeasureSpec. The two int data contains measurement mode and size.

//获取测量模式        
int modeWidth = MeasureSpec.getMode(widthMeasureSpec);
int modeHeight = MeasureSpec.getMode(heightMeasureSpec);
//获取尺寸
int sizeWidth = MeasureSpec.getSize(widthMeasureSpec);
int sizeHeight = MeasureSpec.getSize(heightMeasureSpec);

What is measurement mode? There are three measurement modes, namely UNSPECIFIED/ EXCATLY/ AT_MOST.

measurement mode express meaning
UNSPECIFIED The parent container does not have any restrictions on the current View, and the current View can be of 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

Now rewrite an onMeasure function to implement a custom square View.

//继承View
public class SquareView extends View {

    //1.只有Context的构造函数
    public SquareView(Context context) {
        super(context);
    }

    //2.含有Context和AttributeSet的构造函数
    public SquareView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    //3.重写onMesure方法
    @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);
    }

    //根据测量模式
    private int getMySize(int defaultSize, int measureSpec) {
        int mSize = defaultSize;
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
        switch (mode){
            //父容器没有对当前View有任何限制,当前View可以任意取尺寸
            case MeasureSpec.UNSPECIFIED:
                mSize = defaultSize;
                break;
            //View能取得的最大尺寸
            case MeasureSpec.AT_MOST:
                mSize = size;
                break;
            //当前的尺寸就是当前View应该取的尺寸
            case MeasureSpec.EXACTLY:
                mSize = size;
                break;
        }
        return mSize;
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="15sp"
        android:text="自定义View_SquareView"
        android:textSize="30sp" />

    <com.example.appb.SquareView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_margin="20dp"
        android:background="@color/purple_200" />
    
    <com.example.appb.SquareView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="20dp"
        android:background="@color/teal_200" />

</LinearLayout>

running result:

        Tips: When customizing View, two constructors are required. Otherwise, an exception will be reported during compilation: Binary XML file line Error inflating class. The reason is: Android will call the double-parameter construction method of View when creating a View object based on the xml folder, that is, public SquareView(Context context, AttributeSetattrs), So if it is not written in full, an error will be reported.

2.2 OnDraw() method

The custom size is implemented in the onMeasure method, and the custom drawing View is implemented in the onDraw method. Next make a custom circular View.

  @Override
    protected void onDraw(Canvas canvas) {
        //调用父类的onDraw函数,因为View这个类实现了一些基本的绘制功能,比如绘制背景颜色和背景图片
        super.onDraw(canvas);
        //半径
        int r = getMeasuredWidth()/2;
        //以圆心的横坐标为当前View的左起始位置+半径
        int centerX = getLeft() + r;
        //以圆心的横坐标为当前View的顶部起始位置+半径
        int centerY = getTop() + r;
        Paint paint = new Paint();
        paint.setColor(Color.YELLOW);
        canvas.drawCircle(centerX,centerY,r,paint);
    }
 <com.example.appb.CycloView
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        />

running result:

 2.3 Custom layout properties

First, you need to declare a custom attribute in the res/values/styles.xml file:

<resources>
    <!--声明属性集合的名称-->
    <declare-styleable name="CycloView">
        <!--声明属性名称,和取值类型-->
        <attr name="default_size" format="dimension"/>
    </declare-styleable>
</resources>

layout file:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:hc="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.example.appb.CycloView
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        hc:default_size="100dp"
        />

</LinearLayout>

Tips : To use custom attributes, you need to add a namespace to the root tag. The value of the namespace is

"http://schemas.android.com/apk/res-auto"

Modify the constructor to read the value of the custom property:

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.view.View;

import androidx.annotation.Nullable;

public class CycloView extends View {

    private int defaultSize;
    public CycloView(Context context) {
        super(context);
    }

    public CycloView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        //获取属性集合
        TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.CycloView);
        //获取default_size属性
        defaultSize = typedArray.getDimensionPixelSize(R.styleable.CycloView_default_size,100);
        //将TypedArray回收
        typedArray.recycle();
    }
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width = getMySize(defaultSize,widthMeasureSpec);
        int height = getMySize(defaultSize,heightMeasureSpec);

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

    private int getMySize(int defaultSize, int measureSpec) {
        int mSize = defaultSize;
        int mode = MeasureSpec.getMode(measureSpec);
        int size = MeasureSpec.getSize(measureSpec);
        switch (mode){
            case MeasureSpec.UNSPECIFIED:
                mSize = defaultSize;
                break;
            case MeasureSpec.AT_MOST:
                mSize = size;
                break;
            case MeasureSpec.EXACTLY:
                mSize = size;
                break;
        }
        return mSize;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //调用父类的onDraw函数,因为View这个类实现了一些基本的绘制功能,比如绘制背景颜色和背景图片
        super.onDraw(canvas);
        //半径
        int r = getMeasuredWidth()/2;
        //以圆心的横坐标为当前View的左起始位置+半径
        int centerX = getLeft() + r;
        //以圆心的横坐标为当前View的顶部起始位置+半径
        int centerY = getTop() + r;
        Paint paint = new Paint();
        paint.setColor(Color.YELLOW);
        canvas.drawCircle(centerX,centerY,r,paint);
    }
}

Reference documents:

Custom View (1) - Short Book (jianshu.com)

(3 messages) Custom View, this article is enough

Guess you like

Origin blog.csdn.net/weixin_43858011/article/details/125102763