一、View绘制流程核心考点
在Android面试中,View绘制流程是必问的基础题。面试官通常要求候选人详细描述从Activity启动到View最终渲染在屏幕上的全过程。
1.1 三大方法:measure, layout, draw
理解 View 的绘制过程,关键在于掌握 measure()、layout() 和 draw() 这三个核心方法。
View测量:通过 onMeasure() 确定View的宽和高。如果是自定义View,通常需要重写此方法,根据传入的 MeasureSpec 来计算自身的尺寸。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// 自定义测量逻辑
int width = resolveSize(size, widthMeasureSpec);
setMeasuredDimension(width, height);
}
View布局:通过 onLayout() 确定View在父容器中的位置。对于 ViewGroup,需要遍历子View并调用其 layout() 方法。
View绘制:通过 onDraw() 将View绘制到Canvas上。这是自定义UI效果的核心环节,可以使用 Paint 和 Canvas 进行各种绘图操作。
MeasureSpec 的三种模式:UNSPECIFIED, EXACTLY, AT_MOST。理解父容器如何传递约束给子View。
二、View事件分发机制深度解析
事件分发是Android交互的核心,也是面试中的高频难点。主要涉及三个方法:dispatchTouchEvent、onInterceptTouchEvent 和 onTouchEvent。
ViewGroup 事件分发流程
- dispatchTouchEvent:负责事件的分发。如果返回 true,表示消费该事件;返回 false 或 super,则交由子View处理或传递给父容器。
- onInterceptTouchEvent:负责事件的拦截。只有ViewGroup才有此方法。如果返回 true,表示拦截事件,后续事件将直接交给ViewGroup的
onTouchEvent处理。 - onTouchEvent:负责处理事件。如果返回 true,表示消费;返回 false,表示事件传递给父容器的
onTouchEvent。
口诀:“从上往下分发,从下往上传递”。分发看 dispatch,拦截看 intercept,处理看 onTouch。
View 事件处理流程
View 没有 onInterceptTouchEvent。其事件处理流程相对简单:
dispatchTouchEvent:将事件分发给onTouchEvent或OnTouchListener。setOnTouchListener的onTouch方法优先级高于onTouchEvent。onTouchEvent:处理点击、滑动等事件。如果View可点击且事件为点击,则返回 true。
常见事件冲突与解决
在实际开发中,ViewPager 与 ScrollView 的滑动冲突最常见。解决方案通常包括:
- 外部拦截法:在父View的
onInterceptTouchEvent中判断,如果是横向滑动则拦截,纵向滑动不拦截。 - 内部拦截法:配合
requestDisallowInterceptTouchEvent使用,子View根据滑动方向请求父View是否拦截事件。
三、属性动画与ValueAnimator
属性动画是Android 3.0引入的强大动画系统,相比传统的 Tween动画,属性动画可以真正改变View的属性值,并支持自定义插值器和估值器。
3.1 ObjectAnimator 核心用法
使用 ObjectAnimator 可以非常方便地实现平移、旋转、缩放、透明度等动画效果。需要注意的是,目标属性必须有对应的 setXxx() 方法。
ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationX", 0, 100, 0);
animator.setDuration(500);
animator.start();
3.2 自定义估值器 (TypeEvaluator)
对于非基础类型(如颜色、贝塞尔曲线点),需要自定义 TypeEvaluator 来计算中间值。这是实现复杂自定义View动画的关键。
| 动画类型 | 对应属性 | 说明 |
|---|---|---|
| Alpha | alpha | 透明度变化,0.0 - 1.0 |
| Translation | translationX/Y | 位置移动 |
| Scale | scaleX/Y | 缩放比例 |
| Rotation | rotation | 旋转角度 |
四、高阶实战:贝塞尔曲线与性能优化
4.1 贝塞尔曲线在自定义View中的应用
贝塞尔曲线常用于实现水波纹、下拉刷新、波浪效果等。Android中的 Path 类提供了 quadTo(二阶)和 cubicTo(三阶)方法。
面试中常问:如何结合属性动画实现动态的波浪效果?
解答思路:利用 ValueAnimator 改变控制点的坐标,每一帧重新绘制 Path,从而实现动态效果。
4.2 自定义View性能优化
在 onDraw 方法中,严禁进行对象分配、数据库操作或网络请求。应遵循以下原则:
- 避免创建对象:在
onDraw中复用Paint和Path对象。 - 减少绘制范围:使用
canvas.clipRect()裁剪不需要绘制的区域。 - 硬件加速:了解哪些API支持硬件加速,哪些不支持,避免在复杂View中频繁切换。
- 双层缓冲:对于极其复杂的绘制,可以使用
Bitmap离屏绘制,减少重绘开销。
五、网友们还关心:高频面试Q&A
以下是求职者最常搜索的 Android自定义View面试题 及其深度解答。
场景主要分为三种:外部滑动与内部滑动方向不一致、外部滑动与内部滑动方向一致、外部滑动与内部滑动的嵌套。
解决方法主要推荐外部拦截法,因为逻辑更清晰,符合Android事件分发机制的设计初衷。内部拦截法需要配合 requestDisallowInterceptTouchEvent,逻辑较为复杂。
1. 必须正确处理子View的 measure 和 layout。
2. 注意 onMeasure 中 child.measure() 的调用。
3. 处理 onLayout 时的边界情况,如子View超出范围。
4. 考虑 Margin 和 Padding 的影响。
在 onMeasure 中,当 MeasureSpecMode 为 AT_MOST 时,需要遍历子View或计算自身内容尺寸,取子View最大尺寸与父容器限制的最小值作为最终尺寸。