第4章 View的工作原理

一、ViewRoot 和 DecorView

  • ViewRoot对应于ViewRootImpl 类,它是连接WIndowManager 和 DecorView的纽带,View的三大流程均是通过VIewRoot来完成的,在ActivityThread中,当Activity对象被创建后,会将DecorView 添加到Window中,同时会创建VIewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联

  • View 的绘制流程是从ViewRoot的 performTraversals 方法开始的,performTraversals 依次调用 performMeasure、performLayout 和 performDraw,这三个方法分别完成顶级View的measure、layout 和 draw

  • performMeasure -> measure -> onMeasure,对子元素进行measure(layout 和 draw类似)

  • measure过程决定了View的宽/高,getMeasuredwidth/getMeasuredHeight,除特殊情况外它都等于View的最终宽/高;Layout过程决定View的四个顶点座标和实际宽/高,getTop/getBottom/getLeft/getRight/getWidth/getHeight;Draw决定View的显示

  • DecorView作为顶级View,包含上下两个部分,上面是标题栏,下面是内容栏,Activity中 setContentView 所设置的布局是被加到内容栏;View层的事件都先经过DecorView 然后传递给我们的View

二、MeasureSpec

MeasureSpec代表一个32位的int值,高 2 位代表SpecMode 测量模式,低30位代表 SpecSize 规格大小

1. SpecMode有三类

  • UNSPECIFIED:父容器不对View有任何限制,要多大有多大,一般用于系统内部

  • EXACTLY:父容器已经检测说View所需要的的精确大小,View的最终大小就是SpecSize指定的值,对应于 match_parent 和 具体数值两种模式

  • AT_MOST:父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,对应于 wrap_content

2. MeasureSpec 和 LayoutParams

对于顶级View(DecorView),其MeasureSpec由窗口的尺寸和其自身的LayoutParams来共同确定;

对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同确定

3. View 的measure

View的measure过程由ViewGrou传递而来,ViewGroup 的 measureChildWithMargins

protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

4. 注意

  • 当View采用固定宽/高的时候,不管父容器的 MeasureSpec是什么,View的MeasureSpec 都是精确模式且遵循Layoutparams中的大小
  • 当View的宽/高是 match_parent 时,如果父容器是最大模式,那么子View也是最大模式,且不超过父容器的剩余空间
  • 当View的宽/高是 wrap_content 时,不管父容器的模式是精准还是最大化,View的模式总是最大化,且不超过父容器的剩余空间

三、View的工作流程

1. measure 过程

1)View 的 measure过程

View 的measure过程由其measure方法来完成,measure方法是一个final类型的方法,子类不能重写,measure方法中会去调用 onMeasure->setMeasuredDimension方法会设置View 宽/高的测量值

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
2)ViewGroup 的 measure过程

ViewGroup 除了完成自己的measure,还会遍历去调用所有子元素的measure,各个子元素再递归去执行这个过程。ViewGroup 是一个抽象类,没有重写 onMeasure,其测量过程的onMeasure 方法需要各个子类去具体实现,但提供了 measureChildren方法,ViewGroup 在measure时,会对每一个子元素进行measure

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
3)异步获取View宽高

如果想在onCreate等方法里获取View的宽高,有4种方式:

  • Activity/View#onWindowFocusChanged

onWindowFocusChanged 表示View已经初始化完毕,宽高已经准备好,在Activity的窗口得到和失去焦点时均会被调用

  • view.post(runnable)
  • ViewTreeObserver#onGlobalLayoutListener
  • view.measure 手动measure

2. Layout 过程

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

View 和 ViewGroup 均没有真正实现 onLayout

3. Draw 过程

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

四、自定义View

1. 分类

1)继承View重写onDraw方法
2)继承ViewGroup 派生特殊的 Layout
3)继承特定的View
4)继承特定的ViewGroup

2. 一些注意

1)让View支持 wrap_content
2)如果有必要,让View支持 padding
3)尽量不要再View中使用 Handler
4)View中如果有线程或者动画,需要及时停止 View#onDetachedFromWindow
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章