The basic flow of custom view

Sometimes we need some special effects or functions, but the system controls cannot meet our needs. At this time, we need to define a control ourselves.

Custom view process

Inherit View

To customize View, you first need to inherit View or its subclasses. If the effect you need to achieve is more complicated, you usually need to inherit View. Sometimes what we need is system controls plus some special effects, then you can inherit View subclasses ( such as TextView)

If you want to design a layout yourself or combine other controls, you need to inherit the layout that comes with ViewGroup or LinearLayout, FrameLayout and other systems

override constructor

Custom View needs to override at least two construction methods

  • The constructor used to directly create controls in java code
public CustomView(Context context) {
        this(context, null);
}
  • Define the constructor used by View in the xml file
public CustomView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
}

public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
}

Attributes in custom xml

First, you need to create a new res/values/custom_view_attrs.xml, and declare it as follows

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CustomView">
        <attr name="custom_width" format="dimension" />
    </declare-styleable>
</resources>

Then you can declare it in the xml layout file

<?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"
    tools:context="cn.lkllkllkl.customview.MainActivity">

    <cn.lkllkllkl.customview.CustomView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:custom_width="100dp"/>

</LinearLayout>

Then we can take out the value of this property and use it in the constructor of the custom View

public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr){
        super(context, attrs, defStyleAttr);
        mPaint = new Paint();
        float customWidth = 0;
        TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView);
        customWidth = typeArray.getDimension(R.styleable.CustomView_custom_width, 100);
        // 需要记得回收
        typeArray.recycle();
}

onMeasure method

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);    

MeasureSpec

To understand the two parameters of onMeasure here, you first need to understand the MeasureSpec class.

MeasureSpec packs SpecMode (mode) and SpecSize (specific size) into an int (ie widthMeasureSpec, heightMeasureSpec), the first 2 digits represent SpecMode, and the last 30 digits represent the size.

There are three types of SpecMode

  • UNSPECIFIED: The parent container has no limit on the size of the View. Generally, we use less custom Views

  • EXACTLY: Set the specific size in dp in the xml file, or set SpecMode to EXACTLY if it is set to match_parent. This mode means that SpecSize is directly used as the size of the custom View

  • AT_MOST: corresponds to wrap_content in the xml file, indicating that the set size cannot exceed SpecSize

Usually we only need to rewrite onMeasure when we need the view to support wrap_content. If not rewritten, the effect of wrap_content is the same as match_parent. Generally, the following code is enough

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

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        // dp转换成px, 事先定义的默认宽高单位为dp
        int defaultWidth = dp2px(getContext(), DEFAULT_WIDTH);
        int defaultHeight = dp2px(getContext(), DEFAULT_HEIGHT);

        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            widthSize = Math.min(defaultWidth, widthSize);
            heightSize = Math.min(defaultHeight, heightSize);
        } else if (widthMode == MeasureSpec.AT_MOST) {
            widthSize = Math.min(defaultWidth, widthSize);
        } else if (heightMode == MeasureSpec.AT_MOST){
            heightSize = Math.min(defaultHeight, heightSize);
        }
        
        setMeasuredDimension(widthSize, heightSize);
    }

Generally, in order to adapt to different screens, you need to use dp as the unit, and setMeasuredDimension uses px as the unit, so the above code converts the default width and height we set into px, and the conversion method is as follows

public static int dp2px(Context context, float dp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dp, context.getResources().getDisplayMetrics());
    }

onLayout method

Generally, custom ViewGroup needs to rewrite this method to discharge sub-Views

onDraw method

Through the onDraw method, we can draw the view to the screen. If you want the view to support the padding attribute, you need to do it in onDraw

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /* mPaint为成员变量,画笔Paint的对象,
         * 在构造函数中进行初始化,可以设置一些在canvas上绘制的通用属性
         */
        mPaint.setColor(Color.RED);
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        //在View所占区域绘制一条对角线
        canvas.drawLine(paddingLeft, paddingTop,
                getWidth() - paddingRight, getHeight() - paddingBottom, mPaint);
    }

Precautions

  • The three methods onMeasure, onLayout, and onDraw may call this, especially the onDraw method, so it is best not to create objects in these methods to avoid memory jitter caused by frequent memory allocation, or to do some time-consuming operations that cause frame skipping

  • There are post series methods in View, so there is no need to use Handler in View

  • If there is a creation thread or animation in View that needs to be stopped in time, View#onDetachedFromWindow is a good time

  • If there is sliding nesting in the View, the sliding conflict needs to be handled properly

reference

 

Guess you like

Origin blog.csdn.net/u013773608/article/details/130337550