Custom View incomplete to be continued

Custom View

Purple wish_ The world is as good as possible. A teenager who is committed to internship in the factory during the sophomore summer vacation wrote for the first time on February 13, 2022

What is a custom View

What is the difference between a custom View and a custom ViewGroup?

  • Custom views generally need to implement their own onMeasure() and onDraw() methods.

  • To customize the ViewGroup, you need to implement the onMeasure() and onLayout() methods.

To customize a View

Three steps:

  1. layout onLayout(){}
  2. draw onDraw(){}
  3. Touch event onTouchEvent(){}

layout

Layout, which determines the size and position of the child View. If it is not set, the child View will be in the upper left corner of the parent control. Then determine the required size according to the size of the child control.

First, the child control needs to measure itself, that is, call the onMeasure() method of the child control to make the child control measure itself. Before layout, the measurement operation must be carried out first, otherwise the width and height of the get sub control will be inconsistent when getting the value for many times. When calling the onMeasure() method of a child control, including rewriting its own onMeasure() method, you will encounter two parameters widthMeasureSpec: Int and hightmeasurespec: int. why are these two parameters? How to set the values of these two parameters? How to use the values of these two parameters? I believe these problems are difficult for beginners and must be solved.

What are widthMeasureSpec and hightmeasurespec

Let's see what these two words mean first:

width measure spec

Height measurement spec

Through the name, we can easily understand that it is the recommended width / height value that the parent View tells the child View.

So how can the parent View clearly describe the recommended width / height value to the child View with only one parameter? This is wonderful. I'm really amazed at the ingenuity of Google engineers. Don't underestimate this variable. It stores both specMode and specSize information. How? We know that integer is represented by 4-byte binary in computer, that is, 32 zeros or 1s. In general, the pixel value of the width or height of the Android device does not exceed 230, so Google engineers store the high bits 31 and 30 specifically for other intention information.

Although there are only two, there are three kinds of information stored in it, which are just the dead dp value and wrap_content and match_parent, combined with the restrictions of the parent view on itself and its own restrictions on the child view, there are nine cases, which cover all the current situations - the parent view is match_parent, the child view is wrap_content; The parent view is wrap_content, sub view is match_parent and so on. Then the remaining 30 bits store information about the specific value size. This way of storing information in high places that can't be used at all is a new idea.

Let's see how Google official calculated the result of Spec

public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
    if (sUseBrokenMakeMeasureSpec) {
        return size + mode;
    } else {
        return (size & ~MODE_MASK) | (mode & MODE_MASK);
    }
}

When sUseBrokenMakeMeasureSpec is true, it is the calculation method of API17 and previous versions. Here, the size and mode are added directly. If the order of the passed parameters is inconsistent, it can still be executed normally. However, the overflow of either of the two values will affect the result of MeasureSpec, which leads to the bug of ConstraintsLayout, Google officials replaced it with a more rigorous bit calculation method to solve this problem. Now, the value of sUseBrokenMakeMeasureSpec is as follows:

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;	// The 31st and 30th bits are 1 and the rest are 0
// Use the old (broken) way of building MeasureSpecs.
private static boolean sUseBrokenMakeMeasureSpec = false;

If the default value is false, it will be calculated by (size & ~ mode_mask) | (mode & mode_mask).

What values should the widthMeasureSpec and hightmeasurespec parameters be set to

The system has given a good method for converting to WidthMeasureSprc or hightmeasurespec

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);

    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
        // Parent has imposed an exact size on us
        case MeasureSpec.EXACTLY:
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let them have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
    }
    //noinspection ResourceType
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}

In other words, only three parameters, int spec, int padding and int childdimension, need to be passed here. How are the parameters passed?

  • Specs can directly pass in their own widthMeasureSpec and hightmeasurespec, which are used to obtain the widthMeasureSpec and hightmeasurespec of subclasses
  • Padding needs to pass in its corresponding padding
  • childDimension passes the width or height in the layoutParams of the child View

Here is my general way of writing:

val childLayoutParams = childView.layoutParams

val chileWidthMeasureSpec = getChildMeasureSpec(
    widthMeasureSpec, paddingLeft + paddingRight, childLayoutParams.width
)
val chileHeightMeasureSpec = getChildMeasureSpec(
    heightMeasureSpec, paddingTop + paddingBottom, childLayoutParams.height
)
    // Finally, calculate your own width and height settings according to business requirements

Some small problems

The onMeasure() method may be called multiple times by the parent View, so you need to reset and initialize the used object properties. Here we can see an example:

// sdk30 -FrameLayout.java source code
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    mMatchParentChildren.clear();
    /**
     * Omit some codes
     */
    for (int i = 0; i < count; i++) {       
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
			measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
        }
    }
    if (count > 1) {
        for (int i = 0; i < count; i++) {
            child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        }
    }
}

Here we can see that the onMeasure method of the subclass is called twice, and the FrameLayout itself empties its ArrayList during onMeasure.

draw

Update after learning

Touch event

Update after learning

Purple wish_ The world is as good as possible. A teenager who is committed to internship in the factory during the sophomore summer vacation wrote for the first time on February 13, 2022

Keywords: Android kotlin UI

Added by hedgehog90 on Sun, 13 Feb 2022 08:34:48 +0200