第四章、View的工作原理

来源:互联网 发布:荣耀v10网络制式 编辑:程序博客网 时间:2024/05/28 11:28

第四章、View的工作原理

> 为了更好的自定义View,还需要掌握View底层工作原理,比如View的测量布局,布局流程以及绘制流程。掌握了这几个流程后,我们就对View的底层更加了解,这样就可以做出一个比较完善的自定义View。
  1. ViewRoot和DecorView

    1. View的绘制流程是从ViewToot的performTraversals方法开始的,经过的measure、layout和draw三个过程才能最终将一个View绘制出来。其中measure用来测量View的宽和高,layout用来确定View在父容器的放置位置,draw负责将View绘制在屏幕上。
    2. performTraversals 会依次调用performMeasure,performLayout和performDraw三个方法,这三个方法分别完成顶级View的measure、layout和draw这三大流程,其中performMeasure会调用measure,measure中又会调用onMeasure方法,在onMeasure方法中则会对所有的子元素进行measure,这个时候measure就从父容器传递到了子元素中,这样就完成了一次measure过程,接着子元素会重复父容器的measure过程,如此反复就完成了整个View树的遍历。
    3. performLayout和performDraw的传递流程同performMeasure一样。
  2. 理解MeasureSpec

    MeasureSpec 在很大程度上确定了一个View的尺寸规则,之所以说是很大程度上是因为这个过程还受父容器的影响,因为父容器影响View的MeasureSpec的创建过程。在测量过程中,根据父容器所施加的规则,系统会将View的LayoutParams转换成对应的MeasureSpec,然后再根据这个measureSpec来测量出View的宽高。

    1. MeasureSpec
      • MessureSpec代表了一个32int值,高两位代表SpecMode,低两位代表了SpecSize。SpecMode是指测量模式,SpecMode是指在某种测量模式下的规格大小。
      • MeasureSpec通过将SpceMode和SpecSize打包成一个int值来避免过多的对象内存分配问题。为了方便,其提供了打包解包方法。SpecMode和SpceSize也是一个int值。
      • SpecMode有三类:
        1. UNSPECIFIED: 父容器不对View有任何限制、,要多大给多大,这种情况一般用于系统内部,表示一种测量的状态。
        2. EXACTLY: 父容器已经检测出View所需要的精确大小,这个时候View的最终大小就是SpecSize所指定的值,它对应于LayoutParams中march_parent和具体数值这两种模式。
        3. AT_MOST: 父容器指定一个可用大小即SpecSize,View大小不能大于这个值。对应于LayoutParams中的wrap_content.
    2. MeasureSpec 和LayoutParams的对应关系

      对于顶级View和普通View(即DecorView)来说,MeasureSpce的转换过程略有不同,对于DecorView,其MeasureSpce由窗口尺寸和其自身的LayoutParams来共同确定;对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定,MeasureSpec一旦确定后,onMeasure中就可以确定View的测量宽高了。

      1. DecorView的MeasureSpce的产生,遵守以下规则:

        1. LayoutParams.MATCH_PARENT:精确模式,大小就是窗口的大小。
        2. LayoutParams.WRAP_CONTENT:最大模式,大小不定,但是不能超过窗口的大小。
        3. 固定大小:精确模式,大小为LayoutParams中指定的大小。
      2. 不同父容器和不同layoutParams,产生不同MesureSpec:

        1. 当View采用固定宽/高的时候,不管父容器的MeasureSpec是什么,View的MeasureSpec都是精确模式并且其大小遵循LayouParams中的大小。
        2. 当View的宽/高是match_parent时,如果父容器的模式是精确模式,那么View也是精确模式并且其大小是父容器的剩余空间;如果父容器是最大模式,那么View也是最大模式并且其大小不会超过父容器的剩余空间。
        3. 当view的宽/高是wrap_content时,不管父容器的模式是精确还是最大化模式,View的模式总大化并且大小不能超过父容器的剩余空间。
        4. UNSPECIFIED模式,一般不考虑这种模式,以为这个模式主要用于系统内部多次measure的情形。
  3. View工作流程

    1. View的measure过程

      measure过程要分情况来看,如果只是一个原始的View,那么通过measure就完成了其测量过程,如果是一个ViewGroup,除了完成自己的测量过程外,还会遍历调用所有子元素的measure方法,各个子元素再递归去执行这个流程。

      1. 直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在wrap_content就相当于使用match_content。
      2. 解决上面的问题,只需要给View指定一个默认的内部宽/高并在wrap_content时设置宽/高即可。对于非wrap_content情形,我们沿用系统的测量值即可,至于这个默认内部宽/高的大小如何制指定,没有固定依据,根据需要灵活制定即可。
    2. ViewGroup的measure过程

      1. 对于ViewGroup来说,除了完成自己的measure过程以外,还会遍历去调用所有的子元素的measure方法,各个子元素再递归去执行这个过程。和View不同的是,ViewGroup是一个抽象类,因此它没有重写View的onMeasure方法,但是它提供了一个叫measureChildren的方法。measureChild的思想就是取出子元素的LayoutParams,然后再通过getChildMeasureSpec方法来创建子元素的MeasureSpec,接着将measureSpece直接传递给View的measure方法进行测量。
      2. 为什么ViewGroup不想View一样对其onMeasure方法做统一的实现?
        因为不同的ViewGroup子类有着不同的布局特性,这导致它们的测量细节各不相同。
      3. View的measure过程是三大流程中最复杂的一个,measure完成以后,通过getMeasuredWidth/Height方法就可以正确地获取到View的测量宽/高.需要注意的是,在某些嫉妒啊你情况下,系统可能需要多次measure才能确定最终的测量宽/高,这种情形下,在onMeasure方法拿到测量宽高可能是不准确的。一个良好的习惯是在onLayout方法中去获取View的测量宽高
      4. 在onCreate、onStart、onResume中均无法正确得到整个View的宽、高信息,这是因为View的measure过程和Activity的生命周期方法不是同步执行的,因为无法保证Activity执行了onCreate、onStart、onResume时某个View已经测量完毕了,如果View还没有测量完毕,那么获得的宽、高就是0,解决方法如下:
        1. Activity/View#onWindowFocusChanged.
          表示View已经初始化完毕了,注意会被调用多次,Activity的窗口得到焦点和失去焦点均会被调用。
        2. view.post(runnable)
          通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View已经初始化好了。
        3. ViewTreeObserver
        4. View.measure(int widthMeasureSpc,int heightMeasureSpec)
    3. layout过程

      当ViewGroup的位置被确定后,它在onLayout会遍历所有的子元素并调用其layout方法,在layout方法中onLayout方法又会被调用。layout方法确定View本身的位置,而onLayout方法则会确定所有子元素的位置。

      1. View的getMeasuredWidth和getWidth这两个方法的区别?
        在View的默认实现中,View的测量宽/高和最终宽/高是相等的,只不过测量宽/高形成于View的layout过程,即两者的赋值时机不同。但是的确存在某些特殊情况会导致两者情况不一致。
    4. draw过程
      View的作用就是将View绘制到屏幕上面。View的绘制过程遵循如下几步:
      1. 绘制背景 (backgroud.draw(canvas))
      2. 绘制自己 (onDraw())
      3. 绘制children(dispatchDraw)
      4. 绘制装饰 (onDrawScrollBars)
  4. 自定义View

    1. 自定义View的分类:
      1. 继承View重写onDraw方法
        采用这种方法需要自己支持wrap_content,并且padding也需要自己处理。
      2. 继承ViewGroup派生特殊的Layout
        需要合适地处理ViewGroup的测量、布局这两个过程。
      3. 继承特定的View(比如TextView)
        不需要自己支持wrap_content和padding
      4. 继承特定的ViewGroup(比如LinearLayout)
        不需要自己处理ViewGroup的测量和布局。
    2. 注意事项:
      1. 让View支持wrap_content
      2. 如果有必要,让你的View支持padding
      3. 尽量不要在View中使用Handler。view本身就提供了post系列方法。
      4. View中如果有线程或者动画,需要及时停止,当包含此View的Activity退出或者当前View被remove时,View的ononDetachedFromWindow会被调用。
      5. View带有滑动嵌套情形时,需要处理好滑动冲突。
0 0
原创粉丝点击