Android custom View minimum necessary knowledge, advanced Android Development Technology

Posted by Jaguar83 on Thu, 16 Dec 2021 20:45:58 +0100

The custom View includes three parts:

  1. Layout
  2. Drawing
  3. Touch feedback (Event Handling)

Layout stage: determine the position and size of the View.
Drawing stage: draw the contents of the View.
Touch feedback: determine where the user clicked.

The layout stage includes two processes: Measurement and layout. In addition, the layout stage supports the drawing and touch feedback stages, which has no direct effect. It is precisely because the size and position of the View are determined in the layout stage that the drawing stage knows where to draw, and the touch feedback stage knows where the user points.

In addition, since touch feedback is a big topic, it will not be explained here due to space limitations. If I have the opportunity later, I will add an article on touch feedback.

In the custom View and custom ViewGroup, although the layout and drawing processes are the same as a whole, the details of the custom View and custom ViewGroup are still different. Therefore, we will discuss them in two categories:

  • Customize View layout and drawing process
  • Customize ViewGroup layout and drawing process

3.1 custom View layout and drawing process

Custom View layout and drawing mainly includes three stages:

  1. Measurement phase
  2. layout phase
  3. draw phase

3.1. 1. Custom measurement phase

In the measurement phase of view, two methods will be executed (in the measurement phase, the parent view of view will pass in the size requirements of the parent view by calling the measure() method of view. The measure() method immediately following View will do some pre and optimization work, then call the onMeasure() method of View, and introduce the parent View to View size requirement through onMeasure() method. In a custom view, the onMeasure() method needs to be overridden only when the size of the view needs to be modified. In the onMeasure() method, carry out corresponding logical processing according to business requirements, and finally inform the parent view of its expected size by calling the setMeasuredDimension() method:

  • measure()
  • onMeasure()

measure(): scheduling method, which mainly does some pre-processing and optimization work, and will eventually call onMeasure() method to perform actual measurement work;

onMeasure(): the method of actually performing measurement tasks, which is mainly used to measure the size and position of View. In the onmeasure () method of the custom View, the View calculates its desired size according to its own characteristics and the size requirements of the parent View, and informs the parent View of its desired size through the setMeasuredDimension() method.

onMeasure() calculates the expected size of View as follows:

  1. Calculate the expected size of the View by referring to the size requirements of the parent View and the actual business requirements:

    • Analyze widthMeasureSpec;
    • Analyze heightMeasureSpec;
    • Modify the "calculate the size of the View according to the actual business requirements" according to the "size requirements of the parent View for the View" to obtain the expected size of the View (by calling the resolveSize() method);
  2. Save the expected size of the View through setMeasuredDimension() (in fact, inform the parent View of its own expected size through setMeasuredDimension());

be careful:
In most cases, the desired size here is the final size of the View. However, whether the expected size of the View is the same as the actual size depends on whether its parent View agrees. The parent View of the View will eventually inform the View of the actual size by calling the layout() method of the View, and in the layout() method, the View needs to save the actual size for use in the drawing stage and touch feedback stage, This is why the View needs to save its actual size in the layout() method - because it needs to be used in the drawing stage and touch feedback stage!

3.1. 2. Custom View layout stage

In the View layout phase, two methods will be executed (in the layout phase, the parent View of the View will pass the actual size of the View (the actual size of the View determined by the parent View according to the expected size of the View) to the View by calling the View's layout() method. The View needs to save its actual size in the layout() method (by calling the View's setFrame() method, In the setframe () method, the developer will be informed of the modified size of the View by calling the onSizeChanged() method for use in the drawing and touch feedback stages. After saving the actual size of the View, the View's layout () method will call the View's onLayout() method, but the View's onLayout() method is an empty implementation because it has no child View):

  • layout()
  • onLayout()

layout(): saves the actual size of the View. Call the setFrame() method to save the actual size of the View, call onSizeChanged() to notify the developer that the size of the View has changed, and finally call the onLayout() method to make the child View layout (if there is a child View. Because there is no child View in the custom View, the onLayout() method of the custom View is an empty Implementation);

onLayout(): empty implementation, doing nothing because it has no child views. If it is a ViewGroup, you need to call the layout() method of the child View in the onlayout () method to pass the actual size of the child View to them and let the child View save its own actual size. Therefore, you do not need to override this method in a custom View, and you need to override this method in a custom ViewGroup.

be careful:
layout() & onLayout() is not the relationship between "scheduling" and "actually doing things". Both layout() and onLayout() do things, but their responsibilities are different.

3.1. 3. Custom View drawing stage

In the drawing stage of the View, a method, draw(), is executed. draw() is the general scheduling method of the drawing stage, in which the method of drawing background (), the method of drawing main body onDraw(), the method of drawing sub View dispatchDraw(), and the method of drawing foreground ondrawforegroup (), are called:

  • draw()

draw(): the general scheduling method in the drawing stage, in which the method drawBackground(), the method onDraw(), the method dispatchDraw() and the method ondrawforegroup() for drawing the background, the method onDraw() for drawing the main body, the method dispatchDraw() for drawing the sub View and the method ondrawforegroup() for drawing the foreground will be called;

drawBackground(): the method of drawing background. It cannot be rewritten. The background can only be set or modified through xml layout file or setBackground();

onDraw(): the method to draw the main content of the View. Generally, when customizing the View, only this method can be implemented;

dispatchDraw(): method to draw sub views. Like the onLayout() method, it is an empty implementation in a custom View and does nothing. But in a custom ViewGroup, it calls ViewGroup Drawchild() method, in ViewGroup In the drawchild () method, the View. Of each child View will be called Draw() lets the child View draw itself;

Ondrawforegroup(): the method of drawing the View foreground, that is, when you want to draw something on the main content, you can implement it in this method.

be careful:
The drawing in Android is in order, and the content drawn first will be covered by the content drawn later. For example, the results of "draw a circle before drawing a square" and "draw a square before drawing a circle" at the overlapping position are different, as shown in the following table:

[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-3xvtxxxr-1630645917985)( https://user-gold-cdn.xitu.io/2019/9/2/16cf0103f4c76c31?imageView2/0/w/1280/h/960/ignore -error/1)]
[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-lqwd6xj-1630645917987)( https://user-gold-cdn.xitu.io/2019/9/2/16cf0103f4a8c299?imageView2/0/w/1280/h/960/ignore -error/1)]

3.1. 4. Customize View layout and draw flow sequence diagram

[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (IMG hucftucj-1630645917988)( https://user-gold-cdn.xitu.io/2019/9/2/16cf0103f49fee81?imageView2/0/w/1280/h/960/ignore -error/1)]

3.2 customize ViewGroup layout and drawing process

Custom ViewGroup layout and drawing mainly includes three stages:

  1. Measurement phase
  2. layout phase
  3. draw phase

3.2. 1 custom ViewGroup measurement phase

Like a custom View, two methods are executed during the measurement phase of a custom ViewGroup:

  • measure()
  • onMeasure()

measure(): scheduling method, which mainly does some pre-processing and optimization work, and will eventually call onMeasure() method to perform actual measurement work;

onMeasure(): the method of actually executing the measurement task is different from the custom View. In the onMeasure() method of the custom ViewGroup, the ViewGroup will recursively call the measure() method of the child View, The size requirements of the ViewGroup for the child views are determined through measure() (the ViewGroup will determine its own parent View according to the size requirements of the developer for the child views (the parent View of the ViewGroup calculates its own size requirements for the child View based on its own size requirements and its own available space) pass in, measure the child View, and temporarily save the measurement results for use in the layout stage. After measuring the actual size of the child View, the ViewGroup will calculate its own expected size based on the actual size of the child View and pass s The etmeasureddimension () method tells the parent View (the parent View of the ViewGroup) its desired size.

The specific process is as follows:

  1. Before running, the developer writes the size requirements layout of ViewGroup and ViewGroup sub views in xml_ xxx;
  2. In its own onMeasure() method, ViewGroup calculates its own size requirements for child views according to the size requirements for child views of ViewGroup written by the developer in xml, its own size requirements for parent views (parent views of ViewGroup) and its own available space, and calls the measure() of each child View to pass in the size requirements for child views of ViewGroup, Measure the sub dimension;
  3. After the ViewGroup calculates the expected size of the child View (in the onMeasure() method of the ViewGroup, the ViewGroup recursively calls the measure() method of each child View, In its onmeasure () method, the child View will call the setMeasuredDimension() method to inform the parent View (ViewGroup) of its desired size), get the actual size and position of the child View, and temporarily save the calculation results for use in the layout stage;
  4. The ViewGroup calculates its own expected size according to the size and position of the child View, and informs the parent View of its own expected size through the setMeasuredDimension() method. If you want to do better, you can modify the size requirements of the ViewGroup in combination with the parent View of the ViewGroup after "the ViewGroup calculates its own expected size according to the size and position of the child View", so that the expected size of the ViewGroup can better meet the size requirements of the parent View of the ViewGroup.

3.2. 2. Customize ViewGroup layout stage

Like a custom View, two methods are executed during the layout phase of a custom ViewGroup:

  • layout()
  • onLayout()

layout(): saves the actual size of the ViewGroup. Call the setFrame() method to save the actual size of the ViewGroup, call onSizeChanged() to notify the developer that the size of the ViewGroup has changed, and finally call the onLayout() method to make the child View layout;

onLayout(): ViewGroup will recursively call the layout() method of each sub View to transfer the actual size and position of the sub View calculated in the measurement stage to the sub View, so that the sub View can save its own actual size and position.

3.2. 3 custom ViewGroup drawing phase

Like a custom View, a method - draw () is executed during the drawing phase of a custom ViewGroup. draw() is the general scheduling method of the drawing stage, in which the method drawBackground(), the method onDraw(), the method dispatchDraw() and the method onDrawForeground() of drawing the background, the main body, the sub View and the foreground will be called:

  • draw()

draw(): the general scheduling method in the drawing stage, in which the method drawBackground(), the method onDraw(), the method dispatchDraw() and the method ondrawforegroup() for drawing the background, the method onDraw() for drawing the main body, the method dispatchDraw() for drawing the sub View and the method ondrawforegroup() for drawing the foreground will be called;

In the ViewGroup, you can also override the method onDraw() for drawing the main body, the method dispatchDraw() for drawing the sub View, and the method ondrawforegroup() for drawing the foreground. However, in most cases, a custom ViewGroup does not need to override any drawing methods. Because usually, the role of ViewGroup is a container, a transparent container, which is only used to hold child views.

3.2. 4. Customize ViewGroup layout and draw flow sequence diagram

[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-erbwyiwm-1630645917989)( https://user-gold-cdn.xitu.io/2019/9/2/16cf0103f48b0917?imageView2/0/w/1280/h/960/ignore -error/1)]

3.3 custom View steps

  1. Declaration and acquisition of custom attributes;
  2. Rewrite the measurement phase related method (onMeasure());
  3. Rewrite the layout phase related methods (onLayout() (only ViewGroup needs to be rewritten));
  4. Rewrite the methods related to the drawing stage (onDraw() drawing the main body, dispatchDraw() drawing the sub View and ondrawforegroup() drawing the foreground);
  5. onTouchEvent();
  6. onInterceptTouchEvent() (only ViewGroup has this method);

4. Actual combat drill

4.1 custom View

4.1. 1. Custom View - customize the drawing content of the View

Customize the View. Its content is "three concentric circles with different radii and colors". The effect diagram is as follows:

[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-fhw5njmv-1630645917990)( https://user-gold-cdn.xitu.io/2019/9/2/16cf010572139870?imageView2/0/w/1280/h/960/ignore -error/1)]

  1. Declaration and acquisition of custom attributes
//1.1 customize View attribute in xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--CircleView-->
    <declare-styleable name="CircleView">
        <attr name="circle_radius" format="dimension" />
        <attr name="outer_circle_color" format="reference|color" />
        <attr name="middle_circle_color" format="reference|color" />
        <attr name="inner_circle_color" format="reference|color" />
    </declare-styleable>
</resources>

//1.2 get custom View attribute in View constructor
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
mRadius = typedArray.getDimension(R.styleable.CircleView_circle_radius, getResources().getDimension(R.dimen.avatar_size));
mOuterCircleColor = typedArray.getColor(R.styleable.CircleView_outer_circle_color, getResources().getColor(R.color.purple_500));
mMiddleCircleColor = typedArray.getColor(R.styleable.CircleView_middle_circle_color, getResources().getColor(R.color.purple_500));
mInnerCircleColor = typedArray.getColor(R.styleable.CircleView_inner_circle_color, getResources().getColor(R.color.purple_500));
typedArray.recycle(); 
  1. Rewrite the measurement phase related method (onMeasure())

Since you do not need to customize the size of the View, you do not need to override this method.

  1. Override layout phase related methods (onLayout() (only ViewGroup needs to be overridden))

Since no child View needs layout, you do not need to override this method.

  1. Rewrite the methods related to the drawing stage (onDraw() to draw the main body, dispatchDraw() to draw the sub View and ondrawforegroup() to draw the foreground)
//4. Override onDraw() method to customize the View content
@Override
protected void onDraw(Canvas canvas) {
    mPaint.setColor(mOuterCircleColor);
    canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
    mPaint.setColor(mMiddleCircleColor);
    canvas.drawCircle(mRadius, mRadius, mRadius * 2/3, mPaint);
    mPaint.setColor(mInnerCircleColor);
    canvas.drawCircle(mRadius, mRadius, mRadius/3, mPaint);
} 
  1. onTouchEvent()

Since the View does not need to interact with the user, there is no need to override this method.

  1. onInterceptTouchEvent() (only ViewGroup has this method)

Method of ViewGroup.

The complete code is as follows:

//1. Declaration of custom attributes  
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--CircleView-->
    <declare-styleable name="CircleView">
        <attr name="circle_radius" format="dimension" />
        <attr name="outer_circle_color" format="reference|color" />
        <attr name="middle_circle_color" format="reference|color" />
        <attr name="inner_circle_color" format="reference|color" />
    </declare-styleable>
</resources>

//2. CircleView  
public class CircleView extends View {

    private float mRadius;
    private int mOuterCircleColor, mMiddleCircleColor, mInnerCircleColor;
    private Paint mPaint;

    public CircleView(Context context) {
        this(context, null);
    }

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

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

    private void initData(Context context, AttributeSet attrs) {
        //1. Declaration and acquisition of custom attributes
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        mRadius = typedArray.getDimension(R.styleable.CircleView_circle_radius, getResources().getDimension(R.dimen.avatar_size));
        mOuterCircleColor = typedArray.getColor(R.styleable.CircleView_outer_circle_color, getResources().getColor(R.color.purple_500));
        mMiddleCircleColor = typedArray.getColor(R.styleable.CircleView_middle_circle_color, getResources().getColor(R.color.purple_500));
        mInnerCircleColor = typedArray.getColor(R.styleable.CircleView_inner_circle_color, getResources().getColor(R.color.purple_500));
        typedArray.recycle();

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mOuterCircleColor);
    }

    //2. Rewrite the measurement phase related method (onMeasure());
    //Since you do not need to customize the size of the View, you do not need to override this method
//    @Override
//    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//    }

    //3. Rewrite the layout phase related methods (onLayout() (only ViewGroup needs to be rewritten));
    //Since no child views need layout, you do not need to override this method
//    @Override
//    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
//        super.onLayout(changed, left, top, right, bottom);
//    }

    //4. Rewrite the methods related to the drawing stage (onDraw() drawing the main body, dispatchDraw() drawing the sub View and ondrawforegroup() drawing the foreground);
    @Override
    protected void onDraw(Canvas canvas) {
        mPaint.setColor(mOuterCircleColor);
        canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
        mPaint.setColor(mMiddleCircleColor);
        canvas.drawCircle(mRadius, mRadius, mRadius * 2/3, mPaint);
        mPaint.setColor(mInnerCircleColor);
        canvas.drawCircle(mRadius, mRadius, mRadius/3, mPaint);
    }

}

//3. Apply CircleView 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=".custom_view_only_draw.CustomViewOnlyDrawActivity">

    <com.smart.a03_view_custom_view_example.custom_view_only_draw.CircleView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:circle_radius="@dimen/padding_ninety_six"
        app:inner_circle_color="@color/yellow_500"
        app:middle_circle_color="@color/cyan_500"
        app:outer_circle_color="@color/green_500" />

</LinearLayout> 

The final effect is as follows:

[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-d2aeewwj-1630645917992)( https://user-gold-cdn.xitu.io/2019/9/2/16cf01057261a363?imageView2/0/w/1280/h/960/ignore -error/1)]

At this time, even if you declare the width and height of the CircleView as "match_parent" in xml, you will find that the final display effect is the same.

The main reason is that by default, the onMeasure() method of View will call the getdefaultsize () method when it tells the parent View of its expected size through setMeasuredDimension(). In the getDefaultSize() method, getSuggestedMinimumWidth() and getSuggestedMinimumHeight() will be called again to obtain the recommended minimum width and height, and correct their size requirements according to the minimum size and parent View. The most important thing is that when it is corrected in the getdefaultsize () method, the measurespec AT_ Most and measurespec Exictly treats the same, and directly returns the size requirements of the parent View for the View:

//1. Processing of default onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

//2. getSuggestedMinimumWidth()
protected int getSuggestedMinimumWidth() {
    return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}

//3. getSuggestedMinimumHeight()
protected int getSuggestedMinimumHeight() {
    return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}

//4. getDefaultSize()
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        //MeasureSpec.AT_MOST,MeasureSpec. Practically equal treatment
        result = specSize;
        break;
    }
    return result;
} 

It is precisely because when processing in the getDefaultSize() method, the measurespec AT_ Most and measurespec The above phenomenon "when applying a CircleView in xml, the effect is the same whether the size of the CircleView is set to match_parent or wrap_content".

The specific analysis is as follows:

Size requirements of developers for ViewSize requirements of the parent View of ViewExpected size of View
android:layout_width="wrap_content"
android:layout_height="wrap_content"MeasureSpec.AT_MOST
specSizespecSize
android:layout_width="match_parent"
android:layout_height="match_parent"MeasureSpec.EXACTLY
specSizespecSize

Note:
In the above table, "size requirements of the View's parent View for the View" means that the View's parent View calculates its size requirements for the child View according to "size requirements of the developer for the child View", "size requirements of its own parent View (parent View of the View's parent View)" and "its own available space".

In addition, the execution results show that the specSize in the above table is actually equal to the size of the View:

2019-08-13 17:28:26.855 16024-16024/com.smart.a03_view_custom_view_example E/TAG: Width(getWidth()):  1080  Height(getHeight()):  1584 

4.1. 2. Custom View - customize the size and drawing content of the View

Customize the View. Its content is "three concentric circles with different radii and colors". The effect diagram is as follows:

[the external chain picture transfer fails. The source station may have an anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-sipov1gd-1630645917992)( https://user-gold-cdn.xitu.io/2019/9/2/16cf010572139870?imageView2/0/w/1280/h/960/ignore -error/1)]

  1. Declaration and acquisition of custom attributes
//1.1 customize View attribute in xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--CircleView-->
    <declare-styleable name="CircleView">
        <attr name="circle_radius" format="dimension" />
        <attr name="outer_circle_color" format="reference|color" />
        <attr name="middle_circle_color" format="reference|color" />
        <attr name="inner_circle_color" format="reference|color" />
    </declare-styleable>
</resources>

//1.2 get custom View attribute in View constructor
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
mRadius = typedArray.getDimension(R.styleable.CircleView_circle_radius, getResources().getDimension(R.dimen.avatar_size));
mOuterCircleColor = typedArray.getColor(R.styleable.CircleView_outer_circle_color, getResources().getColor(R.color.purple_500));
mMiddleCircleColor = typedArray.getColor(R.styleable.CircleView_middle_circle_color, getResources().getColor(R.color.purple_500));
mInnerCircleColor = typedArray.getColor(R.styleable.CircleView_inner_circle_color, getResources().getColor(R.color.purple_500));
typedArray.recycle(); 
  1. Rewrite the measurement phase related method (onMeasure())
//2. onMeasure()
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //2.1 calculate the size of View according to its characteristics or business requirements
    mWidth = (int)(mRadius * 2);
    mHeight = (int)(mRadius * 2);

    //2.2 correct the result by resolveSize()
    mWidth = resolveSize(mWidth, widthMeasureSpec);
    mHeight = resolveSize(mHeight, heightMeasureSpec);

    //2.3 save the expected size of the View through setMeasuredDimension() (inform the parent View of the expected size through setMeasuredDimension())
    setMeasuredDimension(mWidth, mHeight);
} 
  1. Rewrite the layout phase related methods (onLayout() (only ViewGroup needs to be rewritten))

Since no child View needs layout, you do not need to override this method.

  1. Rewrite the methods related to the drawing stage (onDraw() to draw the main body, dispatchDraw() to draw the sub View and ondrawforegroup() to draw the foreground)
//4. Override onDraw() method to customize the View content
@Override
protected void onDraw(Canvas canvas) {
    mPaint.setColor(mOuterCircleColor);
    canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
    mPaint.setColor(mMiddleCircleColor);
    canvas.drawCircle(mRadius, mRadius, mRadius * 2/3, mPaint);
    mPaint.setColor(mInnerCircleColor);
    canvas.drawCircle(mRadius, mRadius, mRadius/3, mPaint);
} 
  1. onTouchEvent()

Since the View does not need to interact with the user, there is no need to override this method.

  1. onInterceptTouchEvent() (only ViewGroup has this method)

Method of ViewGroup.

The complete code is as follows:

//1. Declaration of custom attributes  
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--CircleView-->
    <declare-styleable name="CircleView">
        <attr name="circle_radius" format="dimension" />
        <attr name="outer_circle_color" format="reference|color" />
        <attr name="middle_circle_color" format="reference|color" />
        <attr name="inner_circle_color" format="reference|color" />
    </declare-styleable>
</resources>

//2. MeasuredCircleView
public class MeasuredCircleView extends View {

    private int mWidth, mHeight;
    private float mRadius;
    private int mOuterCircleColor, mMiddleCircleColor, mInnerCircleColor;
    private Paint mPaint;

    public MeasuredCircleView(Context context) {
        this(context, null);
    }

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

    public MeasuredCircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initData(context, attrs);
    }

    private void initData(Context context, AttributeSet attrs) {
        //1. Declaration and acquisition of custom attributes
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CircleView);
        mRadius = typedArray.getDimension(R.styleable.CircleView_circle_radius, getResources().getDimension(R.dimen.avatar_size));
        mOuterCircleColor = typedArray.getColor(R.styleable.CircleView_outer_circle_color, getResources().getColor(R.color.purple_500));
        mMiddleCircleColor = typedArray.getColor(R.styleable.CircleView_middle_circle_color, getResources().getColor(R.color.purple_500));
        mInnerCircleColor = typedArray.getColor(R.styleable.CircleView_inner_circle_color, getResources().getColor(R.color.purple_500));
        typedArray.recycle();

        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPaint.setColor(mOuterCircleColor);
    }

    //2. Rewrite the measurement phase related method (onMeasure());
    //Since you do not need to customize the size of the View, you do not need to override this method
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //2.1 calculate the size of View according to its characteristics or business requirements
        mWidth = (int)(mRadius * 2);
        mHeight = (int)(mRadius * 2);

        //2.2 correct the result by resolveSize()
        mWidth = resolveSize(mWidth, widthMeasureSpec);
        mHeight = resolveSize(mHeight, heightMeasureSpec);

        //2.3 save the expected size of the View through setMeasuredDimension() (inform the parent View of the expected size through setMeasuredDimension())
        setMeasuredDimension(mWidth, mHeight);
    }

    //3. Rewrite the layout phase related methods (onLayout() (only ViewGroup needs to be rewritten));
    //Since no child views need layout, you do not need to override this method
//    @Override
//    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
//        super.onLayout(changed, left, top, right, bottom);
//    }

    //4. Rewrite the methods related to the drawing stage (onDraw() drawing the main body, dispatchDraw() drawing the sub View and ondrawforegroup() drawing the foreground);
    @Override
    protected void onDraw(Canvas canvas) {
        mPaint.setColor(mOuterCircleColor);
        canvas.drawCircle(mRadius, mRadius, mRadius, mPaint);
        mPaint.setColor(mMiddleCircleColor);
        canvas.drawCircle(mRadius, mRadius, mRadius * 2/3, mPaint);
        mPaint.setColor(mInnerCircleColor);
        canvas.drawCircle(mRadius, mRadius, mRadius/3, mPaint);
    }

}

//3. Apply MeasuredCircleView 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"
    android:orientation="vertical"
    tools:context=".custom_view_measure_draw.CustomViewMeasureDrawActivity">

    <com.smart.a03_view_custom_view_example.custom_view_measure_draw.MeasuredCircleView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:circle_radius="@dimen/padding_ninety_six"
        app:inner_circle_color="@color/yellow_500"
        app:middle_circle_color="@color/cyan_500"
        app:outer_circle_color="@color/green_500" />
</LinearLayout> 

The final effect is as follows:

[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-sz659d4g-1630645917993)( https://user-gold-cdn.xitu.io/2019/9/2/16cf01057261a363?imageView2/0/w/1280/h/960/ignore -error/1)]

When the width and height of MeasuredCircleView are declared as "match_parent" in xml, the display effect is the same as that of CircleView.

Size requirements of developers for ViewSize requirements of the parent View of ViewExpected size of View
android:layout_width="match_parent"
android:layout_height="match_parent"MeasureSpec.EXACTLY
specSizespecSize

However, when the width and height of MeasuredCircleView are declared as "wrap_content" in xml, the display effect is as follows:

[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-zvstepxs-1630645917994)( https://user-gold-cdn.xitu.io/2019/9/2/16cf010572139870?imageView2/0/w/1280/h/960/ignore -error/1)]

In fact, it is also easy to understand:

Size requirements of developers for ViewSize requirements of the parent View of ViewExpected size of View
android:layout_width="wrap_content"
android:layout_height="wrap_content"MeasureSpec.AT_MOST
specSizeif(childSize < specSize) childSize
if(childSize > specSize) specSize

4.2 customize ViewGroup

Customize the ViewGroup and label layout, and the effect diagram is as follows:

[external chain picture transfer failed. The source station may have anti-theft chain mechanism. It is recommended to save the picture and upload it directly (img-sqzctul6-1630645917994)( https://user-gold-cdn.xitu.io/2019/9/2/16cf01057257354d?imageslim )]

Whether it is a custom View or a custom ViewGroup, the general process is the same:

  1. Declaration and acquisition of custom attributes;
  2. Rewrite the measurement phase related method (onMeasure());
  3. Rewrite the layout phase related methods (onLayout() (only ViewGroup needs to be rewritten));
  4. Rewrite the methods related to the drawing stage (onDraw() drawing the main body, dispatchDraw() drawing the sub View and ondrawforegroup() drawing the foreground);
  5. onTouchEvent();
  6. onInterceptTouchEvent() (only ViewGroup has this method);

However, in most cases, the ViewGroup does not need "custom attributes" and "rewriting drawing phase related methods", but it is still necessary sometimes. For example, if developers want to draw some content above all child views of the ViewGroup, they can override the ondrawforegroup() of the ViewGroup.

  1. Declaration and acquisition of custom attributes

The method of "Declaration and acquisition of custom attributes" in custom ViewGroup is the same as that of "Declaration and acquisition of custom attributes" in custom View. In most cases, custom attributes are not required in custom ViewGroup, so custom attributes are not required here.

  1. Rewrite the measurement phase related method (onMeasure())
//2. Rewrite the measurement phase related method (onMeasure());
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

    //2.1 resolve the size requirements of the parent View of the ViewGroup for the ViewGroup
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(widthMeasureSpec);
    int heightSize = MeasureSpec.getSize(widthMeasureSpec);

    //2.2 ViewGroup according to "size requirements for child views of ViewGroup written by developers in xml", "size requirements for own parent View (parent View of ViewGroup)" and
    //"Own free space" calculates its own size requirements for the sub View, and passes the size requirements to the sub View through the measure() method of the sub View, so that the sub View can measure its desired size
    int widthUsed = 0;
    int heightUsed = getPaddingTop();
    int lineHeight = 0;
    int lineWidthUsed = getPaddingLeft();
    int maxRight = widthSize - getPaddingRight();

    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUsed);
        //Need to wrap
        if(widthMode != MeasureSpec.UNSPECIFIED && (lineWidthUsed + child.getMeasuredWidth() > maxRight)){
            lineWidthUsed = getPaddingLeft();
            heightUsed += lineHeight + mRowSpace;
            lineHeight = 0;
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, heightUsed);
        }

        //2.3 View Group temporarily saves the dimensions of sub views for use in the layout stage and drawing stage
        Rect childBound;
        if(mChildrenBounds.size() <= i){
            childBound = new Rect();
            mChildrenBounds.add(childBound);
        }else{
            childBound = mChildrenBounds.get(i);
        }
        //Child. Cannot be used here Getxxx() gets the dimension value of the child View, because the child View only measures the dimension and has no layout. These values are 0
//            childBound.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom());
        childBound.set(lineWidthUsed, heightUsed, lineWidthUsed + child.getMeasuredWidth(), heightUsed + child.getMeasuredHeight());

        lineWidthUsed += child.getMeasuredWidth() + mItemSpace;
        widthUsed = Math.max(lineWidthUsed, widthUsed);
        lineHeight = Math.max(lineHeight, child.getMeasuredHeight());
    }

    //2.4 the ViewGroup modifies the "size of itself (ViewGroup) calculated according to the actual size of the child View" combined with the "size requirements of its parent View for itself", and
    //Use the setMeasuredDimension() method to inform the parent View of its desired size
    int measuredWidth = resolveSize(widthUsed, widthMeasureSpec);
    int measuredHeight = resolveSize((heightUsed + lineHeight + getPaddingBottom()), heightMeasureSpec);
    setMeasuredDimension(measuredWidth, measuredHeight);
}

//Override generateLayoutParams()
//2.2. 1 when calling the measureChildWithMargins() method in the custom ViewGroup to calculate the size requirement of ViewGroup for sub View,
//The generateLayoutParams() method must be overridden in the ViewGroup because MarginLayoutParams is used in the measureChildWithMargins() method,
//If the generateLayoutParams() method is not overridden, MarginLayoutParams will be null when the measureChildWithMargins() method is called,
//So when you call the measureChildWithMargins() method in custom ViewGroup, you must rewrite the generateLayoutParams() method.
@Override
public LayoutParams generateLayoutParams(AttributeSet attrs) {
    return new MarginLayoutParams(getContext(), attrs);
} 
  1. Override layout phase related methods (onLayout() (only ViewGroup needs to be overridden))
//3. Rewrite the layout phase related methods (onLayout() (only ViewGroup needs to be rewritten));
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    for (int i = 0; i < getChildCount(); i++) {
        //Apply the dimension value of the sub View calculated in the measurement stage to distribute the sub View
        View child = getChildAt(i);
        Rect childBound = mChildrenBounds.get(i);
        child.layout(childBound.left, childBound.top, childBound.right, childBound.bottom);
    }
} 
  1. Rewrite the methods related to the drawing stage (onDraw() to draw the main body, dispatchDraw() to draw the sub View and ondrawforegroup() to draw the foreground)

By default, when customizing the ViewGroup, you do not need to override the methods of any drawing stage, because the role of the ViewGroup is a container, a transparent container, which is only used to hold child views.

be careful:

Epilogue

If you don't step out, you'll never know your potential. Don't be bound by the shackles of this society on us. I'm not afraid at the age of 30, and I'm not afraid at the age of 35. Do what you want to do and fight for yourself! How do you know you can't do it without trying?

There is no shortcut to change life. This road needs to be taken by yourself. Only by deeply thinking, constantly reflecting and summarizing, maintaining the enthusiasm of learning, and building your own complete knowledge system step by step is the ultimate way to win and the mission that programmers should undertake.

Attached: We collected 20 sets of real Android interview questions for first and second tier Internet companies (including BAT, Xiaomi, Huawei, meituan and didi) and I sorted out my android review notes (including Android basic knowledge points, Android extended knowledge points, Android source code analysis, design pattern summary, Gradle knowledge points and common algorithm questions.)

hildBound = mChildrenBounds.get(i);
child.layout(childBound.left, childBound.top, childBound.right, childBound.bottom);
}
}

4.  Override drawing phase related methods( onDraw() Draw the body dispatchDraw() Draw sub View and onDrawForeground() Draw foreground)

By default, custom ViewGroup There is no need to override any drawing stage methods because ViewGroup The role of is a container, a transparent container, which is only used to hold children View of

be careful:
### Epilogue

If you don't step out, you'll never know your potential. Don't be bound by the shackles of this society on us. I'm not afraid at the age of 30, and I'm not afraid at the age of 35. Do what you want to do and fight for yourself! How do you know you can't do it without trying?

There is no shortcut to change life. This road needs to be taken by yourself. Only by deeply thinking, constantly reflecting and summarizing, maintaining the enthusiasm of learning, and building your own complete knowledge system step by step is the ultimate way to win and the mission that programmers should undertake.

>**Attached: We collected 20 sets of first and second tier Internet companies because of autumn recruitment Android Real interview questions (including BAT,Xiaomi, Huawei, meituan, Didi) and I sort it out Android Review notes (including Android Basic knowledge points Android Expand knowledge points Android Source code analysis, design pattern summary Gradle Summary of knowledge points and common algorithm problems.)**

[External chain picture transfer...(img-QuypBLig-1630645917995)]

**[CodeChina Open source projects:< Android Summary of study notes+Mobile architecture video+Real interview questions for large factories+Project practice source code](https://codechina.csdn.net/m0_60958482/android_p7)**

Topics: Android Design Pattern