Exploration of Android art development - 4 Custom View

Posted by fezzik on Mon, 07 Mar 2022 18:06:15 +0100

1. Customize the classification of View

1. Inherit View and override onDraw method
It is mainly used to realize some irregular effects. This effect is not convenient to be achieved by layout combination. It usually needs to display some irregular graphics statically or dynamically, draw them, and support wrap_content, and supports padding.
2. Inherit the special Layout derived from ViewGroup
This method is mainly used to realize user-defined layout. This method can be adopted when some effect looks like the combination of several VEIWS. This method is more complicated, so it is necessary to properly handle the measurement and layout of ViewGroup and the measurement and layout of sub elements at the same time.
3. Inherit specific View
For example, TextView is a common method, which is generally used to extend the existing View functions, such as TextView. This method is easy to implement and does not need to support wrap_content and padding.
4. Inherit specific ViewGroup
Such as LinearLayout, this kind of square hair is also common. When a certain effect looks like several views combined together, this method can be implemented. This method does not need to deal with the two processes of measurement and layout. Generally speaking, the effect that can be achieved by method 2 can also be achieved by method 4. The difference is that method 2 is closer to the bottom layer.

2. User defined View notes

1. Let View support wrap_content
If the control of measure() does not inherit directly from the control of measure() or measure()_ If content is specially processed, wrap_ The content attribute will become invalid

2. If necessary, let the View support padding
For a control that directly inherits View, if padding is not handled in the draw method, the padding property cannot work.
For controls directly inherited from ViewGroup, consider the impact of padding and the margin of child elements in onMeasure and onLayout.

3. Try not to use Handler in View
The View itself provides post series methods

4. If there are threads or animations in view that need to be stopped in time, refer to View#onDetachedFromWindow
It is mainly aimed at the situation that the View contains threads or animation: when the View exits or is invisible, remember to stop the threads and animation contained in the View in time, otherwise it will cause memory leakage.
How to start or stop threads / Animation:
Start thread / Animation: use View Onattachedtowindow(), because the method is called when the Activity containing View starts
Stop thread / Animation: use View Ondetachedfromwindow(), because the method is called when the Activity containing the View exits or the current View is remove d.

5. When the view has sliding nesting, handle the sliding conflict

3. Example of custom View

3.1 inherit View and override onDraw method

To achieve some irregular effects, override the onDraw method. You need to support wrap yourself_ Content and padding should also be handled by yourself
1.CircleView inherits View and overrides onDraw method

public class CircleView extends View {
    private int mColor = Color.RED;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    public CircleView(Context context){
        super(context);
        init();
    }

    public CircleView(Context context, AttributeSet attrs){
        super(context,attrs);
        init();
    }

    public CircleView(Context context,AttributeSet attrs,int defStyleAttr){
        super(context,attrs,defStyleAttr);
        init();
    }

    private void init(){
        mPaint.setColor(mColor);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        int radius = Math.min(width,height)/2;
        canvas.drawCircle(width/2,height/2,radius,mPaint);
    }
}

2. Layout documents

<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:background="#ffffff"
    android:orientation="vertical"
    tools:context="com.example.viewtest.MainActivity">

    <com.example.viewtest.CircleView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:id="@+id/circleView1"
        android:background="#000000"/>


</LinearLayout>

result:

Modify margin

    <com.example.viewtest.CircleView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_margin="20dp"
        android:id="@+id/circleView1"
        android:background="#000000"/>


The margin attribute is controlled by the parent container and takes effect here
Then adjust the layout parameters of CircleView and set 20dp padding:

    <com.example.viewtest.CircleView
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:layout_margin="20dp"
        android:padding="20dp"
        android:id="@+id/circleView1"
        android:background="#000000"/>


There is no change. padding cannot take effect by default
Then set the width to wrap_content

    <com.example.viewtest.CircleView
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:layout_margin="20dp"
        android:padding="20dp"
        android:id="@+id/circleView1"
        android:background="#000000"/>


No change
Controls directly inherited from View, without special processing, use wrap_content is equivalent to using match_parent

terms of settlement:
1. Support padding attribute

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        int width = getWidth() - paddingLeft - paddingRight;
        int height = getHeight() - paddingTop - paddingBottom;
        int radius = Math.min(width,height)/2;
        canvas.drawCircle(paddingLeft+width/2,paddingTop+height/2,radius,mPaint);
    }

2. Support wrap_content, override the onMeasure method and set wrap_ Default length of content

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        if(widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(200,200);
        }else if(widthSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(200,heightMeasureSpec);
        }else if(heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthMeasureSpec,200);
        }
    }


3. Provide custom attributes
a. In the values directory, create XML that defines attributes, such as attrs xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="CircleView">
        <attr name="circle_color" format="color"/>
    </declare-styleable>
</resources>

b. Resolve the value of the custom attribute in the construction method of View and handle it accordingly
(in this example, the value of the attribute circle_color is to be parsed)

    public CircleView(Context context,AttributeSet attrs,int defStyleAttr){
        super(context,attrs,defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        mColor = a.getColor(R.styleable.CircleView_circle_color,Color.RED);
        a.recycle();
        init();
    }

First load the custom attribute collection CircleView
Then parse the circle in the CircleView attribute set_ Color attribute
After resolving the custom attribute, release the resource through the recycle method

c. Use custom attributes in layout files

<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:background="#ffffff"
    android:orientation="vertical"
    tools:context="com.example.viewtest.MainActivity">

    <com.example.viewtest.CircleView
        android:id="@+id/circleView"
        android:layout_width="wrap_content"
        android:layout_height="100dp"
        android:layout_margin="20dp"
        android:padding="20dp"
        app:circle_color="#03A9F4"
        android:background="#000000"/>


</LinearLayout>

design sketch

Full code:

public class CircleView extends View {
    private int mColor = Color.RED;
    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

    public CircleView(Context context) {
        super(context);
        init();
    }

    public CircleView(Context context, AttributeSet attrs){
        this(context,attrs,0);
        init();
    }

    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        mColor = a.getColor(R.styleable.CircleView_circle_color, Color.RED);
        a.recycle();
        init();
    }

    private void init(){
        mPaint.setColor(mColor);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(200, 200);
        }else if(widthSpecMode == MeasureSpec.AT_MOST ){
            setMeasuredDimension(200, heightSpecSize);
        }else if(heightSpecMode == MeasureSpec.AT_MOST){
            setMeasuredDimension(widthSpecSize, 200);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        final int paddingLeft = getPaddingLeft();
        final int paddingRight = getPaddingRight();
        final int paddingTop = getPaddingTop();
        final int paddingBottom = getPaddingBottom();
        int width = getWidth() - paddingLeft - paddingRight;
        int height = getHeight() - paddingTop - paddingBottom;
        int radius = Math.min(width,height)/2;
        canvas.drawCircle(paddingLeft+width/2,paddingTop+height/2,radius,mPaint);
    }

}

Topics: Android