Custom View-on Measure for Newcomers (2)

Keywords: Android xml encoding Google



This is mentioned last time in the getChildMeasureSpec method of ViewGroup, which means that the father View already has the actual value (the width (height) of the parent ViewGroup in Xml is defined as the actual value, not wrap or match).

       // 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;

Last time, we analyzed child Dimension (the width and height of child View, the result of Xml parsing). When child Dimension <= 0, we explained that MatchParent and WrapContent were parsed.
So let's move on.

else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            }

When child Dimension is resolved into MatchParent, the final size is equal to size. Remember what size is? That is, the int size = Math.max(0, specSize - padding) we mentioned last time; that is, the size of the parent container currently available in width (height), and then the resultMode = MeasureSpec.EXACTLY; OK continues to look.

 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;
            }

When the result of the analysis is Wrap_Content, the width (height) also assigns the maximum available size, but the measurement mode is MeasureSpec.AT_MOST. Let's continue with the following code

        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;

This time, the parent ViewGroup was not parsed out in Xml (as we understand it first).

  if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            }

Nothing changed. It's the same as above.

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;
            }

The final MeasureSpec.makeMeasureSpec(resultSize, resultMode); the final result is assembled into a MeasureSpec.
Look again at the measureChildWithMargins method

    protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);

child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
It calls the onMeasure method of the sub-View directly and passes the measurement specification to the sub-View. That's almost part of the run. It's not that confusing.

Here's an idea. Have you ever customized a simple View and found that although the size of View is defined as wrap_content in Xml, View fills in all parent Views, but, like some official controls, it's different?

You can personally experience this effect, I was very curious at first, until I saw the source code, and finally wake up (in fact, the answer is in the source code). Someone will surely ask why the system controls like TextView and Button use wrap content and why they don't fill in? First tell you the answer, because those system controls, already in their own onMeasure method, the father passed the measurement mode, analysis, and processing. OK, which can also illustrate a problem, the size of the View itself, not entirely determined by its own parent View, is determined by both the View itself and the parent View.

It's not very interesting to just read the text, so now let's write a simple demo test.
We can define a child View directly. The parent View uses LinearLayout directly, because we haven't talked about the onLayout method yet. Let's start our code under OK.

public class MyView extends View {
    Paint mPaint;

    public MyView(Context context) {
        super(context);
        initView();
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
    }

    private void initView() {
        mPaint = new Paint();
        mPaint.setColor(Color.BLUE);
    }
}

Layout

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

    <picture.yisi.com.viewconfigrationtest.MyView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>


</LinearLayout>

Ok, that's it. It's very easy. Instead of BB with you, it's a simple custom View, which takes the width of the View as the width of the rectangle. After onDraw, you will read the results briefly. Verify the same onMeasure.


Paste_Image.png

This picture seems to be a little big. The cut-off is not very good. I'm sorry. Sure enough, although we've already used wrap_content in View above, the picture fills all the screens. We can analyze the results according to the source code above. The parent View Linear Layout in this case is mathch_parent, so it corresponds to this situation, that is to say, look at the source code.

        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;

The third else if, the result is the final size of the View, which is the size (the maximum size available for the parent View) that is match_parent.

There must be some doubts about it. Isn't it true that the size of the child View is entirely determined by the parent View? OK, let's try to modify MyView's onMeasure method. First, we only deal with wrap_content.

Modified MyView has more onMeasure methods

public class MyView extends View {
    Paint mPaint;

    public MyView(Context context) {
        super(context);
        initView();
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0, 0, getWidth(), getHeight(), mPaint);
    }

    private void initView() {
        mPaint = new Paint();
        mPaint.setColor(Color.BLUE);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        //Just test the width.
        switch (widthMode) {
            //As you can see from the source code of ViewGroup, when wrap_content
            case MeasureSpec.AT_MOST:
                //We wrote 200 dead here.
                widthSize = 200;
                break;
            case MeasureSpec.EXACTLY:
                break;
            case MeasureSpec.UNSPECIFIED:
                break;
        }
  //Don't forget to tell Father View that the final size calls this method. If you don't adjust, you will collapse.
        setMeasuredDimension(widthSize, heightSize);
    }
}

Look at the results this time.


Paste_Image.png

Aha, it's 200. Aha, so it can be said that there really is a father View and a son View together. Then I'm giving you a form that I stole from my teacher, Google's younger brother. Hey hey.


Custom View onMeasure.png

OK, onMeasure may be written here, if there's anything else I'll add, if there's no idea, maybe it's onLayout directly. Thank you for your support.

Posted by lhaynes on Mon, 01 Apr 2019 17:21:30 -0700