Android开发 - onMeasure、onLayout和onDraw方法解析

onLayout、onMeasure和onDraw方法介绍

onMeasure(int widthMeasureSpec, int heightMeasureSpec)

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

    • MeasureSpec:测量规范,以自定义View.xml视图为规范进行测量。在Android开发中,MeasureSpec是一个32位的int值,用于描述View的宽度和高度信息。它由两部分组成:模式(mode)尺寸(size),模式占据高2位,尺寸占据低30位。其中,它有3种模式:

      1. EXACTLY:精确模式,对应于LayoutParams中的match_parent和具体的数值,表示父View希望子View的大小应该是一个确切的值
      2. AT_MOST:最大模式,对应于LayoutParams中的wrap_content,表示子View的大小最多是指定的值,它可以决定自己的大小
      3. UNSPECIFIED:未指定模式,通常在系统内部使用,表示父View没有给子View任何限制,子View可以设置任何大小
      • 总的来说,MeasureSpec是Android中测量View大小的一个重要机制,它帮助我们理解和处理View的测量过程
    • widthMeasureSpecheightMeasureSpec:分别对应于自定义View.xml视图的宽度和高度信息。在测量View的过程中,父View会根据自己的尺寸和子ViewLayoutParams,计算出合适的widthMeasureSpecheightMeasureSpec,然后通过onMeasure方法传递给子View

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

  • onLayout方法用于确定View位置的属性。在自定义ViewGroup(视图容器基类:可以理解为它可以调用所有的View)中,我们需要重写这个方法,根据子View的测量宽高来确定它们的位置

onDraw(Canvas canvas)

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

自定义View案例

  • 下面我们将通过一个简单的自定义View案例来演示使用这三个方法绘制一个带有边框的圆形

创建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);
        }
    }
    

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

重写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);
    }
    

小结

  • 我们已经完成了一个简单的自定义View - CircleView。在布局文件xml中使用这个自定义View,就可以看到一个带有边框的蓝色圆形。通过这个案例可以看到onMeasureonDraw这两个方法在自定义View中的重要作用。onMeasure方法用于测量View的大小,onDraw方法用于绘制View的内容,而onLayout方法在此例中并未涉及,因为我们的CircleView直接继承自View,没有子View的布局需求。但如果我们需要自定义一个ViewGroup,那么onLayout方法将会用于确定子View的位置

自定义ViewGroup案例

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

创建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);
        }
    }
    

重写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;	//	ViewGroup的宽
        int height = 0;	//	ViewGroup的高
        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;		// rowWidth = childWidth + horizontalSpacing + rowWidth
    
            // 检查当前行宽度是否超过ViewGroup的宽度
            if (rowWidth > widthSize) {
                // 更新ViewGroup的宽度
                width = Math.max(width, rowWidth - horizontalSpacing);
    
                // 累加高度
                height += rowHeight + verticalSpacing;		// height = rowHeight + verticalSpacing + height
    
                // 重置当前行的宽度
                rowWidth = childWidth + horizontalSpacing;
            }
        }
    
        // 更新ViewGroup的宽度和高度
        width = Math.max(width, rowWidth - horizontalSpacing);
        height += rowHeight;	// height = rowHeight + height
    
        // 设置ViewGroup的测量宽高
        setMeasuredDimension(widthMode == MeasureSpec.EXACTLY ? widthSize : width, heightMode == MeasureSpec.EXACTLY ? heightSize : height);
    }
    
  • 在这段代码中,先获取宽度和高度的测量模式和尺寸。然后定义子View的宽高和水平、垂直间距,并初始化ViewGroup的宽高和当前行的宽高。接着遍历所有的子View,测量子View的大小,并更新当前行的宽度。检查当前行宽度是否超过ViewGroup的宽度,如果超过就更新ViewGroup的宽度并累加高度,并重置当前行的宽度。最后更新ViewGroup的宽度和高度,并设置ViewGroup的测量宽高

重写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;	// x = childWidth + horizontalSpacing + x
        }
    }
    

小结

  • 以上完成了一个简单的自定义ViewGroup - CustomLayout。在布局文件中使用这个自定义ViewGroup,然后添加多个子View,就可以看到它们按照从左到右、从上到下的顺序排列。通过这个案例我们可以看到onLayout方法自定义ViewGroup中的重要作用。它用于确定子View的位置,根据子View的测量宽高来进行布局。在实际开发中,我们可以根据需求自定义不同的布局方式,实现各种复杂的界面效果

总结

  • 通过本文了解了onLayoutonMeasureonDraw这三个方法在自定义View自定义ViewGroup中的作用和用法。onMeasure方法用于测量View的大小,onDraw方法用于绘制View的内容,onLayout方法用于确定子View的位置。这三个方法对于实现自定义View自定义ViewGroup至关重要,有助于我们在实际开发中更好地满足设计需求,提高界面的交互性和美观性
posted @ 2024-07-23 15:11  阿俊学JAVA  阅读(175)  评论(0编辑  收藏  举报