美文网首页
view 绘制流程

view 绘制流程

作者: 刘佳阔 | 来源:发表于2017-09-05 11:19 被阅读0次
    performTravseral.png
    • DecorView 是界面上的顶层view. ViewRoot是连接WindowManager和DecorView的纽带.view的绘制流程从ViewRoot的perfromTraversals开始.
    • ViewRoot作为顶级View,通常包括一个竖直方向的LinerLayout.这个LinerLayout分两部分.上边是标题栏.就是屏幕显示时间的那一行,下边是内容栏.id为content,我们平时设置的setContentView(),就是在想contentView里添加子类.通过ViewGroup content =(ViewGroup)findViewById(android.R.id.content);拿到contentView. content.getChildAt(0);拿到我们设置的View;
    • MeasureSpace 用一个32位的int代表测量的数据和类型,高二位代表SpecMode,表示测量模式,低30位 SpecSize表示测量大小.
    • MeasureSpace的三种模式
      • UNSPECIFIED:父容器不做限制,通常用于系统内部.
      • EXACTLY 指定大小,view此时的大小就是SpecSize的值.它对应于LayoutParams中的march_parent和具体指定的值(多少dp,sp).
      • At_MOST 父容器指定一个可用大小即SpecSize,View不能大于这个值.对于wrap_content.
    • MeasureSpace 决定view的宽高,但MeasureSpace由父类和view自己的layoutParams共同决定.
    • DecorView的MeasureSpace决定方式
    3.png
    2.png

    -接着看vp如何测设计子类的MeasureSpace

     //parentWidthMeasureSpec,parentHeightMeasureSpec 是vp自己的measureSpace宽高值
        //widthUsed,heightUsed 是被vp额外用掉的宽高值
        protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
    
            final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
            
            //viewgroup自己的measureSpace,第二个参数是vp所使用的值
            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);
        }
        //得到子类的MeasureSpace 通过父类的specMode和子类Layoutparams的组合得到子类最终的MeasureSpace
        public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
            vp的MeasureSpec
            int specMode = MeasureSpec.getMode(spec);
            int specSize = MeasureSpec.getSize(spec);
            
            //vp自己的尺寸减去自己的padding.margin等 最后得到子类能用的最大尺寸
            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: //vp是精确模式
                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);
         }
    

    -最终的匹配结果如下,parentSize值最终父类可用大小,既getChildMeasureSpec中的 size

    4.png

    -measure 过程

    -view 的measures过程

    view 的measure方法是final,既子类无法重写该方法,在measure 中又调用了onMeasure方法,只需要看onMeasure方法就可以.

    //widthMeasureSpec,heightMeasureSpec为父类vp传过来的子类的MeasureSpace 宽高值
    //getSuggestedMinimumWidth()  是建议的最小宽度,一般为view背景的大小和属性android:minWidth大小的最大值.
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
    //子类可以重写这个方法来决定view的measure大小.但是必须调用setMeasuredDimension()方法,不然会抛出异常.
    setMeasuredDimension()方法会设置view宽高的测量值.
    
    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;
    }
    //UNSPECIFIED的情况可以忽略.看剩下两种情况.所以 getDefaultSize的取值其实就是vp传过来的measureSpace的specSize的值.
    
    • 经过以上代码我们得出结论:直接继承View的自定义空间要重写onMeasure方法并设置wrap_content时自身大小,不然设置wrap_content和设置march_parent的效果一样.
    • 因为view 设置为wrap_content, 对应的MeasureSpec 为SpecMode=AT_MOST,SpecSize=parentSize,在onMeasure中就会把父类可用空间的值设置给view.解决办法如下
    5.png

    -ViewGroup 的measures过程

    • viewgroup 出来测量自己,还要遍历子veiw测量大小.vp通过measureChildren方法来遍历测量子类大小

      //Gone的子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];
              if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                  measureChild(child, widthMeasureSpec, heightMeasureSpec);
              }
          }
      }
      //这个方法和之前的 measureChildWithMarging 方法差不多,只是少了widthUsed,heighUsed参数
       protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {
          final LayoutParams lp = child.getLayoutParams();
      
          final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                  mPaddingLeft + mPaddingRight, lp.width);
          final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                  mPaddingTop + mPaddingBottom, lp.height);
      
          child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
       }
      
    • viewgroup并没有实现具体测量过程,因为他是一个抽象类,需要子类例如LinerLayout等自行实现测量过程.

    • 四种方法拿到view的宽高

      1. Activity/View#onWindowFocusChanged,这个方法的含义标识窗口的交点变化时回调,此时view已经初始化完毕,宽高已经准备好了.此方法会回调多次
      public void onWindowFocusChanged(boolean hasFocus){
          super.onWindowFocusChanged(hasFocus);
          if(hasFocus){
              int width=view.getMeasuredWidth();
              int hei=view.getMeasuredHeigh();
          }
      }
      
      1. view.post(runnable) 发送一个消息到消息队列的尾部,等Lopper调用此消息时,view已经初始化完成

        view.post(new Runnable{

        @Override
        public void run(){
             int width=view.getMeasuredWidth();
             int hei=view.getMeasuredHeigh();
        }
        

        });

      2. ViewTreeObserver#onGlobalLayoutListener,当view树的状态或者view树的子view可见性发生改变时会回调,方法会被回调多次

        ViewTreeObserver observer=view.getViewTreeObserver();
        observer.addonGlobalLayoutListener(new onGlobalLayoutListener(){

        publci void onGlobalLayout(){
            view.getViewTreeObserver().removeGlobalLayoutListener(this);//先移除监听
            int width=view.getMeasuredWidth();
            int hei=view.getMeasuredHeigh();
        }
        

        });

      3. view.measure(int,int);需要根据view 的layoutParam 来区分

        march_parent :此状态无法得到结果,因为此时父view的测量宽高也不知道
        具体数值 :比如宽高为100

         int width =MeasureSpec.makeMeasureSpec(100,MeasursSpec.EACTLY)
         int heigh =MeasureSpec.makeMeasureSpec(100,MeasursSpec.EACTLY)
         view.measurd(width,heigh);
        

        wrap_content

         int width =MeasureSpec.makeMeasureSpec((1<<30)-1,MeasursSpec.EACTLY)
         int heigh =MeasureSpec.makeMeasureSpec((1<<30)-1,MeasursSpec.EACTLY)
         view.measurd(width,heigh);
        

        View 的最大尺寸为30位二进制,既 2的30次方减1 既(1<<30)-1,在最大化模式下,我们用view理论上的最大值去构造MeasureSpec是合理的.

    -layout 过程

    • layout 方法确定view本身的位置,在onLayout方法则会确定所有子元素的位置.

    • viewGroup 在位置被确定后,会在onLayout中便利所有子类比调用其layout方法,在layout方法中onLayout方法又被调用.

    • view 的layout方法中, 通过setFrame(l,t,r,b)来设定view的四个定点的位置.父容器通过onLayout方法来确定子view的位置.

    • view和viewgroup均没实现onLayout方法,需要看具体的layout.

    • view 的layout过程

        public void layout(int l, int t, int r, int b) {
            //如果需要在layout前进行一次measure
            if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) { 
                onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }
            //之前的位置 左上右下
            int oldL = mLeft;
            int oldT = mTop;
            int oldB = mBottom;
            int oldR = mRight;
            //主要部分,设置view的左上右下位置.
            boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
            
            //通知监听器,界面layout发生变化
            if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
                onLayout(changed, l, t, r, b); //空实现
                mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
      
                ListenerInfo li = mListenerInfo;
                if (li != null && li.mOnLayoutChangeListeners != null) {
                    ArrayList<OnLayoutChangeListener> listenersCopy =
                            (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                    int numListeners = listenersCopy.size();
                    for (int i = 0; i < numListeners; ++i) {
                        listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                    }
                }
            }
      
            mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
            mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
        }
      

    -vieGroup layout过程

    public final void layout(int l, int t, int r, int b) {
            if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
                if (mTransition != null) {
                    mTransition.layoutChange(this);
                }
                super.layout(l, t, r, b); //其实还是调用了父类,最终调用view 的layout
            } else {
                // record the fact that we noop'd it; request layout when transition finishes
                mLayoutCalledWhileSuppressed = true;
            }
    }
    
    • getwidth getMeasuredWidth 区别

    • getMeasuredWith 形成于measure过程.getWidth 形成于layout过程.

    • 一般情况两者是相同的.除非如下情况

      public void layout (int l,int t,int r,int b){
          super.layout(l,t,r+100,b+100);
      }     
      

    -draw 过程

    1、对View的背景进行绘制
    2、保存当前的图层信息(可跳过)
    3、绘制View的内容
    4、对View的子View进行绘制(如果有子View)
    5、绘制View的褪色的边缘,类似于阴影效果(可跳过)
    6、绘制View的装饰(例如:滚动条)

    public void draw(Canvas canvas) {
     final int privateFlags = mPrivateFlags;
     final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
       (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
     mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
    
     /*
      * Draw traversal performs several drawing steps which must be executed
      * in the appropriate order:
      *
      *  1. Draw the background
      *  2. If necessary, save the canvas' layers to prepare for fading
      *  3. Draw view's content
      *  4. Draw children
      *  5. If necessary, draw the fading edges and restore layers
      *  6. Draw decorations (scrollbars for instance)
      */
     
     // Step 1, draw the background, if needed
     int saveCount;
     
     if (!dirtyOpaque) {
      drawBackground(canvas); //绘制背景
     }
     
     // skip step 2 & 5 if possible (common case)
     final int viewFlags = mViewFlags;
     boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
     boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
     if (!verticalEdges && !horizontalEdges) {
      // Step 3, draw the content
      if (!dirtyOpaque) onDraw(canvas); //空实现,需要子类实现
     
      // Step 4, draw the children
      dispatchDraw(canvas);
     
      // Overlay is part of the content and draws beneath Foreground
      if (mOverlay != null && !mOverlay.isEmpty()) {
       mOverlay.getOverlayView().dispatchDraw(canvas);
      }
     
      // Step 6, draw decorations (foreground, scrollbars)
      onDrawForeground(canvas);
     
      // we're done...
      return;
     }
     ...
    }
    
    
    分布讲解
    1绘制背景
    private void drawBackground(Canvas canvas) {
    
         //mBackground是该View的背景参数,比如背景颜色,没有背景就不绘制
         final Drawable background = mBackground;
         if (background == null) {
          return;
         }
         
         //根据View四个布局参数来确定背景的边界 mBackground.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);
         setBackgroundBounds();
         
         ...
         
         //获取当前View的mScrollX和mScrollY值
         final int scrollX = mScrollX;
         final int scrollY = mScrollY;
         if ((scrollX | scrollY) == 0) {
          background.draw(canvas);
         } else {
          //如果scrollX和scrollY有值,则对canvas的坐标进行偏移,再绘制背景
          canvas.translate(scrollX, scrollY);
          background.draw(canvas);
          canvas.translate(-scrollX, -scrollY);
         }
    }
        3.绘制子View,view中这个方法为空,viewgroup实现了这个方法
        protected void dispatchDraw(Canvas canvas) {
            boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
            final int childrenCount = mChildrenCount;
            final View[] children = mChildren;
            int flags = mGroupFlags;
                
            for (int i = 0; i < childrenCount; i++) { 
                while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
                    final View transientChild = mTransientViews.get(transientIndex);
                    if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||
                    transientChild.getAnimation() != null) {
                        more |= drawChild(canvas, transientChild, drawingTime); //这句是重点
                    }
                    transientIndex++;
                    if (transientIndex >= transientCount) {
                        transientIndex = -1;
                    }
                }
                int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
                final View child = (preorderedList == null)? children[childIndex] : preorderedList.get(childIndex);
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);//这句是重点
                }
            }
                //省略...
        }
    
        drawChild(canvas, transientChild, drawingTime)的实现,调用了子view的绘制方法
        protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
             return child.draw(canvas, this, drawingTime);
        }
    
    • view 的draw(Canvas canvas, ViewGroup parent, long drawingTime)
    • 我们主要来看核心部分,首先判断是否已经有缓存,即之前是否已经绘制过一次了,如果没有,则会调用draw(canvas)方法,开始正常的绘制,即上面所说的六个步骤,否则利用缓存来显示。
    • 这一步也可以归纳为ViewGroup绘制过程,它对子View进行了绘制,而子View又会调用自身的draw方法来绘制自身,这样不断遍历子View及子View的不断对自身的绘制,从而使得View树完成绘制
    •   Skip 6 绘制装饰,指View除了背景、内容、子View的其余部分,例如滚动条等,我们看View#onDrawForeground:
        public void onDrawForeground(Canvas canvas) {
         onDrawScrollIndicators(canvas);
         onDrawScrollBars(canvas);
         
         final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
         if (foreground != null) {
          if (mForegroundInfo.mBoundsChanged) {
           mForegroundInfo.mBoundsChanged = false;
           final Rect selfBounds = mForegroundInfo.mSelfBounds;
           final Rect overlayBounds = mForegroundInfo.mOverlayBounds;
         
           if (mForegroundInfo.mInsidePadding) {
            selfBounds.set(0, 0, getWidth(), getHeight());
           } else {
            selfBounds.set(getPaddingLeft(), getPaddingTop(),
              getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());
           }
         
           final int ld = getLayoutDirection();
           Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),
             foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);
           foreground.setBounds(overlayBounds);
          }
         
          foreground.draw(canvas);
         }
        }
      

    自定义view,宽高写为wrap_content时,如果不处理,和写成 march_parent是一样的,处理规则如下,mWidth,mHeigh为自定义宽高

    11.png

    相关文章

      网友评论

          本文标题:view 绘制流程

          本文链接:https://www.haomeiwen.com/subject/tolujxtx.html