安卓白话文面试(7) —— 自定义View?手绘一个简单的仪表盘

9 阅读4分钟

你有没有试过在纸上画一个仪表盘?先拿圆规画个圆,再标上刻度,最后画根指针。自定义View就像这个过程——系统提供了画布和画笔,但具体画什么、怎么画,全凭你自己。


1. 测量尺寸?先量好画纸有多大

比喻:你拿到一张白纸,准备画仪表盘。但纸可能被折过、剪过,你得先知道有效画布有多大。如果纸太小,圆画不完整;纸太大,画面又太空。

技术点:自定义View的第一步是onMeasure()。系统会问:“你要占多大地方?”你需要测量自己的宽高,然后告诉系统。比如你希望仪表盘是正方形,那就在onMeasure()里根据父布局给的建议尺寸(MeasureSpec),算出最终宽高,调用setMeasuredDimension()定下来。说白了,就是告诉Android:“我就要这么大,多了我不要,少了会变形。”

至于onLayout(),那是ViewGroup安排子View坐标的活儿,咱们画单个仪表盘用不上——就像你只在一张纸上画一个圆,不需要考虑圆内部还有别的小图形怎么摆。


2. 绘制内容?拿起画笔开始涂鸦

比喻:尺寸定好了,现在你真要画了。先画个灰色的圆盘,再画刻度线——短的、长的,还要写上数字。最后用红色画一根指针,指向某个角度。每一步都是一笔一笔画上去的

技术点onDraw(Canvas canvas)就是你的手,Paint就是你的笔。
下面是一个极简的指针绘制片段(感受一下旋转画布的用法):

canvas.save();                     // 保存当前画布状态
canvas.rotate(angle, centerX, centerY); // 旋转画布到指针角度
canvas.drawLine(centerX, centerY - radius,
                centerX, centerY, paint); // 画指针
canvas.restore();                  // 恢复画布,不影响画刻度
  • canvas.drawCircle()画圆盘
  • canvas.drawLine()画刻度线(循环60次,每6°一条短刻;再循环12次,每30°一条长刻)
  • canvas.drawText()写数字

记住:绘制的顺序就是覆盖的顺序——先画的在底层,后画的在上面。


3. 自定义属性?给仪表盘换个颜色

比喻:你画了好几个仪表盘,有的想要蓝色背景,有的想要红色指针。难道每次都改代码?不,你可以提前留好“可调参数”,比如“盘面颜色”、“指针颜色”、“最大刻度值”。使用时像填空一样填进去就行。

技术点:在res/values/attrs.xml里声明自定义属性(如circleColorpointerColor),然后在View的构造方法里通过obtainStyledAttributes()读取XML里填的值。注意:用完要调用recycle()回收,否则会内存泄漏——就像借了画笔要记得还。这样你在布局文件里写app:circleColor="#FF0000"就能直接换颜色。


4. 触摸交互?让指针跟着手指转

比喻:你画了一个真正的仪表盘,现在用户想用手拨动指针。你得感应手指的位置,算出角度,然后重新画一遍——看起来指针就转过来了。

技术点:重写onTouchEvent(),拿到手指坐标(x, y),用Math.atan2()计算相对于View中心的夹角,转换成0~360度。然后调用invalidate()触发重绘,onDraw里根据新角度画指针。

注意invalidate()只会重绘View自身区域,但onDraw里的计算逻辑(比如三角函数、Path运算)仍然消耗CPU。如果手指滑动时一帧触发60次重绘,且onDraw里有复杂运算,照样会卡顿。优化建议:用postInvalidateDelayed(16)限制刷新率,或者把刻度盘等静态元素提前画成Bitmap复用。说白了,不要以为重绘就是免费的


总结表格

环节生活比喻技术方法核心作用
测量尺寸量画纸大小onMeasure()确定View宽高
绘制内容拿画笔涂鸦onDraw(Canvas)画出形状、文字、图片
自定义属性预留可调参数attrs.xml + 构造方法读取布局时动态配置样式
触摸交互手指拨指针onTouchEvent() + invalidate()响应手势,更新界面

面试官爱怎么问?

:自定义View有三个关键方法,分别是什么?
onMeasure()(测量尺寸)、onLayout()(确定位置,ViewGroup才需要)、onDraw()(绘制内容)。就像画画的三步:先量纸有多大,再摆放在哪,最后动笔画

:为什么自定义View的onDraw里不要用new对象?
:因为onDraw会频繁调用(比如手指滑动时一帧60次),每次new对象就像画画前重新磨墨——不仅慢,还会触发大量GC导致掉帧。应该把PaintPath这些对象提前创建好,在onDraw里复用。

问(扩展)onMeasure()里的MeasureSpec三种模式怎么理解?
EXACTLY是“纸就这么大,必须画满”;AT_MOST是“纸最大这么大,你可以画小点”;UNSPECIFIED是“你想画多大就多大,不限制”。就像老师给画纸:第一种指定A4纸,第二种说“不能超过A4”,第三种说“给你一卷无限长的纸”。


人话总结

自定义View就是把Canvas当画板,Paint当画笔,onDraw当你的右手——你画什么,用户就看到什么。但记住,画得别太费劲,否则会手抖(掉帧)。


汇总导航

安卓白话文面试导航