《Android开发艺术探索第四章读书笔记-自定义VIew》

本文深入解析了Android中View的绘制流程,包括ViewRoot和DecorView的初始化、MeasureSpec的理解及其与LayoutParams的关系、View的工作流程(measure、layout、draw过程),以及自定义View的实践技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

初始ViewRoot和DecorView

  1. ViewRoot对应于ViewRootImpl类,它是连接WIndowManager和Decorview的纽带,View的三大流程均是通过ViewRoot来完成的。
  2. 在ActivityThread中,当Activity对象被创建完毕完,会将DecorView添加到Window中,同时创建ViewRootImpl对象,并对二者建立关联。
  3. View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout和draw三个过程才能最总将一个View绘制出来。
    • measure用来测量View的宽和高
    • layout用来确定View在父容器中的放置位置
    • 而draw则负责将VIew绘制在屏幕上
  4. performTraversals会依次调用performMeasure、performLayout和performDraw三个方法。performMeasure中会调用measure方法,在measure方法中会调用onMeasure方法,在onMeasure方法则会对所有的子元素进行measure过程,这个时候measure流程就从父容器传递到子元素中,这样就完成了依次measure过程。performDraw的传递流程和performMeasure是类似的。
  5. 在measure完成以后,可以通过getmeasureedWidth和getMeasureHeight方法来获取到View测量的宽/高,在几乎所有的情况下它都等于VIew的最终的宽和高。
  6. DecorView作为顶级View,有上下两部分。 其实DecorView是一个FrameLayout,View层的时间都先经过DecorView,然后才传递给我们的View。

理解MeasureSpec

在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个measure来测量出View的宽/高

SpecMode有三类:
1. UNSPECIFIED
父容器不对View有任何限制,要多大就给多大,这种情况一般用于系统内部,表示一种测量的状态。
2. EXACTLY
父容器已经检测出View所需要的精确大小,这和时候View的最终大小就是SpecSizes所指定的值。它对应于LayoutParams 中的match_parent和具体的数值这两种模式
3. AT_MOST
父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体体现。它对应于LayoutParams中的wrap_content.


  1. MeasureSpec和layoutOarams的对应关系
    在VIew测量的时候,系统会将LayoutParam在父容器的约束下转换后才能对应的MeasureSpec,然后再根据这个MeasureSpec来确定View测量后的宽/高。(LayoutParams需要和父容器一起才能决定VIew的MeasureSpec)、
  2. 普通View的measureSpec的创建规则如表格P182介绍的。
    • 当View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式并且大小遵循LayoutParams的大小。
    • 当View的宽/高是match_parent时,如果父容器的模式是精确模式,那么View也是最大模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么View也是最大模式并且不会超过父容器的剩余空间。
    • 当View的宽和高是wrap_content时,不管父容器的模式是精准还是最大化,View也是最大模式并且不会超过父容器的剩余空间。

VIew的工作流程

measure过程

  1. View的measure过程由measure方法来完成。measure方法中调用View的onMeasure方法 -> getDefaultSize方法,这个返回的大小就是measureSpec中的specSize,而这个specSize就是View测量后的大小
  2. 直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent
  3. ViewGroup是一个抽象类,其测量过程的onMeasure方法需要各个子类去具体实现
  4. 比较好的习惯是在onLayout方法中去获取View的测量宽/高或者最终宽/高。
  5. 如果我们想在Activity已启动的时候就做一件任务,但是这一件任务需要获取某个View宽/高,改在那里获取呢?(如果view还没有测量完毕,那么获得的宽/高就是0)

    • Activity/View# onWIndowFocusChanged
      View已经初始化完毕了,宽/高已经准备好了,这个时候去获取宽/高是没问题的。当Activity就执行和暂停执行时,onWindowFocusChanged均会被调用。
    • view.post(runnable)
      通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了。
    • ViewTreeObserver
      伴随着View树的状态改变等,onGlobalLayout会被代用多次。
    • view.measure(int widthMeasureSpec,int heightMeasureSpec)

      1. match_parent无法获得具体的宽/高
      2. 具体的数值 100px

        int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
        int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
        view.measure(widthMeasureSpec ,heightMeasureSpec  );
      3. wrap_content

int widthMeasureSpec = MeasureSpec.makeMeasureSpec( (1 << 30) -1, MeasureSpec.AT_MOST);

View的尺寸使用30位二进制表示

layout过程

  1. layout方法确定View本身的位置,而onLayout方法则会确定所有子元素的位置

draw过程

View的绘制过程遵循如下几步:

  • 绘制背景 background.draw(canvas)
  • 绘制自己(onDraw)
  • 绘制children(dispatchDraw)
  • 绘制装饰(onDrawScrpllBars)

在setWillNotDeaw方法中有个标记位。当我们的自定义控件集成于ViewGroup并且本身不具备绘制功能时,就可以开启这个标记位进行优化

自定义View

  1. 继承view重写onDraw方法需要自己支持wrap_content,并且padding也要自己处理。继承特定的View例如TextView不需要考虑。
  2. 尽量不要在View中使用Handler,因为view内部本身已经提供了post系列的方法,完全可以替代Handler的作用。
  3. view中如果有线程或者动画,需要在onDetachedFromWindow方法中及时停止。
  4. 处理好view的滑动冲突情况。

接下来是原书中的自定义view的示例
HorizontalScrollViewEx.java
可下拉的PinnedHeaderExpandableListView的实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值