当前位置:   article > 正文

Android自定义View中的onMeasure、onLayout和onDraw方法解析_自定义view的onmeasure是如何确定它的size

自定义view的onmeasure是如何确定它的size


在Android开发中,我们经常需要自定义View来实现特定的界面效果。要实现一个自定义View,我们需要了解并掌握onLayout、onMeasure和onDraw这三个关键方法。本文将详细介绍这三个方法的用法和解释,并给出两个自定义View的案例。

一、onLayout、onMeasure和onDraw方法

1.1 onMeasure(int widthMeasureSpec, int heightMeasureSpec)

onMeasure方法用于测量View的大小。在自定义View中,我们需要重写这个方法,根据View的宽高测量模式(MeasureSpec)来计算并设置View的宽高。

关于MeasureSpec

Android中,MeasureSpec是一个32位的int值,用于描述View的宽度和高度信息。它由两部分组成:模式(mode)和尺寸(size)。模式占据高2位,尺寸占据低30位。

MeasureSpec有三种模式:

  1. EXACTLY:精确模式,对应于LayoutParams中的match_parent和具体的数值,表示父View希望子View的大小应该是一个确切的值。

  2. AT_MOST:最大模式,对应于LayoutParams中的wrap_content,表示子View的大小最多是指定的值,它可以决定自己的大小。

  3. UNSPECIFIED:未指定模式,通常在系统内部使用,表示父View没有给子View任何限制,子View可以设置任何大小。

widthMeasureSpec和heightMeasureSpec分别对应于View的宽度和高度信息。在测量View的过程中,父View会根据自己的尺寸和子View的LayoutParams,计算出合适的widthMeasureSpec和heightMeasureSpec,然后通过onMeasure方法传递给子View。

在onMeasure方法中,我们可以使用MeasureSpec.getMode和MeasureSpec.getSize方法来获取MeasureSpec的模式和尺寸。然后根据这些信息,计算并设置View的宽度和高度。

总的来说,MeasureSpec是Android中测量View大小的一个重要机制,它帮助我们理解和处理View的测量过程。

1.2 onLayout(boolean changed, int left, int top, int right, int bottom)

onLayout方法用于确定View的位置。在自定义ViewGroup中,我们需要重写这个方法,根据子View的测量宽高来确定它们的位置。

1.3 onDraw(Canvas canvas)

onDraw方法用于绘制View的内容。在自定义View中,我们需要重写这个方法,利用Canvas进行绘制操作,如绘制形状、文本、图片等。

二、自定义View案例

下面我们将通过一个简单的自定义View案例来演示如何使用这三个方法。我们将创建一个名为CircleView的自定义View,它会绘制一个带有边框的圆形。

2.1 创建CircleView类

首先,创建一个名为CircleView的类,继承自View,并实现构造方法。

public class CircleView extends View {

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

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

    public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2.2 重写onMeasure方法

在CircleView类中,重写onMeasure方法,根据MeasureSpec来计算并设置View的宽高。这里我们假设圆形的半径为100dp。

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

    int desiredSize = (int) (100 * getResources().getDisplayMetrics().density);

    int width = measureDimension(desiredSize, widthMode, widthSize);
    int height = measureDimension(desiredSize, heightMode, heightSize);

    setMeasuredDimension(width, height);
}

private int measureDimension(int desiredSize, int mode, int size) {
    int result;
    if (mode == MeasureSpec.EXACTLY) {
        result = size;
    } else if (mode == MeasureSpec.AT_MOST) {
        result = Math.min(desiredSize, size);
    } else {
        result = desiredSize;
    }
    return result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

2.3 重写onDraw方法

在CircleView类中,重写onDraw方法,使用Canvas绘制圆形和边框。

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    int width = getWidth();
    int height = getHeight();
    int radius = Math.min(width, height) / 2;

    Paint paint = new Paint();
    paint.setAntiAlias(true);

    // 绘制圆形
    paint.setColor(Color.BLUE);
    canvas.drawCircle(width / 2, height / 2, radius, paint);

    // 绘制边框
    paint.setColor(Color.BLACK);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(5);
    canvas.drawCircle(width / 2, height / 2, radius - 2.5f, paint);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

至此,我们已经完成了一个简单的自定义View——CircleView。在布局文件中使用这个自定义View,就可以看到一个带有边框的蓝色圆形。

通过这个案例,我们可以看到,onMeasure、onLayout和onDraw这三个方法在自定义View中的重要作用。onMeasure方法用于测量View的大小,onDraw方法用于绘制View的内容,而onLayout方法在此例中并未涉及,因为我们的CircleView直接继承自View,没有子View的布局需求。但如果我们需要自定义一个ViewGroup,那么onLayout方法将会用于确定子View的位置。

三、自定义ViewGroup案例

为了演示onLayout方法的使用,我们将创建一个名为CustomLayout的自定义ViewGroup,它将简单地将子View按照从左到右、从上到下的顺序排列。

3.1 创建CustomLayout类

首先,创建一个名为CustomLayout的类,继承自ViewGroup,并实现构造方法。

public class CustomLayout extends ViewGroup {

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

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

    public CustomLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

3.2 重写onMeasure方法

在CustomLayout类中,重写onMeasure方法,根据MeasureSpec来计算并设置ViewGroup的宽高。这里我们假设每个子View的宽高为100dp,水平间距和垂直间距均为20dp。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // 获取宽度和高度的测量模式和尺寸
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSize = MeasureSpec.getSize(heightMeasureSpec);

    // 定义子View的宽高和水平、垂直间距
    int childWidth = (int) (100 * getResources().getDisplayMetrics().density);
    int childHeight = (int) (100 * getResources().getDisplayMetrics().density);
    int horizontalSpacing = (int) (20 * getResources().getDisplayMetrics().density);
    int verticalSpacing = (int) (20 * getResources().getDisplayMetrics().density);

    // 初始化ViewGroup的宽高和当前行的宽高
    int width = 0;
    int height = 0;
    int rowWidth = 0;
    int rowHeight = childHeight;

    // 遍历所有子View
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);

        // 测量子View的大小
        measureChild(child, MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY));

        // 更新当前行的宽度
        rowWidth += childWidth + horizontalSpacing;

        // 检查当前行宽度是否超过ViewGroup的宽度
        if (rowWidth > widthSize) {
            // 更新ViewGroup的宽度
            width = Math.max(width, rowWidth - horizontalSpacing);

            // 累加高度
            height += rowHeight + verticalSpacing;

            // 重置当前行的宽度
            rowWidth = childWidth + horizontalSpacing;
        }
    }

    // 更新ViewGroup的宽度和高度
    width = Math.max(width, rowWidth - horizontalSpacing);
    height += rowHeight;

    // 设置ViewGroup的测量宽高
    setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width, heightMode == MeasureSpec.EXACTLY ? heightSize : height);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

在这段代码中,我们首先获取宽度和高度的测量模式和尺寸。然后,定义子View的宽高和水平、垂直间距,并初始化ViewGroup的宽高和当前行的宽高。接着遍历所有的子View,测量子View的大小,并更新当前行的宽度。检查当前行宽度是否超过ViewGroup的宽度,如果超过了,更新ViewGroup的宽度,累加高度,并重置当前行的宽度。最后,更新ViewGroup的宽度和高度,并设置ViewGroup的测量宽高。

3.3 重写onLayout方法

在CustomLayout类中,重写onLayout方法,根据子View的测量宽高来确定它们的位置。

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int width = getWidth();
    int childWidth = (int) (100 * getResources().getDisplayMetrics().density);
    int childHeight = (int) (100 * getResources().getDisplayMetrics().density);
    int horizontalSpacing = (int) (20 * getResources().getDisplayMetrics().density);
    int verticalSpacing = (int) (20 * getResources().getDisplayMetrics().density);

    int x = 0;
    int y = 0;

    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);

        if (x + childWidth > width) {
            x = 0;
            y += childHeight + verticalSpacing;
        }

        child.layout(x, y, x + childWidth, y + childHeight);
        x += childWidth + horizontalSpacing;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

至此,我们已经完成了一个简单的自定义ViewGroup——CustomLayout。在布局文件中使用这个自定义ViewGroup,然后添加多个子View,就可以看到它们按照从左到右、从上到下的顺序排列。

通过这个案例,我们可以看到,onLayout方法在自定义ViewGroup中的重要作用。它用于确定子View的位置,根据子View的测量宽高来进行布局。在实际开发中,我们可以根据需求自定义不同的布局方式,实现各种复杂的界面效果。

四、总结

通过本文的介绍,我们了解了onLayout、onMeasure和onDraw这三个方法在自定义View和自定义ViewGroup中的作用和用法。onMeasure方法用于测量View的大小,onDraw方法用于绘制View的内容,onLayout方法用于确定View的位置。掌握这三个方法对于实现自定义View和自定义ViewGroup至关重要,有助于我们在实际开发中更好地满足设计需求,提高界面的交互性和美观性。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/673428
推荐阅读
相关标签
  

闽ICP备14008679号