9.view 绘制过程,讲讲draw/onDraw和drawChild


 9.1 view的绘制过程
     view的绘制过程在UI中还是非常重要的,view的绘制是从根节点,自上而下的一个过程,主要经历三个过程:
     
     先看performTraversals函数代码,这段代码很长,主要工作可以归结为三个函数:    
[java]  view plain copy
  1. ViewRootImpl.java中performTraversals()  
  2.     private void perfromTraversals(){  
  3.          final View host = mView;  
  4.          ....  
  5.          //1.measure过程  
  6.          host.<strong>measure</strong>(childWidthMeasureSpec,childHeightMeasureSpec);  
  7.            ....  
  8.          //2.layout过程  
  9.          host.<strong>layout</strong>(0,0,host.getMeasureWidth(),host.getMeasureHeight);          
  10.            .....  
  11.          //3 draw过程  
  12.          <strong>draw</strong>(fullRedrawNeeded);  
  13.     }  

     下面分别来介绍着三个过程:

     9.1.1 measure
     measure中主要涉及以下几个方法:
     public final void measure(int widthMeasureSpec,int heightMeasureSpec)
     protected void onMeasure(int widthMeasureSpec,int heightMeasureSpec)
     protected final void setMeasureDimension(int measuredWidht,int measureHeight)
     这里先还要介绍一个类:MeasureSpec。引入这个类的主要目的是在android中子view的尺寸还要受到父view的限制
     一个MeasureSpec是一个int 类型,共32b,有两部分组成:高两位是mode,剩下的30位是size
     mode中有三种模式:
     1 UNSPECIFIED:不受到父view的限制,可以设置为任意值。基本上不用到。
     2 EXACTLY:父view中指定子view的大小,不管子view如果设置。比如:match_parent、或者具体的大小
     3 AT_MOST:父view中指定子view最多只能这么多。比如:wrap_content
     在ViewRootImpl中已经说明了对应关系    
[java]  view plain copy
  1. private int getRootMeasureSpec(int windowSize, int rootDimension) {  
  2.       int measureSpec;  
  3.       switch (rootDimension) {  
  4.       case ViewGroup.LayoutParams.<strong>MATCH_PARENT</strong>:  
  5.           // Window can't resize. Force root view to be windowSize.  
  6.           measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.<strong>EXACTLY</strong>);  
  7.           break;  
  8.       case ViewGroup.LayoutParams.<strong>WRAP_CONTENT</strong>:  
  9.           // Window can resize. Set max size for root view.  
  10.           measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.<strong>AT_MOST</strong>);  
  11.           break;  
  12.       default:  
  13.           // Window wants to be an <strong>exact size</strong>. Force root view to be that size.  
  14.           measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.<strong>EXACTLY</strong>);  
  15.           break;  
  16.       }  
  17.       return measureSpec;  
  18.   }  

      可以看出match_parent具体尺寸对应的都是EXACTLYwrap_content对应的是AT_MOST
     接下来看一下view.measure函数的处理过程     
[java]  view plain copy
  1. public final void measure (int widthMeasureSpec, int heightMeasureSpec) {  
  2.             ....  
  3.          // measure ourselves, this should set the measured dimension flag back  
  4.          <strong>onMeasure</strong>(widthMeasureSpec, heightMeasureSpec);  
  5.           ....      
  6.          mOldWidthMeasureSpec = widthMeasureSpec;  
  7.          mOldHeightMeasureSpec = heightMeasureSpec;  
  8.    }  

    调用了onMeasure方法:    
[java]  view plain copy
  1. protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) {  
  2.       <strong>setMeasuredDimension</strong>(<strong>getDefaultSize</strong>(<strong>getSuggestedMinimumWidth</strong>(), widthMeasureSpec),  
  3.               getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
  4.   }  

     里面涉及了三个方法:getSuggestedMinimumWidth,getDefaultSize,setMeasuredDimension
     先看view.getSuggestedMinimumWidth()    
[java]  view plain copy
  1. protected int getSuggestedMinimumWidth() {  
  2.       int suggestedMinWidth = mMinWidth ;  
  3.       if (mBGDrawable != null) {  
  4.           final int bgMinWidth = mBGDrawable.getMinimumWidth();  
  5.           if (suggestedMinWidth < bgMinWidth) {  
  6.               suggestedMinWidth = bgMinWidth;  
  7.           }  
  8.       }  
  9.       return <strong>suggestedMinWidth</strong>;//返回背景图最小值和view最小值中的较大者  
  10.   }  

      getSuggestedMinimumHeight()方法类似,不做介绍。
     再看view.getDefaultSize()方法      
[java]  view plain copy
  1. public static int getDefaultSize(int size, int measureSpec) {  
  2.       int result = size;  
  3.       int specMode = MeasureSpec.getMode(measureSpec);  
  4.       int specSize = MeasureSpec.getSize(measureSpec);  
  5.       switch (specMode) {  
  6.       case MeasureSpec.<strong>UNSPECIFIED</strong>:  
  7.           result = size;<strong>//UNSPECIFIED,返回值为size值</strong>  
  8.           break;  
  9.       case MeasureSpec.<strong>AT_MOST</strong>:<strong>//AT_MOST,返回值为size值</strong>  
  10.       case MeasureSpec.<strong>EXACTLY</strong>:  
  11.           result = specSize;<strong>//EXACTLY,返回值为specSize值</strong>  
  12.           break;  
  13.       }  
  14.       return result;  
  15.   }  

    再看setMeasuredDimension方法:       
[java]  view plain copy
  1. protected final void setMeasuredDimension (int measuredWidth, int measuredHeight) {  
  2.       <strong> mMeasuredWidth = measuredWidth;  
  3.        mMeasuredHeight = measuredHeight;  
  4.        mPrivateFlags |= MEASURED_DIMENSION_SET ;</strong>  
  5.    }  

     我们在override完onMeasure方法之后一定要执行这个setMeasuredDimension方法,这个方法的目的就是保存measure之后的值。
     以上介绍的是view的measure过程,如果是viewGroup时流程会有区别:viewGroup循环measure所有子view。先看一看ViewGroup中的measureChildren方法    
[java]  view plain copy
  1. /** 
  2.    * Ask all of the children of this view to measure themselves, taking into account both the MeasureSpec requirements for this view and its padding. 
  3.    * We skip children that are in the GONE state The heavy lifting is done in getChildMeasureSpec.      
  4.    * @param widthMeasureSpec The width requirements for this view 
  5.    * @param heightMeasureSpec The height requirements for this view 
  6.    */  
  7.   protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {  
  8.       final int size = mChildrenCount;  
  9.       final View[] children = mChildren;  
  10.       <strong>for </strong>(int i = 0; i < size; ++i) {  
  11.           final View child = children[i];  
  12.           if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {//过滤掉GONE的view  
  13.               <strong>measureChild</strong>(child, widthMeasureSpec, heightMeasureSpec);//让所有的子view measure自己。调用measureChild方法。  
  14.           }  
  15.       }  
  16.   }  

     调用ViewGroup.measureChild(view,int,int)。类似measureChild函数还有measureChildWithMargins(view,int,int))
[java]  view plain copy
  1. /** 
  2.    * Ask one of the children of this view to measure itself, taking into account both the MeasureSpec requirements for this view and its padding. 
  3.    * The heavy lifting is done in getChildMeasureSpec. 
  4.    * @param child The child to measure 
  5.    * @param parentWidthMeasureSpec The width requirements for this view 
  6.    * @param parentHeightMeasureSpec The height requirements for this view 
  7.    */  
  8.   protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {  
  9.       final LayoutParams lp = child.getLayoutParams();  
  10. lt;strong>//主要工作还是在getChildMeasureSpec函数中进行</strong>  
  11.       final int childWidthMeasureSpec = <strong>getChildMeasureSpec</strong>(parentWidthMeasureSpec,mPaddingLeft+mPaddingRight, lp.width);  
  12.       final int childHeightMeasureSpec = <strong>getChildMeasureSpec</strong>(parentHeightMeasureSpec, mPaddingTop+ mPaddingBottom, lp.height);  
  13.   
  14.       child.<strong>measure</strong>(childWidthMeasureSpchildHeightMeasureSpec ec, childHeightMeasureSpec);//计算完childWidthMeasureSpec 、childHeightMeasureSpec 调用<strong>子view的measure方法</strong>。  
  15.   }  

     其中调用了ViewGroup.getChildMeasureSpec方法,这个方法比较复杂:  此方法的目的是根据父view对子view的 MeasureSpec和子view的layout参数产生一个最合适的 MeasureSpec 
 
[java]  view plain copy
  1. / * Does the hard part of measureChildren: figuring out the MeasureSpec to pass to a particular child. This method figures out the<strong> right MeasureSpec for one dimension</strong> (height or width) of one child view. The goal is to combine information from <strong>ourMeasureSpec </strong>with the <strong>LayoutParams of the child </strong>to get the<strong> best possible results</strong>. For example,if the this view knows its size (because its MeasureSpec has a mode of EXACTLY), and the child has indicated in its LayoutParamsthat it wants to be the same size as the parent, the parent should ask the child to layout given an exact size.  
  2.    * @param spec The requirements for this view  
  3.    * @param padding The padding of this view for the current dimension and  margins, if applicable  
  4.    * @param childDimension How big the child wants to be in the current    dimension  
  5.    * @return a MeasureSpec integer for the child  
  6.    */  
  7.   public static int getChildMeasureSpec(int spec, int padding, int childDimension) {  
  8.       int specMode = MeasureSpec.getMode(spec);  
  9.       int specSize = MeasureSpec.getSize(spec);  
  10.       int size = Math.max(0, specSize - padding);  
  11.       int resultSize = 0;  
  12.       int resultMode = 0;  
  13.   
  14.       switch (specMode) {  
  15.       // Parent has imposed an exact size on us  
  16.       case MeasureSpec.EXACTLY:  
  17.           if (childDimension >= 0) {  
  18.               resultSize = childDimension;  
  19.               resultMode = MeasureSpec.EXACTLY;  
  20.           } else if (childDimension == LayoutParams.MATCH_PARENT) {  
  21.               // Child wants to be our size. So be it.  
  22.               resultSize = size;  
  23.               resultMode = MeasureSpec.EXACTLY;  
  24.           } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  25.               // Child wants to determine its own size. It can't be  
  26.               // bigger than us.  
  27.               resultSize = size;  
  28.               resultMode = MeasureSpec.AT_MOST;  
  29.           }  
  30.           break;  
  31.       // Parent has imposed a maximum size on us  
  32.       case MeasureSpec.AT_MOST:  
  33.           if (childDimension >= 0) {  
  34.               // Child wants a specific size... so be it  
  35.               resultSize = childDimension;  
  36.               resultMode = MeasureSpec.EXACTLY;  
  37.           } else if (childDimension == LayoutParams.MATCH_PARENT) {  
  38.               // Child wants to be our size, but our size is not fixed.  
  39.               // Constrain child to not be bigger than us.  
  40.               resultSize = size;  
  41.               resultMode = MeasureSpec.AT_MOST;  
  42.           } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  43.               // Child wants to determine its own size. It can't be  
  44.               // bigger than us.  
  45.               resultSize = size;  
  46.               resultMode = MeasureSpec.AT_MOST;  
  47.           }  
  48.           break;  
  49.       // Parent asked to see how big we want to be  
  50.       case MeasureSpec.UNSPECIFIED:  
  51.           if (childDimension >= 0) {  
  52.               // Child wants a specific size... let him have it  
  53.               resultSize = childDimension;  
  54.               resultMode = MeasureSpec.EXACTLY;  
  55.           } else if (childDimension == LayoutParams.MATCH_PARENT) {  
  56.               // Child wants to be our size... find out how big it should  
  57.               // be  
  58.               resultSize = 0;  
  59.               resultMode = MeasureSpec.UNSPECIFIED;  
  60.           } else if (childDimension == LayoutParams.WRAP_CONTENT) {  
  61.               // Child wants to determine its own size.... find out how  
  62.               // big it should be  
  63.               resultSize = 0;  
  64.               resultMode = MeasureSpec.UNSPECIFIED;  
  65.           }  
  66.           break;  
  67.       }  
  68.       return MeasureSpec.makeMeasureSpec(resultSize, resultMode);  
  69.   }  
      可以看出上面的处理过程还是挺复杂的, 该方法返回父view对子view的一个32b的 measureSpec。
     measureChild方法最后调用view的measure方法,再按照view的measure流程绘制。
     具体的可以参看不同view控件,以及measure的实现。

     9.1.2 layout
     measure的过程就是确定view的大小 而layout的过程则是确定view 位置
     看一下view.layout()方法:    
[java]  view plain copy
  1. public void layout(int l, int t, int r, int b) {  
  2.       int oldL = mLeft ;  
  3.       int oldT = mTop ;  
  4.       int oldB = mBottom ;  
  5.       int oldR = mRight ;  
  6.       boolean changed = setFrame(l, t, r, b);  
  7.       if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED ) {  
  8.           if (ViewDebug. TRACE_HIERARCHY) {  
  9.               ViewDebug. trace( this, ViewDebug.HierarchyTraceType.ON_LAYOUT );  
  10.           }  
  11.           <strong>onLayout</strong>(changed, l, t, r, b);//调用onLayout方法  
  12.           mPrivateFlags &= ~LAYOUT_REQUIRED ;  
  13.           if (mOnLayoutChangeListeners != null) {  
  14.               ArrayList<OnLayoutChangeListener> listenersCopy =  
  15.                       (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners .clone();  
  16.               int numListeners = listenersCopy.size();  
  17.               for (int i = 0; i < numListeners; ++i) {  
  18.                   listenersCopy.get(i).onLayoutChange(this , l, t, r, b, oldL, oldT, oldR, oldB);//通知对象的listener调用onLayoutChange  
  19.               }  
  20.           }  
  21.       }  
  22.       mPrivateFlags &= ~FORCE_LAYOUT ;  
  23.   }       

      调用onLayout方法,再看onLayout方法是一个空实现:      
[java]  view plain copy
  1. protected void onLayout( boolean changed, int left, int top, int right, int bottom) {    }  
      也就是我们需要自己实现onLayout方法。
    这是view的layout过程,再看viewGroup过程,先看viewGroup.layout方法:    
[java]  view plain copy
  1. @Override  
  2.    public final void layout(int l, int t, int r, int b) {  
  3.        if (mTransition == null || !mTransition.isChangingLayout()) {  
  4.            super.<strong>layout</strong>(l, t, r, b);//<strong>调用父类view的layout方法</strong>  
  5.        } else {  
  6.            // record the fact that we noop'd it; request layout when transition finishes  
  7.            mLayoutSuppressed = true;  
  8.        }  
  9.    }  
   调用父类view的layout方法,再看onLayout方法   
[java]  view plain copy
  1. protected abstract void onLayout(boolean changed,int l, int t, int r, int b);  
    viewGroup.onLayout是抽象函数。
    ViewGroup对应的就是一个个具体的布局,看具体布局是如何实现的。
    LinearLayout的layoutVertical方法  
[java]  view plain copy
  1. void layoutVertical() {  
  2.      // ....       
  3.       for (int i = 0; i < count; i++) {  
  4.           final View child = getVirtualChildAt(i);  
  5.           if (child == null) {  
  6.               childTop += measureNullChild(i);  
  7.           } else if (child.getVisibility() != GONE) {  
  8.               childTop += lp. topMargin ;  
  9.               <strong>setChildFrame</strong>(child, childLeft, childTop + getLocationOffset(child),  
  10.                       childWidth, childHeight);  
  11.               childTop += childHeight + lp. bottomMargin + getNextLocationOffset(child);  
  12.               i += getChildrenSkipCount(child, i);  
  13.           }  
  14.       }  
  15.   }  
     其中出现了LinearLayout.setChildFrame方法:      
[java]  view plain copy
  1. private void setChildFrame(View child, int left, int top, int width, int height) {         
  2.     child.layout(left, top, left + width, top + height);  
  3. }  
    再看FrameLayout的onLayout方法:  
[java]  view plain copy
  1. protected void onLayout( boolean changed, int left, int top, int right, int bottom) {  
  2.      final int count = getChildCount();          
  3.      for (int i = 0; i < count; i++) {  
  4.          final View child = getChildAt(i);  
  5.          if (child.getVisibility() != GONE) {  
  6.              final LayoutParams lp = (LayoutParams) child.getLayoutParams();  
  7.              final int width = child.getMeasuredWidth();  
  8.              final int height = child.getMeasuredHeight();                    ..  
  9.              switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK ) {  
  10.                 ..  
  11.              }  
  12.              switch (verticalGravity) {  
  13.                 ..  
  14.              }  
  15.              child.<strong>layout</strong>(childLeft, childTop, childLeft + width, childTop + height);  
  16.          }  
  17.      }  
  18.  }     
     可以看出都是调用子view中的layout方法。所以在自定义view的时候需要自己重写view的onLayout方法。


     9.1.3 draw
     得到位置之后,最后就是绘制view了。
     view做的事情主要有以下几个部分:
     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)
     比较重要的是step3和step4:        
[java]  view plain copy
  1. // Step 3, draw the content  
  2.      if (!dirtyOpaque) onDraw(canvas);  
  3.   其中onDraw实现:空实现。因为view怎样展示是由具体的view自行实现。  
  4.    protected void onDraw(Canvas canvas) {  
  5.       }  
  6.      // Step 4, draw the children  
  7.      dispatchDraw(canvas);  
里面涉及了两个函数,onDraw和dispatchDraw函数,先看onDraw函数:
protected void onDraw(Canvas canvas) { }//空实现,因为view到底怎么展示是view自己决定的,所以要空实现
再看dispatchDraw方法:
[java]  view plain copy
  1. <pre name="code" class="java">    protected void dispatchDraw(Canvas canvas) {  }也是空实现,因为view中没有子view,也就不需要向子view派发draw请求了,所以是空实现。  
 
  
viewGroup中没有重写draw函数,所以是继承view的draw函数; 
viewGroup中也没有重写onDraw函数,所以是继承view的onDraw函数,也就是空实现
      再看viewGroup中的viewGroup.dispatchDraw(Canvas)方法:       
[java]  view plain copy
  1. protected void dispatchDraw(Canvas canvas) {  
  2.      final int count = mChildrenCount;  
  3.      final View[] children = mChildren ;  
  4.      int flags = mGroupFlags ;       ..            
  5.   
  6.      if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {  
  7.          for (int i = 0; i < count; i++) {  
  8.              final View child = children[i];  
  9.              if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
  10.                  more |= <strong>drawChild</strong>(canvas, child, drawingTime);  
  11.              }  
  12.          }  
  13.      } else {  
  14.          for (int i = 0; i < count; i++) {  
  15.              final View child = children[getChildDrawingOrder(count, i)];  
  16.              if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {  
  17.                  more |= <strong>drawChild</strong>(canvas, child, drawingTime);  
  18.              }  
  19.          }  
  20.      }  
  21.   
  22.      // Draw any disappearing views that have animations  
  23.      if (mDisappearingChildren != null) {  
  24.          final ArrayList<View> disappearingChildren = mDisappearingChildren ;  
  25.          final int disappearingCount = disappearingChildren.size() - 1;  
  26.          // Go backwards -- we may delete as animations finish  
  27.          for (int i = disappearingCount; i >= 0; i--) {  
  28.              final View child = disappearingChildren.get(i);  
  29.              more |= <strong>drawChild</strong>(canvas, child, drawingTime);  
  30.          }  
  31.      }  
  32.     ..  
  33.  }  

  又调用了drawChild(Canvas,View,Long)方法,此方法主要作用是draw viewGroup中的某一个view,代码太长就不贴出现了。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值