Android View体系 笔记

Android View体系是构建用户界面的基石,内容庞杂但逻辑清晰。为了帮你建立起完整的知识框架,我将从架构全景、核心职责、三大工作流程、事件分发、自定义实践五个维度进行拆解。


一、架构全景:从Activity到ViewRoot

要理解View体系,首先需要厘清它在大框架中的位置。Activity不是视图,而是视图的控制层;真正绘制和处理事件的是View。

1.1 核心组件关系

每个Activity内部持有一个Window对象(唯一实现是PhoneWindow),PhoneWindow中包含了顶层视图DecorView。当我们调用setContentView时,实际是将自定义布局添加到DecorViewandroid.R.id.content区域。

层级链:
ActivityWindowDecorViewContentParentYour Layout

1.2 ViewRoot的角色

ViewRoot不是View,而是Handler的实现类,它建立了DecorView与窗口系统Server端的桥梁。绘制的总入口是ViewRootImpl.performTraversals(),该方法依次触发measure、layout、draw三大流程。

关键认知: View树是单线程的,所有UI操作必须在主线程执行。跨线程刷新需使用postInvalidate()


二、View体系核心组成

2.1 View与ViewGroup的本质区别

维度 View ViewGroup
继承 直接继承Object 继承自View
职能 绘制自己、处理事件 容器、管理子View
绘制 重写onDraw() 不重写onDraw(),重写dispatchDraw()
布局 onLayout()无操作 onLayout()为abstract,必须实现
子类 TextView, ImageView LinearLayout, RecyclerView

关键代码印证:
ViewGroup实现了ViewManager接口,因此具备addViewremoveView能力;同时实现ViewParent接口,负责焦点、滚动等控制。

2.2 树形结构(组合模式)

Android采用组合模式设计视图层级:

  • 叶节点:普通View(不可包含子View)
  • 树枝节点:ViewGroup(可包含子ViewGroup或View)

这种设计使得绘制和事件分发的递归操作成为可能------从根节点开始深度遍历。


三、三大工作流程(必考/必会)

整个绘制流程由ViewRootImpl.performTraversals()驱动,依次调用performMeasureperformLayoutperformDraw

3.1 Measure:测量尺寸

入口方法: measure(int widthMeasureSpec, int heightMeasureSpec)(final,不可重写)。
核心重写: onMeasure(int widthMeasureSpec, int heightMeasureSpec)

3.1.1 MeasureSpec 解密

MeasureSpec是一个64位压缩值 (实际代码中为32位int):高2位模式 + 低30位尺寸,通过位运算提高性能。

模式 取值 含义
EXACTLY 2 bits 11 精确值(match_parent / 100dp),父View强制指定
AT_MOST 2 bits 10 最大值(wrap_content),不能超过SpecSize
UNSPECIFIED 2 bits 00 无限制(ScrollView嵌套、RecyclerView),想多大就多大

子View的MeasureSpec生成规则:

由父View的MeasureSpec + 子View的LayoutParams共同决定。例如:父View是EXACTLY,子View是wrap_content → 子View变为AT_MOST,size=父View可用剩余空间。

3.1.2 自定义View测量要点

java 复制代码
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    
    if (widthMode == MeasureSpec.AT_MOST) {
        // wrap_content:需要自己计算一个默认大小
        setMeasuredDimension(mDefaultWidth, mDefaultHeight);
    } else {
        // EXACTLY 直接使用
        setMeasuredDimension(widthSize, heightSize);
    }
}

必须调用setMeasuredDimension(),否则会报错。

3.2 Layout:分配位置

入口方法: layout(int l, int t, int r, int b)(final)。
核心重写: onLayout(boolean changed, int left, int top, int right, int bottom)

3.2.1 位置参数

View的位置由相对于父View的左上角坐标定义:

  • getLeft() = 左边缘距父容器左侧距离
  • getTop() = 上边缘距父容器顶部距离
  • getRight() = getLeft() + getWidth()
  • getBottom() = getTop() + getHeight()

坑点提醒: getWidth()getMeasuredWidth()可能不同。前者是layout后最终尺寸,后者是measure阶段测量尺寸。理论上layout不应改变尺寸,但开发者可以强行改。

3.2.2 ViewGroup必须实现onLayout

java 复制代码
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        child.layout(x, y, x + child.getMeasuredWidth(), y + child.getMeasuredHeight());
        x += child.getMeasuredWidth(); // 水平排列示例
    }
}

若不遍历调用child.layout(),子View将不可见

3.3 Draw:绘制内容

入口方法: draw(Canvas canvas)(final,定义绘制骨架)。
核心重写: onDraw(Canvas canvas)(View绘制自身)、dispatchDraw(Canvas canvas)(ViewGroup绘制子View)。

3.3.1 draw方法内部执行顺序

  1. 绘制背景(drawBackground()
  2. 绘制自身(调用onDraw()
  3. 绘制子View(调用dispatchDraw())→ 内部drawChild()→ 调用子View的draw()
  4. 绘制渐变框(边缘效果)
  5. 绘制滚动条

重要规律:

ViewGroup通常不重写onDraw (即使重写也可能不显示,需设置setWillNotDraw(true)优化),而重写dispatchDraw可干涉子View绘制顺序或添加装饰。


四、事件分发机制(交互核心)

虽然搜索结果中仅部分提及,但这是View体系的关键闭环。

4.1 三大方法

  • dispatchTouchEvent(MotionEvent ev):事件分发入口,决定是否传递。
  • onInterceptTouchEvent(MotionEvent ev):拦截判断(仅ViewGroup有)。
  • onTouchEvent(MotionEvent ev):事件处理。

4.2 传递顺序(Activity→Window→ViewGroup→View)

伪代码逻辑:

java 复制代码
public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean consume = false;
    if (onInterceptTouchEvent(ev)) {
        consume = onTouchEvent(ev); // 拦截了,自己处理
    } else {
        for (child in children) {
            if (child.dispatchTouchEvent(ev)) {
                consume = true;
                break; // 子View消费了,终止传递
            }
        }
    }
    return consume;
}

经典结论:

  1. 事件由外向内传递,再由内向外回溯(责任链模式)。
  2. onTouchListener优先级高于onTouchEvent
  3. OnClickListeneronTouchEvent的ACTION_UP触发。

五、自定义View实践

5.1 分类与选择策略

场景 继承方式 必须重写的方法
全新绘制 View onMeasure() + onDraw()
组合控件 ViewGroup onMeasure() + onLayout()
扩展控件 TextView/Button等 onDraw() 或 仅增加功能
特殊布局 ViewGroup onMeasure() + onLayout()

5.2 自定义属性流程

  1. res/values/attrs.xml 定义<declare-styleable>
  2. 构造函数中context.obtainStyledAttributes()获取TypedArray
  3. 解析属性值,最后recycle()

5.3 构造函数解析

java 复制代码
// 代码new时调用
public MyView(Context context) { this(context, null); }

// XML解析时调用
public MyView(Context context, @Nullable AttributeSet attrs) { this(context, attrs, 0); }

// style指定时调用
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { ... }

// API 21+ 样式资源指定
public MyView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { ... }

必须实现前两个,后两个按需。


六、性能优化与常见陷阱

6.1 刷新机制对比

方法 触发场景 后果
invalidate() 外观不变(文字/颜色) 仅重绘draw流程
postInvalidate() 非UI线程调用 同上,Handler切线程
requestLayout() 尺寸/位置变化 measure+layout+draw
forceLayout() 标识需要重新measure 下次layout生效

原理: invalidate会从当前View向上标记直到ViewRoot,最终触发performTraversals(),但仅执行draw阶段。

6.2 常见误区

  1. 误以为View有margin:View只有padding,margin是ViewGroup.LayoutParams的属性。
  2. 直接调用measure() :严禁开发者主动调用measure,应使用requestLayout由系统触发。
  3. setTranslationX与setX混淆setX设置绝对坐标,setTranslationX设置相对于原始位置的偏移,即X = left + translationX
  4. findViewById性能:从根开始DFS遍历树,避免在循环中调用。

总结

Android View体系可归纳为一树、三流、一分发

  • 一棵树:组合模式构建的视图树
  • 三大流程:measure(量)、layout(位)、draw(绘)
  • 一套分发:事件自上而下、自下而上的责任链
相关推荐
城东米粉儿2 小时前
Android Messenger 笔记
android
城东米粉儿2 小时前
Android消息机制 笔记
android
奥陌陌2 小时前
用SurfaceControlViewHost 跨进程显示view
android
诸神黄昏EX2 小时前
Android SystemServer 系列专题【篇五:SystemConfig系统功能配置】
android
城东米粉儿2 小时前
Android IdleHandler 优化笔记
android
城东米粉儿2 小时前
Android Binder 笔记
android
Android系统攻城狮2 小时前
Android tinyalsa深度解析之pcm_get_available_min调用流程与实战(一百一十六)
android·pcm·tinyalsa·音频进阶·音频性能实战
lxysbly2 小时前
nds模拟器安卓版官网
android
hewence12 小时前
协程间数据传递:从Channel到Flow,构建高效的协程通信体系
android·java·开发语言