利用 onMeasure、onLayout、onDraw 创建自定义 View

在 Android 中,自定义 View 是实现特殊 UI 效果和控件的重要方式。掌握 onMeasureonLayoutonDraw 这三个方法,是写好自定义 View 的关键。本文通过一个示例,加强对这三个方法的理解。

三个关键方法介绍

1. onMeasure(int widthMeasureSpec, int heightMeasureSpec)

作用:测量 View 的尺寸。告诉系统"我希望的大小是多少"。

  • widthMeasureSpecheightMeasureSpec 是测量规格,包含模式和尺寸。
  • 根据父控件的要求和自己的内容,计算并调用 setMeasuredDimension(width, height),设置 View 最终尺寸。

简单理解: 测量阶段,告诉系统你需要多大空间。

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

作用:确定 View 在父布局中的位置和大小。

  • 对于普通 View,一般不用重写,因为它的位置由父布局决定。
  • 对于 ViewGroup(容器),重写此方法来布局子控件的位置。

简单理解: 布局阶段,确定View摆放的具体位置。

3. onDraw(Canvas canvas)

作用:绘制 View 的内容。

  • 这里写绘图代码,使用 Canvas 画图形、文本、图片等。
  • 只有 View 被测量和布局完后,系统才会调用 onDraw

简单理解: 绘制阶段,把你的内容画出来。

通过示例理解三者配合

需求

实现两个子View互相重叠的效果,并且绘制一个背景色

效果图

代码内容

初始化两个不同颜色的子view

js 复制代码
class CustomLayout @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : ViewGroup(context, attrs, defStyleAttr) {

    init {
        val view1 = View(context)
        view1.setBackgroundColor("#FF0000".toColorInt())
        addView(view1)

        val view2 = View(context)
        view2.setBackgroundColor("#00FF00".toColorInt())
        addView(view2)

        setWillNotDraw(false)
    }
    ...
}

先测量两个子View,扣去重叠大小后,计算出当前View的最终大小

js 复制代码
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    // 重叠大小
    val overlapping = 60
    // 子控件宽高
    val itemWidth = 400
    val itemHeight = 300

    // 先测量子控件
    for (i in 0 until 2) {
        val view = getChildAt(i)
        measureChild(
            view,
            MeasureSpec.makeMeasureSpec(itemWidth, MeasureSpec.EXACTLY),
            MeasureSpec.makeMeasureSpec(itemHeight, MeasureSpec.EXACTLY)
        )
    }

    // 本身大小
    val width = itemWidth * 2 - overlapping
    val height = itemHeight * 2 - overlapping
    setMeasuredDimension(width, height)
}

放置两个子View

js 复制代码
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    // 放置在右上角
    val view1 = getChildAt(0)
    view1.layout(measuredWidth - view1.measuredWidth, 0, measuredWidth, view1.measuredHeight)

    // 放置在左下角
    val view2 = getChildAt(1)
    view2.layout(0, measuredHeight - view2.measuredHeight, view2.measuredWidth, measuredHeight)
}

绘制背景色

js 复制代码
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    // 绘制一个浅色的背景
    canvas.drawColor("#FFF0F0F0".toColorInt())
}
相关推荐
鹏晨互联6 小时前
【Compose vs XML:边框内外间距的实现对比】
android·xml
Android系统攻城狮6 小时前
Android tinyalsa深度解析之pcm_plugin_write调用流程与实战(一百七十九)
android·pcm·tinyalsa·android16·音频进阶·android音频进阶
ID_180079054736 小时前
除了JSON,淘宝店铺商品API接口还支持哪些数据格式?
android·数据库
KillerNoBlood7 小时前
2026移动端跨平台开发面经总结
android·算法·flutter·ios·移动开发·鸿蒙·kmp
消失的旧时光-19437 小时前
Android / IoT 面试复盘总结:从 MQTT、TLS 到 JWT 权限体系(标准答案 + 工程理解 + 延伸知识链)
android·物联网·面试
林多8 小时前
【Android】 GPU过度绘制实现原理
android·gpu·性能·实现原理·过度绘制·overdraw
薄荷椰果抹茶8 小时前
手机端Obsidian安装与同步全攻略
android
醇氧8 小时前
CentOS 7安装 mysql-8.0.27-1.el7.x86_64.rpm 安装包
android·mysql·centos
号码认证服务9 小时前
给用户打电话,怎么在对方手机显示为“XX证券”?号码认证办理步骤
android·运维·服务器·ios·智能手机·iphone·webview
Kapaseker9 小时前
我为什么让 Toast 多弹了一次
android·kotlin