在 Android 中,自定义 View 是实现特殊 UI 效果和控件的重要方式。掌握 onMeasure
、onLayout
和 onDraw
这三个方法,是写好自定义 View 的关键。本文通过一个示例,加强对这三个方法的理解。
三个关键方法介绍
1. onMeasure(int widthMeasureSpec, int heightMeasureSpec)
作用:测量 View 的尺寸。告诉系统"我希望的大小是多少"。
widthMeasureSpec
和heightMeasureSpec
是测量规格,包含模式和尺寸。- 根据父控件的要求和自己的内容,计算并调用
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())
}