01-02-15 View系统深入总结与实战路径
系列完结篇 | 回顾14篇核心知识 | 构建完整知识体系 | 指明实战方向
📋 目录
- 系列回顾与知识体系
- 核心知识点速查表
- View绘制流程完整路径
- 自定义View开发全流程
- 触摸事件处理决策树
- 动画系统使用指南
- 性能优化检查清单
- 常见问题与解决方案
- 实战项目路线图
- 进阶学习资源
1. 系列回顾与知识体系
1.1 系列文章全览
本系列共 15 篇文章,涵盖 Android View 系统的 6 大核心主题:
View系统知识体系
┌─────────────────────────────────────────────────────────────┐
│ 01 - Canvas与Paint基础 │
│ (绘制的基石 - 工具篇) │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ 02 - View生命周期与绘制流程 │
│ (理解View运作机制 - Measure/Layout/Draw) │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ 03 - View测量体系 MeasureSpec │
│ (精确控制尺寸 - AT_MOST/EXACTLY/UNSPECIFIED) │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────┼───────────────────┐
│ │ │
┌───────▼──────┐ ┌────────▼───────┐ ┌───────▼──────┐
│04 - 事件分发 │ │ 05 - 手势识别 │ │ 06 - 属性动画│
│ (触摸交互) │ │(GestureDetector)│ │(ObjectAnimator)│
└───────┬──────┘ └────────┬───────┘ └───────┬──────┘
│ │ │
└───────────────────┼───────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ 07 - Android高级动画技巧 │
│ (ValueAnimator/TypeEvaluator/Interpolator) │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ 08-14 - 自定义View实战系列 │
│ 08: 实战总纲 | 09: ViewGroup | 10: 最佳实践 | 11: 状态管理│
│ 12: 焦点输入 | 13: 滑动冲突 | 14: 绘制进阶 │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ 15 - View系统深入总结 (本篇) │
│ (知识体系梳理 + 实战路径规划) │
└─────────────────────────────────────────────────────────────┘
1.2 知识体系分层
┌──────────────────────────────────────────────────────────┐
│ 理论层 (Theory) │
│ View体系结构 | 绘制流程 | MeasureSpec | 事件分发原理 │
├──────────────────────────────────────────────────────────┤
│ 工具层 (Tools) │
│ Canvas API | Paint | Path | Matrix | Shader | PorterDuff│
├──────────────────────────────────────────────────────────┤
│ 交互层 (Interaction) │
│ onTouchEvent | GestureDetector | ScaleGestureDetector │
├──────────────────────────────────────────────────────────┤
│ 动画层 (Animation) │
│ ObjectAnimator | ValueAnimator | Interpolator │
├──────────────────────────────────────────────────────────┤
│ 实战层 (Practice) │
│ 自定义View实现 | 性能优化 | 问题调试 │
└──────────────────────────────────────────────────────────┘
2. 核心知识点速查表
2.1 View绘制流程三部曲
| 阶段 |
关键方法 |
核心作用 |
常见场景 |
相关文章 |
| Measure |
onMeasure() |
测量View尺寸 |
自适应宽高、响应式布局 |
#02, #03 |
| Layout |
onLayout() |
确定View位置 |
ViewGroup子View排列 |
#02, #09 |
| Draw |
onDraw() |
绘制View内容 |
所有自定义View |
#01, #02, #08 |
Measure 关键要点
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val width = when (widthMode) {
MeasureSpec.EXACTLY -> widthSize
MeasureSpec.AT_MOST -> min(desiredWidth, widthSize)
else -> desiredWidth
}
setMeasuredDimension(width, height)
}
Layout 关键要点
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
for (i in 0 until childCount) {
val child = getChildAt(i)
child.layout(childLeft, childTop, childRight, childBottom)
}
}
Draw 关键要点
override fun onDraw(canvas: Canvas) {
canvas.drawCircle(centerX, centerY, radius, paint)
}
2.2 Canvas 绘制 API 速查
| 分类 |
方法 |
用途 |
示例场景 |
文章 |
| 基本图形 |
drawCircle() |
绘制圆形 |
圆形进度条、头像 |
#01 |
|
drawRect() |
绘制矩形 |
进度条背景、卡片 |
#01 |
|
drawLine() |
绘制直线 |
分割线、网格 |
#01 |
|
drawArc() |
绘制圆弧 |
扇形进度、仪表盘 |
#08 |
| 路径绘制 |
drawPath() |
绘制路径 |
波浪、不规则图形 |
#01, #14 |
| 文字绘制 |
drawText() |
绘制文字 |
标签、数值显示 |
#01 |
| 图片绘制 |
drawBitmap() |
绘制位图 |
图标、背景图 |
#01 |
| 裁剪 |
clipRect() |
矩形裁剪 |
局部绘制区域 |
#14 |
|
clipPath() |
路径裁剪 |
不规则裁剪区域 |
#14 |
| 变换 |
translate() |
平移 |
坐标偏移 |
#01 |
|
rotate() |
旋转 |
指针旋转、时钟 |
#01 |
|
scale() |
缩放 |
放大缩小 |
#01 |
Canvas 坐标系统
Canvas 坐标系 (左上角为原点)
┌─────────────────────► X轴 (向右为正)
│
│ (0,0)
│ ┌───────────────────┐
│ │ │
│ │ View绘制区域 │
│ │ │
│ │ (centerX, centerY)
│ │ ● │
│ │ │
│ └───────────────────┘
│ (width, height)
▼
Y轴 (向下为正)
2.3 触摸事件处理速查
| 事件 |
常量 |
含义 |
处理要点 |
文章 |
| 按下 |
ACTION_DOWN |
手指触摸屏幕 |
记录起始位置、返回true拦截 |
#04, #05 |
| 移动 |
ACTION_MOVE |
手指在屏幕上移动 |
计算位移量、触发拖动 |
#04, #05 |
| 抬起 |
ACTION_UP |
手指离开屏幕 |
判断点击/滑动、触发动画 |
#04, #05 |
| 取消 |
ACTION_CANCEL |
事件被父View拦截 |
重置状态、停止动画 |
#04, #13 |
事件流向
Activity.dispatchTouchEvent()
↓
ViewGroup.dispatchTouchEvent()
↓ (如果不拦截)
ViewGroup.onInterceptTouchEvent()
↓ (返回false)
Child View.dispatchTouchEvent()
↓
Child View.onTouchEvent()
↓ (返回true表示消费)
事件链终止
2.4 动画系统速查
| 动画类型 |
类名 |
特点 |
适用场景 |
文章 |
| 属性动画 |
ObjectAnimator |
修改对象属性 |
简单属性变化 |
#06 |
|
ValueAnimator |
值变化回调 |
复杂自定义动画 |
#07 |
|
AnimatorSet |
组合多个动画 |
复杂动画编排 |
#07 |
| 插值器 |
Interpolator |
控制速度曲线 |
加速/减速效果 |
#07 |
| 估值器 |
TypeEvaluator |
计算中间值 |
非标准类型动画 |
#07 |
常用插值器
| 插值器 |
效果 |
公式 |
使用场景 |
LinearInterpolator |
匀速 |
y = x |
旋转、匀速移动 |
AccelerateInterpolator |
加速 |
y = x² |
物体下落 |
DecelerateInterpolator |
减速 |
y = 1-(1-x)² |
滑动停止 |
AccelerateDecelerateInterpolator |
先加速后减速 |
y = cos((x+1)π)/2+0.5 |
平滑过渡 |
OvershootInterpolator |
超出后回弹 |
复杂公式 |
弹性动画 |
3. View绘制流程完整路径
3.1 从setContentView到屏幕显示
用户操作流程:
┌──────────────────────────────────────────────────────────┐
│ 1. Activity.setContentView(R.layout.activity_main) │
└────────────────────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 2. LayoutInflater 解析 XML │
│ → 创建 View 对象 │
│ → 解析布局参数 (LayoutParams) │
│ → 递归创建子 View │
└────────────────────────┬─────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 3. ViewRootImpl 触发绘制 │
│ → performTraversals() │
└────────────────────────┬─────────────────────────────────┘
│
┌────────────────┼────────────────┐
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│Measure │ │ Layout │ │ Draw │
│ 测量 │ → │ 布局 │ → │ 绘制 │
└────────┘ └────────┘ └────────┘
│ │ │
└────────────────┼────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ 4. 硬件加速渲染 │
│ → RenderThread 处理 │
│ → GPU 加速绘制 │
│ → 刷新到屏幕 │
└──────────────────────────────────────────────────────────┘
3.2 自定义View必须实现的方法
class CustomView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
}
override fun onDraw(canvas: Canvas) {
}
override fun onTouchEvent(event: MotionEvent): Boolean {
return true
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
}
override fun onSaveInstanceState(): Parcelable? {
}
override fun onRestoreInstanceState(state: Parcelable?) {
}
}
4. 自定义View开发全流程
4.1 六步开发流程
Step 1: 需求分析与设计
┌─────────────────────────────────────┐
│ □ 明确功能需求 │
│ □ 确定交互方式 │
│ □ 设计UI效果图 │
│ □ 评估技术难点 │
└────────────┬────────────────────────┘
│
Step 2: 创建View类与属性
┌────────────▼────────────────────────┐
│ □ 继承View或ViewGroup │
│ □ 定义自定义属性 (attrs.xml) │
│ □ 解析自定义属性 │
│ □ 初始化Paint/Path等对象 │
└────────────┬────────────────────────┘
│
Step 3: 实现测量逻辑
┌────────────▼────────────────────────┐
│ □ 重写 onMeasure() │
│ □ 处理 wrap_content │
│ □ 处理 padding │
│ □ setMeasuredDimension() │
└────────────┬────────────────────────┘
│
Step 4: 实现绘制逻辑
┌────────────▼────────────────────────┐
│ □ 重写 onDraw() │
│ □ 绘制背景/内容/装饰 │
│ □ 考虑性能优化 │
└────────────┬────────────────────────┘
│
Step 5: 实现交互逻辑
┌────────────▼────────────────────────┐
│ □ 重写 onTouchEvent() │
│ □ 处理手势识别 │
│ □ 触发回调/动画 │
└────────────┬────────────────────────┘
│
Step 6: 测试与优化
┌────────────▼────────────────────────┐
│ □ 功能测试 │
│ □ 性能测试 (过度绘制/内存泄漏) │
│ □ 适配测试 (多屏幕尺寸) │
│ □ 代码优化 │
└─────────────────────────────────────┘
4.2 开发模板代码
class CustomView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var customProperty: Int = 0
set(value) {
field = value
invalidate()
}
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
color = Color.BLUE
style = Paint.Style.FILL
}
private val path = Path()
private var viewWidth = 0
private var viewHeight = 0
init {
context.obtainStyledAttributes(attrs, R.styleable.CustomView).apply {
customProperty = getInt(R.styleable.CustomView_customProperty, 0)
recycle()
}
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val heightMode = MeasureSpec.getMode(heightMeasureSpec)
val heightSize = MeasureSpec.getSize(heightMeasureSpec)
val desiredWidth = 200.dp
val desiredHeight = 200.dp
val width = when (widthMode) {
MeasureSpec.EXACTLY -> widthSize
MeasureSpec.AT_MOST -> min(desiredWidth, widthSize)
else -> desiredWidth
}
val height = when (heightMode) {
MeasureSpec.EXACTLY -> heightSize
MeasureSpec.AT_MOST -> min(desiredHeight, heightSize)
else -> desiredHeight
}
setMeasuredDimension(width, height)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
viewWidth = w
viewHeight = h
calculateDrawParams()
}
private fun calculateDrawParams() {
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.drawCircle(
viewWidth / 2f,
viewHeight / 2f,
min(viewWidth, viewHeight) / 4f,
paint
)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
return true
}
MotionEvent.ACTION_MOVE -> {
invalidate()
}
MotionEvent.ACTION_UP -> {
performClick()
}
}
return super.onTouchEvent(event)
}
override fun performClick(): Boolean {
return super.performClick()
}
override fun onSaveInstanceState(): Parcelable {
val superState = super.onSaveInstanceState()
val savedState = SavedState(superState)
savedState.customProperty = customProperty
return savedState
}
override fun onRestoreInstanceState(state: Parcelable?) {
if (state is SavedState) {
super.onRestoreInstanceState(state.superState)
customProperty = state.customProperty
} else {
super.onRestoreInstanceState(state)
}
}
private class SavedState : BaseSavedState {
var customProperty: Int = 0
constructor(superState: Parcelable?) : super(superState)
constructor(source: Parcel) : super(source) {
customProperty = source.readInt()
}
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
out.writeInt(customProperty)
}
companion object {
@JvmField
val CREATOR = object : Parcelable.Creator<SavedState> {
override fun createFromParcel(source: Parcel) = SavedState(source)
override fun newArray(size: Int) = arrayOfNulls<SavedState>(size)
}
}
}
private val Int.dp: Int
get() = (this * resources.displayMetrics.density).toInt()
private val Float.dp: Float
get() = this * resources.displayMetrics.density
}
5. 触摸事件处理决策树
5.1 事件分发决策流程
onTouchEvent 返回值决策树:
收到触摸事件
│
├─ ACTION_DOWN
│ │
│ └─ 问: 是否要处理后续事件?
│ ├─ 是 → return true (拦截事件流)
│ └─ 否 → return false/super (不消费)
│
├─ ACTION_MOVE
│ │
│ └─ 问: 是否之前返回了true?
│ ├─ 是 → 可以收到MOVE事件
│ └─ 否 → 收不到MOVE事件
│
└─ ACTION_UP
│
└─ 问: 是否之前返回了true?
├─ 是 → 可以收到UP事件
└─ 否 → 收不到UP事件
5.2 常见触摸场景处理
| 场景 |
DOWN |
MOVE |
UP |
实现要点 |
文章 |
| 点击 |
true |
忽略 |
触发onClick |
判断位移量<阈值 |
#04, #05 |
| 长按 |
true |
忽略 |
取消长按 |
Handler.postDelayed |
#05 |
| 拖动 |
true |
更新位置 |
松开 |
translationX/Y |
#04, #05 |
| 滑动 |
true |
跟随滑动 |
惯性滑动 |
VelocityTracker |
#05, #13 |
| 缩放 |
true |
计算缩放 |
结束 |
ScaleGestureDetector |
#05 |
6. 动画系统使用指南
6.1 动画选择决策树
需要添加动画效果
│
└─ 问: 动画的目标是什么?
│
├─ 简单属性变化 (alpha, translation, rotation, scale)
│ └─ 使用 ObjectAnimator.ofFloat/ofInt()
│
├─ 复杂自定义动画 (颜色渐变、路径动画)
│ └─ 使用 ValueAnimator + TypeEvaluator
│
├─ 多个动画组合
│ └─ 使用 AnimatorSet (play/with/after)
│
└─ 特殊速度曲线
└─ 使用 Interpolator (加速/减速/回弹)
6.2 常用动画模式
ObjectAnimator.ofFloat(view, "alpha", 0f, 1f).apply {
duration = 300
interpolator = DecelerateInterpolator()
}.start()
ObjectAnimator.ofFloat(view, "translationY", 0f, 100f).apply {
duration = 500
}.start()
ObjectAnimator.ofFloat(view, "rotation", 0f, 360f).apply {
duration = 1000
repeatCount = ValueAnimator.INFINITE
interpolator = LinearInterpolator()
}.start()
AnimatorSet().apply {
playTogether(
ObjectAnimator.ofFloat(view, "scaleX", 1f, 1.5f, 1f),
ObjectAnimator.ofFloat(view, "scaleY", 1f, 1.5f, 1f)
)
duration = 300
}.start()
ValueAnimator.ofFloat(0f, 1f).apply {
duration = 1000
addUpdateListener { animator ->
val fraction = animator.animatedValue as Float
customView.setProgress(fraction)
}
}.start()
7. 性能优化检查清单
7.1 绘制性能优化
✅ 性能优化检查清单
□ onDraw() 中避免对象分配
❌ val paint = Paint() // 每次onDraw都创建新对象
✅ private val paint = Paint() // 成员变量复用
□ 避免过度绘制
• 使用 clipRect() 限制绘制区域
• 移除不可见的背景
• 使用 ViewStub/ConstraintLayout 减少层级
□ 使用硬件加速
• Android 3.0+ 默认开启
• 复杂路径使用 View Layer
• 避免硬件加速不支持的API (drawPath某些情况)
□ Canvas 操作优化
• save()/restore() 配对使用
• 使用 saveLayer() 时指定CLIP_TO_LAYER_SAVE_FLAG
• 避免频繁调用 Canvas变换
□ Bitmap 优化
• 使用 BitmapFactory.Options 压缩
• 及时 recycle() 大 Bitmap
• 使用 LruCache 缓存
□ Path 操作优化
• Path 对象复用
• 使用 Path.rewind() 而非new Path()
• 复杂 Path 考虑缓存
□ Paint 优化
• 关闭抗锯齿 (不需要平滑时)
• 使用 Paint.Style.FILL (比STROKE快)
7.2 内存优化
class CustomView(context: Context) : View(context) {
private val handler = Handler()
init {
handler.postDelayed({
invalidate()
}, 5000)
}
}
class CustomView(context: Context) : View(context) {
private val handler = Handler(Looper.getMainLooper())
private val invalidateRunnable = Runnable {
invalidate()
}
fun startAnimation() {
handler.postDelayed(invalidateRunnable, 5000)
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
handler.removeCallbacks(invalidateRunnable)
}
}
8. 常见问题与解决方案
8.1 测量问题
| 问题 |
原因 |
解决方案 |
文章 |
| wrap_content 不生效 |
未处理 AT_MOST |
onMeasure中处理wrap_content |
#03 |
| padding 无效 |
未考虑 padding |
计算尺寸时减去padding |
#03 |
| 子View尺寸错误 |
未调用 measureChild |
ViewGroup中测量子View |
#09 |
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthMode = MeasureSpec.getMode(widthMeasureSpec)
val widthSize = MeasureSpec.getSize(widthMeasureSpec)
val desiredWidth = defaultWidth + paddingLeft + paddingRight
val width = when (widthMode) {
MeasureSpec.EXACTLY -> widthSize
MeasureSpec.AT_MOST -> min(desiredWidth, widthSize)
else -> desiredWidth
}
setMeasuredDimension(width, height)
}
8.2 绘制问题
| 问题 |
原因 |
解决方案 |
文章 |
| 内容被裁剪 |
绘制超出边界 |
检查坐标计算 |
#01, #08 |
| 文字显示不全 |
未计算文字高度 |
使用 Paint.getTextBounds() |
#01 |
| 圆角不平滑 |
未开启抗锯齿 |
Paint.ANTI_ALIAS_FLAG |
#01 |
| 渐变色不正确 |
Shader 位置错误 |
检查 Shader 坐标 |
#14 |
8.3 触摸事件问题
| 问题 |
原因 |
解决方案 |
文章 |
| 收不到 MOVE/UP |
DOWN返回false |
DOWN中返回true |
#04 |
| 事件被父View拦截 |
父View拦截了 |
requestDisallowInterceptTouchEvent |
#04, #13 |
| 滑动冲突 |
内外滑动方向冲突 |
使用外部拦截或内部拦截法 |
#13 |
9. 实战项目路线图
9.1 初级项目 (1-2周)
项目1: 圆形进度条 ⭐⭐
├─ 技术点: drawArc, SweepGradient, ValueAnimator
├─ 学习目标: 掌握基本绘制和动画
└─ 参考文章: #01, #06, #08
项目2: 评分控件 ⭐⭐
├─ 技术点: Path绘制, onTouchEvent, 状态保存
├─ 学习目标: 掌握触摸交互和状态管理
└─ 参考文章: #04, #05, #08
项目3: 自定义开关按钮 ⭐⭐
├─ 技术点: 拖动手势, 状态切换, 颜色渐变
├─ 学习目标: 掌握拖动手势和状态切换
└─ 参考文章: #04, #05, #06
9.2 中级项目 (2-3周)
项目4: 仪表盘控件 ⭐⭐⭐⭐
├─ 技术点: 三角函数, drawArc, 刻度绘制, 指针旋转
├─ 学习目标: 掌握复杂图形绘制和坐标计算
└─ 参考文章: #01, #08, #14
项目5: 波浪效果View ⭐⭐⭐⭐
├─ 技术点: 贝塞尔曲线, 正弦函数, ValueAnimator
├─ 学习目标: 掌握路径动画和数学运算
└─ 参考文章: #01, #06, #07, #14
项目6: 图表控件 (折线图/柱状图) ⭐⭐⭐⭐
├─ 技术点: Path, Matrix, 数据映射, 滑动交互
├─ 学习目标: 掌握数据可视化和复杂交互
└─ 参考文章: #01, #04, #05, #08
9.3 高级项目 (3-4周)
项目7: 自定义可拖拽布局 ⭐⭐⭐⭐⭐
├─ 技术点: ViewGroup, onLayout, 拖拽排序, 动画
├─ 学习目标: 掌握ViewGroup开发和复杂交互
└─ 参考文章: #02, #04, #05, #06, #09, #13
项目8: 刮刮卡效果 ⭐⭐⭐⭐⭐
├─ 技术点: PorterDuff, 双层Canvas, Path, 擦除效果
├─ 学习目标: 掌握高级绘制技巧和图层混合
└─ 参考文章: #01, #14
项目9: 自定义图片编辑器 ⭐⭐⭐⭐⭐
├─ 技术点: Matrix变换, 手势缩放旋转, 裁剪, 滤镜
├─ 学习目标: 综合运用所有View技术
└─ 参考文章: #01, #04, #05, #06, #07, #14
9.4 项目选择建议
根据经验选择:
┌────────────────────────────────────────────┐
│ 零基础 → 从项目1开始 → 完成3个初级项目 │
│ 有基础 → 从项目4开始 → 完成2个中级项目 │
│ 要提升 → 从项目7开始 → 完成2个高级项目 │
└────────────────────────────────────────────┘
学习策略:
1. 先理解需求和效果
2. 拆解技术点 (绘制/测量/交互/动画)
3. 逐步实现功能
4. 优化性能和代码质量
5. 编写技术博客总结
10. 进阶学习资源
10.1 官方文档
📚 必读文档:
├─ View | developer.android.com
│ https://developer.android.com/reference/android/view/View
│
├─ Canvas | developer.android.com
│ https://developer.android.com/reference/android/graphics/Canvas
│
├─ 自定义View | developer.android.com/develop/ui/views/layout/custom-views
│
└─ 性能优化 | developer.android.com/topic/performance
10.2 系列文章回顾
| 文章编号 |
主题 |
重点内容 |
难度 |
| #01 |
Canvas与Paint基础 |
绘制API、Paint属性 |
⭐⭐ |
| #02 |
View生命周期与绘制流程 |
Measure/Layout/Draw |
⭐⭐⭐⭐ |
| #03 |
View测量体系 |
MeasureSpec、onMeasure |
⭐⭐⭐⭐ |
| #04 |
事件分发机制 |
dispatchTouchEvent |
⭐⭐⭐⭐⭐ |
| #05 |
手势识别 |
GestureDetector、多点触控 |
⭐⭐⭐⭐ |
| #06 |
属性动画核心 |
ObjectAnimator、ValueAnimator |
⭐⭐⭐ |
| #07 |
高级动画技巧 |
Interpolator、TypeEvaluator |
⭐⭐⭐⭐ |
| #08 |
自定义View实战 |
开发流程、实战案例 |
⭐⭐⭐⭐ |
| #09 |
ViewGroup自定义布局 |
onLayout、子View管理 |
⭐⭐⭐⭐⭐ |
| #10 |
开发最佳实践 |
性能优化、代码规范 |
⭐⭐⭐⭐ |
| #11 |
状态与可见性管理 |
状态保存、Visibility |
⭐⭐⭐ |
| #12 |
焦点与输入处理 |
Focus、IME、Accessibility |
⭐⭐⭐ |
| #13 |
滑动冲突解决 |
外部拦截、内部拦截 |
⭐⭐⭐⭐⭐ |
| #14 |
绘制进阶技巧 |
Shader、PorterDuff、裁剪 |
⭐⭐⭐⭐⭐ |
| #15 |
系统总结 (本篇) |
知识体系、实战路径 |
⭐⭐⭐⭐⭐ |
10.3 推荐学习路径
📖 推荐学习路径:
阶段1: 基础入门 (2周)
├─ 文章: #01, #02, #03
├─ 实践: 简单自定义View (圆形/矩形/文字)
└─ 目标: 理解View基本原理
阶段2: 交互实现 (2周)
├─ 文章: #04, #05
├─ 实践: 带交互的View (可拖动、可点击)
└─ 目标: 掌握触摸事件处理
阶段3: 动画效果 (2周)
├─ 文章: #06, #07
├─ 实践: 带动画的View (进度条、加载动画)
└─ 目标: 掌握属性动画
阶段4: 综合实战 (4周)
├─ 文章: #08, #09, #10
├─ 实践: 复杂自定义View (图表、编辑器)
└─ 目标: 独立开发复杂View
阶段5: 高级技巧 (4周)
├─ 文章: #11, #12, #13, #14
├─ 实践: 解决实际项目问题
└─ 目标: 成为View开发专家
总计: 14周 (约3.5个月)
10.4 技能评估表
自我评估 (打✓):
基础技能:
□ 理解View体系结构
□ 掌握Measure/Layout/Draw流程
□ 能独立实现简单自定义View
□ 理解MeasureSpec机制
绘制技能:
□ 熟练使用Canvas API
□ 掌握Paint属性配置
□ 能绘制复杂路径 (Path)
□ 理解坐标变换 (Matrix)
交互技能:
□ 掌握onTouchEvent处理
□ 能使用GestureDetector
□ 处理多点触控
□ 解决滑动冲突
动画技能:
□ 使用ObjectAnimator
□ 自定义ValueAnimator
□ 编写自定义Interpolator
□ 实现复杂动画组合
高级技能:
□ 开发自定义ViewGroup
□ 优化View性能
□ 处理状态保存恢复
□ 使用高级绘制技巧 (Shader/PorterDuff)
评分标准:
√ 0-5个 : 初学者 → 从#01开始学习
√ 6-10个 : 进阶者 → 从#04开始学习
√ 11-15个 : 高级者 → 从#08开始实战
√ 16-20个 : 专家级 → 挑战高级项目
📚 总结
核心要点回顾
1️⃣ View绘制三部曲
Measure (测量尺寸) → Layout (确定位置) → Draw (绘制内容)
2️⃣ 自定义View开发六步
需求分析 → 创建类 → 实现测量 → 实现绘制 → 实现交互 → 测试优化
3️⃣ 性能优化三原则
• 避免在onDraw中分配对象
• 使用硬件加速
• 减少过度绘制
4️⃣ 触摸事件处理原则
• ACTION_DOWN返回true拦截后续事件
• 使用GestureDetector简化手势识别
• 父子View滑动冲突用外部拦截法
5️⃣ 动画系统选择
• 简单属性动画 → ObjectAnimator
• 复杂自定义动画 → ValueAnimator
• 多动画组合 → AnimatorSet
学习建议
- 循序渐进: 按照系列文章顺序学习,不要跳跃
- 动手实践: 每篇文章都配合实际代码练习
- 项目驱动: 选择感兴趣的项目,边学边做
- 性能优先: 从一开始就注意性能优化
- 总结输出: 写技术博客巩固知识
下一步行动
□ 复习本系列前14篇文章
□ 评估自己的技能等级
□ 选择1个实战项目开始
□ 加入Android开发社区交流
□ 关注最新Android技术动态
所有评论(0)