View的工作原理

ViewRoot和DecorView

ViewRoot

  • ViewRoot对应的是ViewRootImpl类,是链接DecorView和WindowManager的纽带,View的三大流程全都是通过ViewRoot来完成的。
  • 在ActivityThread中当Activity创建完毕后,会将DecorView添加到Window中,同时创建ViewRootImpl对象,并将ViewRootImpl对象和DecorView建立关联。
  • ViewRootImpl和DecorView进行关联的代码
root = new ViewRootImpl(view.getContext(),display);
root.setView(view,wparams,panelParentView);
  • View的绘制是从ViewRoot的performTraversals()方法开始的。
  • performTaversals方法会依次调用performMeasure()、performLayout()、performDraw()方法来完成顶层View的measure,layout和draw的过程。

DecorView

  • 作为顶层View,一般情况下它内部都会包含一个竖直方向的LinearLayout,在这里LinearLayout里面有两部分,分别为title和content。
  • Activity中通过setContentView所设置的布局就是被添加到id为content的FrameLyout中。
  • 获取到content的布局
//获取到content
ViewGroup content = finidViewById(R.android.id.content);
//获取到我们设置的View
View view = content.getChildAt(0);
  • 通过源码我们知道DecorView就是一个FrameLayout (DecorView extends FrameLayout)

MeasureSpec

  • 参与了View的Measure过程,“测量规格”。

  • MeasureSpec代表一个32位的int,高2位是代表SpecMode(测试模式),后30位代表SpecSize(某种测量模式下的大小)。

  • MeasureSpec提供了打包和解包的方法,将SpecMode和SpecSize打包成MeasureSpec;MeasureSpec通过解包将SpecMode和SpecSize分离出来。

    SpecMode
    • UNSPECIFIED:父容器不对View有任何限制。

    • EXACTLY:父容器已经检测出View所需要的精确大小,这个时候View的大小就是SpecSize,对应于LayoutParams中的match_parent和具体的数值。

    • AT_MOST:父容器已经指定了一个大小即SpecSize,View的大小不能大于这个SpecSize,对应于LayoutParams中的wrap_content。

MeasureSpec和LayoutParams对应的关系

DecorView
  • DecorView的MeasureSpec由窗口的尺寸和自身的LayoutParams来共同决定。
普通View
  • 由父容器的MeasureSpec和自身的LayoutParams来决定的。

  • 针对普通View来说,View的measure过程是从ViewGroup中传递过来,代码如下

    /**
    *看的出来View的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams决定,也和Padding和Margin有关。
    */
    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);
        }
    
    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
            int specMode = MeasureSpec.getMode(spec);
            int specSize = MeasureSpec.getSize(spec);
    
            int size = Math.max(0, specSize - padding);
    
            int resultSize = 0;
            int resultMode = 0;
    
            switch (specMode) {
            // Parent has imposed an exact size on us
            case MeasureSpec.EXACTLY:
                if (childDimension >= 0) {
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size. So be it.
                    resultSize = size;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
    
            // Parent has imposed a maximum size on us
            case MeasureSpec.AT_MOST:
                if (childDimension >= 0) {
                    // Child wants a specific size... so be it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size, but our size is not fixed.
                    // Constrain child to not be bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size. It can't be
                    // bigger than us.
                    resultSize = size;
                    resultMode = MeasureSpec.AT_MOST;
                }
                break;
    
            // Parent asked to see how big we want to be
            case MeasureSpec.UNSPECIFIED:
                if (childDimension >= 0) {
                    // Child wants a specific size... let him have it
                    resultSize = childDimension;
                    resultMode = MeasureSpec.EXACTLY;
                } else if (childDimension == LayoutParams.MATCH_PARENT) {
                    // Child wants to be our size... find out how big it should
                    // be
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                    // Child wants to determine its own size.... find out how
                    // big it should be
                    resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                    resultMode = MeasureSpec.UNSPECIFIED;
                }
                break;
            }
            //noinspection ResourceType
            return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
        }
    
    

View的工作流程

Measure

  • 一个原始的View通过measure过程就可以测量完毕,如果是一个ViewGroup那么需要自身的measure还需要遍历子View,调用子View的measure。

    View的Measure
    • View的measure是由View中的measure方法开始的,measure方法是final类型,所以子类不能重写该方法。

    • 在measure方法中调用了onMeasure方法,看onMeasure方法的实现即可。

       protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         //setMeasuredDimension()方法会设置View测量后的宽和高,View的最终宽和高是在layout过程确定的
              setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                      getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
          }
      
      //看getDefaultSize()
       public static int getDefaultSize(int size, int measureSpec) {
              int result = size;
              int specMode = MeasureSpec.getMode(measureSpec);
              int specSize = MeasureSpec.getSize(measureSpec);
      
              switch (specMode) {
              case MeasureSpec.UNSPECIFIED:
                  result = size;
                  break;
              case MeasureSpec.AT_MOST:
              case MeasureSpec.EXACTLY:
                  result = specSize;
                  break;
              }
              return result;
          }
          // 如果View没有设置背景,那么height就是mMinHeight的值,mMinHeight对应的是android:mMinHeight属的值,如果没有指定默认值就是0,
      // 如果设置了背景了就是取max(mMinHeight, mBackground.getMinimumHeight())两者之间的最大值。
      protected int getSuggestedMinimumHeight() {
              return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
      
          }
      //getSuggestedMinimumWidth()同理getSuggestedMinimumHeight()
      
      /**
           * Returns the minimum height suggested by this Drawable. If a View uses this
           * Drawable as a background, it is suggested that the View use at least this
           * value for its height. (There will be some scenarios where this will not be
           * possible.) This value should INCLUDE any padding.
           *
           * @return The minimum height suggested by this Drawable. If this Drawable
           *         doesn't have a suggested minimum height, 0 is returned.
           返回Drawable的原始的高,如果Drawable没有原始高度,默认值就是0
           */
          public int getMinimumHeight() {
              final int intrinsicHeight = getIntrinsicHeight();
              return intrinsicHeight > 0 ? intrinsicHeight : 0;
          }
      //getMinimunWidth()同理与getMinimunHeight()。
      
    ViewGroup的Measure
    • ViewGroup除了完成自己的measure过程后,还需要循环遍历子View,调用子View的meassure过程。

    • ViewGroup是一个抽象类因此没有重写View的onMeasure,提供了一个measureChildren()方法。

    • ViewGroup没有定义自己的测量过程。本身是一个抽象类,并没有实现onMeasure方法,onMeasure需要子View的自己去实现。

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];
            //如果子View是处于GONE就不进行测量了。
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

 /**
     * Ask one of the children of this view to measure itself, taking into
     * account both the MeasureSpec requirements for this view and its padding.
     * The heavy lifting is done in getChildMeasureSpec.
     *
     * @param child The child to measure
     * @param parentWidthMeasureSpec The width requirements for this view
     * @param parentHeightMeasureSpec The height requirements for this view
     */
    protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
        final LayoutParams lp = child.getLayoutParams();

      //子View的MeasureSpec受父容器的MeasureSpec和自己的LayoutParams影响
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
                //开始子View的measure过程
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
View获取到宽高
  • View的绘制与Activity的生命周期不是同步的,当生命周期执行完毕,View的绘制还没有执行完毕,这时在onCreate()、onStart()、onPause获取到的. 和height都是0。

  • Activity/View.onWindowFocusChanged()方法中获取

    //当Window获取焦点和失去焦点的时候,该方法会被调用的,触发该方法的时候, View已经测试完毕,可以获取View的宽和高
    /**
         * 该方法会在window获取到焦点和失去焦点的时候调用,可能会频繁的调用,
         *
         * @param hasFocus
         */
        @Override
        public void onWindowFocusChanged(boolean hasFocus) {
            super.onWindowFocusChanged(hasFocus);
            //当获取到焦点的时候去执行下面的代码
            if (hasFocus) {
                int width = mBtnAsyncTask.getWidth();
                int height = mBtnAsyncTask.getHeight();
                Log.d("measure", "MeasureWidth = " + width + " measureHeight = " + height);
            }
        }
    
    
  • View.post(Runnable)

    //通过post方法将一个runnable投放到消息队列的的尾部,等待looper取到消息调用到这个runnable的时候,View已经初始化好了
    //典型的模版代码如下
    @Override
        protected void onStart() {
            super.onStart();
            mBtnToastWorkThread.post(new Runnable() {
                @Override
                public void run() {
                    int width = mBtnToastWorkThread.getWidth();
                    int height = mBtnToastWorkThread.getHeight();
                    Log.d("post", "MeasureWidth = " + width + "MeasureHeight = " + height);
    
                }
            });
        }
    
  • ViewTreeObserver

    //使用ViewTreeObserver的众多回调接口可以实现获取到View的宽和高
    //当View树的状态发生改变或者View树内部的View可见性发生改变的时候,此时OnGlobalLayout接口中的onGlobalLayout()方法将会被回调。此时是获取到View的宽和高的好时机。
    @Override
        protected void onStart() {
            super.onStart();
            ViewTreeObserver viewTreeObserver = mBtnIntentService.getViewTreeObserver();
            viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    mBtnIntentService.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                    int width = mBtnIntentService.getWidth();
                    int height = mBtnIntentService.getHeight();
                    Log.d("ViewTreeObserver = ", "height = " + height + " width = " + width);
                }
            });
        }
    
  • View.measure(int widthMeasureSpec,int heightMeasureSpec)

Layout

  • layout()方法确认了View本身的位置,onLayout()方法确认了所有子元素的位置。

    layout()方法的流程
    • 首先通过setFrame()方法确定了View的四个顶点的位置。
    • 确认了View的顶点也就是确认了View在父布局中的位子。
    • 接着调用了onLayout()方法,这个方法是父容器确认了子元素的位置。
    • onLayout()在View和ViewGroup中都没有具体的实现,不同View有不同的布局实现。
    LinearLayout_layoutVertcial
    • LinearLayout通过layout方法调用了onLayout方法中的layoutVertical
    • 在layoutVertical()方法中调用了setChildFrame来确认子view的位置。
    • setChildFrame方法中调用了子View的layout()方法,子View中layout方法中调用了onLayout()方法来确认了子 view的顶点的位置。
    View的测量后宽和高与最总得到的宽和高相等吗?

    该问题就是 getMeasuredHeight()和getMeasuredWidth() 与 getHeight()和getWidth() 得到得值相等吗?

    • 正常情况下测量后得到的值域最终得到的值是相等的
    • 前者是发生在measure过程中,后者发生在layout过程中。
    • 赋值的时机是不同的。
    • 某些情况下measure过程中得到的值域layout过程得到的值是不同的
    public void layout(int l,int t,int r,int b){
      super.layout(l,t+100,r,b+20)
    }
    //如上的操作,就会导致 layout过程中得到的值与measure规程得到的值是不同的。
    

Draw

View的draw作用就是将View绘制到屏幕上,遵循如下几步操作

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

你可能感兴趣的:(View的工作原理)