一、View 绘制流程
1. View 的绘制流程是什么?measure/layout/draw 分别做什么?
-
measure:测量 View 的宽高,从根 View 递归调用
measure()→onMeasure(),结果存在mMeasuredWidth/Height -
layout:确定 View 的位置,调用
layout()→onLayout(),子 View 一般在这里调用child.layout() -
draw:绘制内容,调用
draw()→ 依次执行drawBackground→onDraw→dispatchDraw→onDrawForeground
2. MeasureSpec 是什么?三种模式有什么区别?
| 模式 | 值 | 含义 |
|---|---|---|
| EXACTLY | 1 | 精确值,如 match_parent 或具体数值 |
| AT_MOST | 2 | 最大值,如 wrap_content |
| UNSPECIFIED | 0 | 无限制,如 ScrollView 中的子 View |
3. 为什么 onMeasure 会执行多次?
-
父 View 可能多次测量子 View 以确定最终尺寸
-
典型的如
RelativeLayout、LinearLayout的权重分配
4. getWidth() 和 getMeasuredWidth() 的区别?
-
getMeasuredWidth():measure 阶段获取的原始测量值 -
getWidth():layout 阶段确定的最终宽度(mRight - mLeft) -
通常两者相等,但在某些自定义布局中可能不同
二、自定义 View 的分类与选择
5. 自定义 View 有哪些实现方式?如何选择?
| 方式 | 适用场景 | 特点 |
|---|---|---|
| 继承 View | 完全自定义绘制 | 重写 onDraw,无子 View |
| 继承 ViewGroup | 自定义布局 | 重写 onLayout,管理子 View 位置 |
| 继承已有 View | 扩展功能 | 如继承 TextView 增加新属性 |
| 继承已有 ViewGroup | 组合控件 | 如继承 FrameLayout 封装复杂 UI |
6. 自定义 View 如何支持 wrap_content?
-
必须在
onMeasure中处理AT_MOST模式,否则wrap_content效果等同于match_parent -
示例:
java
if (widthMode == MeasureSpec.AT_MOST) {
widthSize = desiredWidth;
}
三、触摸事件分发(高频考点)
7. 事件分发的三个核心方法?
-
dispatchTouchEvent:分发事件,返回 true 表示消费 -
onInterceptTouchEvent:ViewGroup 专属,是否拦截事件 -
onTouchEvent:处理事件
8. 事件传递顺序?
plain
Activity.dispatchTouchEvent
→ PhoneWindow.DecorView.dispatchTouchEvent
→ ViewGroup.dispatchTouchEvent
→ onInterceptTouchEvent(返回 true 则拦截,调用自己的 onTouchEvent)
→ 不拦截则传给子 View
→ 子 View 的 dispatchTouchEvent → onTouchEvent
9. 如果子 View 不消费事件,事件会怎么处理?
-
子 View 的
onTouchEvent返回 false → 事件回传给父 View 的onTouchEvent -
如果所有 View 都不消费,最终回传给 Activity 的
onTouchEvent
10. requestDisallowInterceptTouchEvent 的作用?
-
子 View 调用此方法可以阻止父 View 拦截事件
-
常用于解决 ScrollView 嵌套 RecyclerView 的滑动冲突
四、自定义绘制
11. onDraw 中常用的 Canvas API 有哪些?
-
绘制基础图形:
drawLine、drawRect、drawCircle、drawOval、drawArc -
绘制文字:
drawText -
绘制路径:
drawPath -
绘制图片:
drawBitmap
12. Paint 常用属性?
-
setColor/setARGB:颜色 -
setStrokeWidth:描边宽度 -
setStyle:FILL(填充)、STROKE(描边)、FILL_AND_STROKE -
setAntiAlias:抗锯齿 -
setTextSize:文字大小
13. Path 的使用场景?
-
绘制贝塞尔曲线、复杂形状
-
moveTo、lineTo、quadTo(二阶贝塞尔)、cubicTo(三阶贝塞尔) -
addArc、addCircle等添加基本图形
14. Canvas.save() 和 Canvas.restore() 的作用?
-
save():保存当前 Canvas 状态(坐标系、裁剪区域等) -
restore():恢复到上一次 save 的状态 -
常用于临时变换坐标系后恢复
五、动画
15. Android 动画的分类?
| 类型 | 实现方式 | 特点 |
|---|---|---|
| 补间动画(View Animation) | AlphaAnimation、TranslateAnimation 等 | 只改变视觉效果,不改变实际属性 |
| 属性动画(Property Animation) | ValueAnimator、ObjectAnimator | 真正改变属性值,更灵活 |
| 帧动画(Drawable Animation) | AnimationDrawable | 逐帧播放图片 |
16. 属性动画的原理?
-
通过
ValueAnimator在时间内计算插值 -
利用反射或
setter方法不断更新目标属性 -
底层依赖
Choreographer的VSync信号驱动
17. 自定义 View 中如何实现属性动画?
-
定义属性对应的
setter方法 -
使用
ObjectAnimator.ofFloat(view, "propertyName", values...) -
或者使用
ValueAnimator+AnimatorUpdateListener手动更新
六、性能优化
18. 自定义 View 有哪些性能优化手段?
-
避免在
onDraw中创建对象:应提前初始化 Paint、Path 等 -
使用
clipRect和quickReject:减少不必要的绘制区域 -
开启硬件加速:
setLayerType(View.LAYER_TYPE_HARDWARE, null) -
使用
invalidate(Rect):局部刷新而非全屏刷新 -
避免过度绘制:减少重叠区域的绘制次数
19. 什么是双缓冲?自定义 View 中如何利用?
-
双缓冲:在内存中先绘制到 Bitmap,再一次性绘制到屏幕
-
自定义 View 中可通过
setLayerType或手动使用离屏 Bitmap 实现
20. invalidate() 和 requestLayout() 的区别?
-
invalidate():触发重绘,调用onDraw,不改变尺寸 -
requestLayout():触发重新测量和布局,调用onMeasure→onLayout
七、其他细节问题
21. 自定义属性(attrs)如何定义和使用?
-
在
res/values/attrs.xml中定义<declare-styleable> -
在构造方法中通过
TypedArray获取属性值 -
记得
typedArray.recycle()回收
22. 自定义 View 的三个构造方法分别什么时候调用?
-
一参:代码中
new View(context) -
二参:XML 中使用
-
三参:XML 中使用 + 指定 style
-
四参(API 21+):XML 中使用 + 指定 theme attribute
23. postInvalidate() 和 invalidate() 的区别?
-
invalidate():必须在 UI 线程调用 -
postInvalidate():内部通过 Handler 投递到 UI 线程,可在子线程调用
24. 如何解决自定义 View 的内存泄漏?
-
及时移除动画监听和 Handler 回调
-
避免在
onDraw中创建大量临时对象 -
页面销毁时停止动画
八、实战场景题
25. 如何实现一个圆形进度条?
-
思路:继承 View,在
onDraw中用drawArc绘制背景圆弧和进度圆弧 -
通过属性动画更新进度值,调用
invalidate重绘
26. 如何实现一个可拖拽的 View?
-
在
onTouchEvent中处理ACTION_DOWN、ACTION_MOVE、ACTION_UP -
ACTION_MOVE时计算偏移量,调用layout()或offsetLeftAndRight()更新位置 -
或使用
ViewDragHelper(更推荐)
27. 如何实现一个折线图/柱状图?
-
计算坐标映射关系
-
使用
Path连接数据点 -
考虑边缘 padding、动画过渡效果
&spm=1001.2101.3001.5002&articleId=163784795&d=1&t=3&u=865f1c602769486dbbb3a39d67e9a83b)
714

被折叠的 条评论
为什么被折叠?



