orginal source : https://programmer.help/blogs/android-custom-view-foundation-onmeasure-onlayout.html

Android Custom View Foundation onMeasure & onLayout

Official Guidelines: https://developer.android.com/guide/topics/ui/custom-components.html

Method introduction

  • onMeasure()   Used to measure the width and height of view
  • onLayout()   Used to control the position of subviews

onMeasure

In this method, the width of the view is measured and then the height of the view is determined. It should be noted here that the width of the sub-view is measured, not its own.

The complete method is like this.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)

Two parameters are passed in, which are the measurements of the size of the current View by the parent layout. It contains wide and high values and measurement modes, which can be obtained using the View.MeasureSpec class.

int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);

It goes without saying that size is px. There are three measurement modes, as shown in the table below.

Measurement mode

  • MeasureSpec.EXACTLY Precise size, parent layout has given a clear size, such as match_parent or 50dp, all of which are precise.
  • MeasureSpec.AT_MOST Adaptive, the parent layout gives a maximum, and the child view adapts itself to the size, but cannot exceed the maximum, such as setting the width to wrap_content
  • MeasureSpec.UNSPECIFIED There is no clear size, the parent layout does not give any value, and the child view is free to play! uuuuuuuuuuu It should be used in a layout like a list adapter

Well, knowing the meaning of this parameter, we can then measure the width of the self-view. In general, if you need to measure subviews, you must inherit ViewGroup or its subclasses.

Several methods are provided in ViewGroup to facilitate simple measurement operations:

  • measureChild()
    Measure the self-view according to the width and height measurements given by the parent layout
  • measureChildWithMargins()
    Measure the self-view according to the given parent layout width and height measurement data and the occupied width and height values, and consider the outer margin of the sub-view. (Margin Layout Params also needs to be implemented if this measurement method is used)

Or you can set the measurements using the measure method of the subview.

Here’s a simple example, inheriting ViewGroup

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

    for (int i = 0, count = getChildCount(); i < count; i++){
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE){
            //Measurement Subview
            measureChild(child, widthMeasureSpec,heightMeasureSpec);
        }
    }
    //In onMeasure, this method must be called to set the final measurement width and height.
    setMeasuredDimension(
                MeasureSpec.getSize(widthMeasureSpec),
                MeasureSpec.getSize(heightMeasureSpec));
}

onLayout

This method is called after onMeasure, where the position of the child view is set. The layout() method is called once for all the subviews, and the task is completed.

The simplest example is to stack the layout from the top corner without considering any other factors.

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    for (int i = 0, count = getChildCount(); i < count; i++){
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE){
            child.layout(0,0,child.getMeasuredWidth(),child.getMeasuredHeight());
        }
    }
}

Example

Combining the above two methods gives rise to a view that looks like FrameLayout (there’s still a big gap in detail! )

public class CustomFrameLayout extends ViewGroup {

    public CustomFrameLayout(Context context) {
        super(context);
    }

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

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

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

        for (int i = 0, count = getChildCount(); i < count; i++){
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE){
                measureChild(child, widthMeasureSpec,heightMeasureSpec);
            }
        }

        setMeasuredDimension(
                MeasureSpec.getSize(widthMeasureSpec),
                MeasureSpec.getSize(heightMeasureSpec));
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        for (int i = 0, count = getChildCount(); i < count; i++){
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE){
                child.layout(0,0, child.getMeasuredWidth(),child.getMeasuredHeight());
            }
        }
    }
}

Of course, the official FrameLayout is far more complicated than the examples I wrote, because there are many other things to consider, such as the margin problem, the size of the outlook, gravity of sub-views, left-right swaps to consider when internationalizing, and so on. So when you can’t fully grasp the customized view, don’t easily inherit ViewGroup directly. You can find a suitable subclass to inherit and make a little modification. It will be much easier!

Comments are closed.

Post Navigation