4. Android Framework - View的工作原理

  1. ViewRoot和DecorView
    ViewRoot对应ViewRootImpl类, 它是连接WindowManager和DecorView的桥梁, View的measure/layout/draw流程均有ViewRoot发起. 在ActivityThread中, 当Activity对象创建后, 会将DecorView添加到Window中, 同时创建ViewRootImpl对象, 并使它们关联起来.

    View的绘制从ViewRoot的performTraversals开始, 它经过measure/layout/draw三个过程, 最终被绘制出来.

    • measure 测量View的宽高
    • layout 确定View在父容器中的位置
    • draw 将View绘制在屏幕上

    performTraversals中调用measure方法, measure调用onMeasure, 在onMeasure中会对所有子元素进行measure过程, 此时measure流程从父容器传递到了子元素中, 完成一次measure, 子元素重复父容器的过程, 遍历整个View树. performLayout和performDraw也类似, 但performDraw的传递是通过在draw中调用dispatchDraw实现的.

    Measure后, 可以通过getMeasuredWidthgetMeasuredHeight获取View测量后的宽高.

  2. MeasureSpec(20990行)
    1. MesaureSpec
      • 代表一个32为int值, 高2位代表SpecMode, 低30位代表SpecSize
      • SpecMode 有三种
        • UNSPECIFIED 父容器不对View有任何限制, 要多大给多大, 一般用于系统内部.
        • EXACTLY 父容器已经检测出了View所需要的精确大小, 其大小就是MeasureSize指定的值. 对应于LayoutParams中match_parent和具体的数值这两种模式
        • AT_MOST 父容器指定了一个可用大小即MeasureSize, View的大小不能大于这个值, 对应于LayoutParams中的wrap_content.
    2. MeasureSpec和LayoutParams的对应关系
      MeasureSpec由父容器和LayoutParams一起决定.
      • DecorView(1214行)
        ViewRootImpl#measureHierarchy调用ViewRootImpl#getRootMeasureSpec通过屏幕尺寸和LayoutParams计算出DecorView的MeasureSpec.
      • 普通View(5939行)
        MeasureSpec和父容器的MeasureSpec和子元素本身的LayoutParams有关, 并且还有View的margin和padding.
        ViewGroup#measureChildWithMargins调用ViewGroup#getChildMeasureSpec计算出普通View的MeasureSpec.
  3. View的工作流程

    1. measure过程

      • View 的measure过程
        View#measure调用View#onMeasure它又调用View#getDefaultSize,
        如果SpecMode是UNSPECIFIED

        • measuredWidth = (background == null) ? minWidth : max(minWidth, background.getMinimumWidth)
        • measuredHeight = (background == null) ? minHeight : max(minHeight, background.getMinimumHeight)

        如果SpecMode是AT_MOST或EXACTLY

        • 宽高就是measureSpec中的specSize
          如果直接继承View的自定义控件需要重写onMeasure方法, 并设置wrap_content时的自身大小, 否则就相当于使用match_parent原因在于wrap_content时的SpecMode是AT_MOST, 根据上面的结论, 它实际就是父容器当前剩余的空间大小. 解决办法是给View指定一个默认的内部宽高, 并在wrap_content时设置此默认的宽高.
      • ViewGroup的measure过程
        除了完成自己的measure过程以外, 还会遍历调用所有子元素的measure方法, 各个子元素再递归去执行这个过程. ViewGroup#measureChildren会为每一个子元素调用measureChild
        所有子元素measure结束后, ViewGroup会将得到的子元素的宽高加上padding, measure自己的宽高.

      怎样在Activity启动时就获取一个View的宽高(有例子)

      • Activity/View#onWindowFocusChanged 回调里获取
      • 使用View.post(Runnable)
      • 在ViewTreeObserver 回调中获取
      • View.measure 比较复杂
    2. layout过程
      1. setFrame设定View的四个顶点位置
      2. 调用onLayout方法, 确定子元素的位置(View和ViewGroup没有真正实现)
      3. onLayout调用子元素的layout方法确定自己的位置, 这样完成整个View树的layout过程.
    3. draw过程
      1. 绘制背景
      2. 绘制自己
      3. 绘制children
      4. 绘制装饰
  4. 自定义View
    1. 分类
      • 继承View 重写onDraw
      • 继承ViewGroup 派生特殊的Layout
      • 继承特定的View
      • 继承特定的ViewGroup
    2. 须知
      • 让自定义View 支持wrap_content
      • 让自定义View 支持padding
      • 使用View#post, 而不是使用Handler
      • View中有线程或动画需要在View#onDetachedFromWindow时停止
      • 处理滑动冲突
    3. 实例
    4. 思想
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值