Android View底层到底是怎么绘制的

Android绘制链图:

网上很多讲Android  view的绘制流程往往只讲到了Measure - Layout - Draw。

但是,这只是一个大体的流程,而我们需要探讨的是Android在我们调用setcontentView()之后,系统给我们干了什么事情,这个完整的逻辑是什么样的,却很少有人讲,还是先看下系统代码吧。

 

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. public void setContentView(@LayoutRes int layoutResID) {  
  2.        getWindow().setContentView(layoutResID);  
  3.        initWindowDecorActionBar();  
  4.    }  

而最终调用了initWindowDecorActionBar这个方法,我们看下这个方法里面都实现了什么

 

 

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size: 16px;"</span><span style="font-size:14px;"> private void initWindowDecorActionBar() {  
  2.         Window window = getWindow();  
  3.         // Initializing the window decor can change window feature flags.  
  4.         // Make sure that we have the correct set before performing the test below.  
  5.         window.getDecorView();  
  6.   
  7.         if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {  
  8.             return;  
  9.         }  
  10.   
  11.         mActionBar = new WindowDecorActionBar(this);  
  12.         mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);  
  13.   
  14.         mWindow.setDefaultIcon(mActivityInfo.getIconResource());  
  15.         mWindow.setDefaultLogo(mActivityInfo.getLogoResource());  
  16.     }</span>  

根据人家给我们的注释,这段代码是创建一个actionbar,初始化这个view和actionbar。这里面有一段很重要的代码:

 

 

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. window.getDecorView();  

正式这段代码告知系统可以从view的根节点开始绘制了,通过DecorView方法,decorview调用了performTraversals方法,我们来看下performTraversals源码:

 

 

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. <span style="font-size:14px;">private void performTraversals() {    
  2.     final View host = mView;    
  3.     ...    
  4.     host.measure(childWidthMeasureSpec, childHeightMeasureSpec);    
  5.     ...    
  6.     host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());    
  7.     ...    
  8.     draw(fullRedrawNeeded);    
  9. </span>  

 

调用然后系统再调用Measure - Layout - Draw实现了View的绘制。

我们看一下完整的绘制流程,直接上一张图,或许更能说明这个意思:




到这里,系统会调用我们之前的比较熟悉的几个方法:Measure - Layout - Draw

 

Measure

 

 

Measure过程是计算视图大小,View中视图measure过程相关的方法主要有三个

 

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec)    
  2. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)    
  3. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)    

measure调用onMeasure,onMeasure测量完成后setMeasureDimension,setMeasureDimension是final类型,view的子类不需要重写。

 

measure 源码:

 

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. public final void measure(int widthMeasureSpec, int heightMeasureSpec) {    
  2.     if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||    
  3.             widthMeasureSpec != mOldWidthMeasureSpec ||    
  4.             heightMeasureSpec != mOldHeightMeasureSpec) {    
  5.     
  6.         // first clears the measured dimension flag    
  7.         mPrivateFlags &= ~MEASURED_DIMENSION_SET;    
  8.     
  9.         if (ViewDebug.TRACE_HIERARCHY) {    
  10.             ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);    
  11.         }    
  12.     
  13.         // measure ourselves, this should set the measured dimension flag back    
  14.         onMeasure(widthMeasureSpec, heightMeasureSpec);    
  15.     
  16.         // flag not set, setMeasuredDimension() was not invoked, we raise    
  17.         // an exception to warn the developer    
  18.         if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {    
  19.             throw new IllegalStateException("onMeasure() did not set the"    
  20.                     + " measured dimension by calling"    
  21.                     + " setMeasuredDimension()");    
  22.         }    
  23.     
  24.         mPrivateFlags |= LAYOUT_REQUIRED;    
  25.     }    
  26.     
  27.     mOldWidthMeasureSpec = widthMeasureSpec;    
  28.     mOldHeightMeasureSpec = heightMeasureSpec;    
  29. }    


我们看一下OnMearsure方法:

 

 

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  2.        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
  3.                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
  4.    }  

这个方法主要是实现setMeasuredDimension,这个方法是测量view的大小:

 

 

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
  2.        boolean optical = isLayoutModeOptical(this);  
  3.        if (optical != isLayoutModeOptical(mParent)) {  
  4.            Insets insets = getOpticalInsets();  
  5.            int opticalWidth  = insets.left + insets.right;  
  6.            int opticalHeight = insets.top  + insets.bottom;  
  7.   
  8.            measuredWidth  += optical ? opticalWidth  : -opticalWidth;  
  9.            measuredHeight += optical ? opticalHeight : -opticalHeight;  
  10.        }  
  11.        setMeasuredDimensionRaw(measuredWidth, measuredHeight);  
  12.    }  

而对于这个measuredWidth和measuredHeight参数,系统却调了一个getDefaultSize();

 

 

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  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.   
  6.         switch (specMode) {  
  7.         case MeasureSpec.UNSPECIFIED:  
  8.             result = size;  
  9.             break;  
  10.         case MeasureSpec.AT_MOST:  
  11.         case MeasureSpec.EXACTLY:  
  12.             result = specSize;  
  13.             break;  
  14.         }  
  15.         return result;  
  16.     }  

widthMeasureSpec和heightMeasureSpec决定了Mode和Size的值,widthMeasureSpec和heightMeasureSpec来自父视图,这两个值都是由父视图经过计算后传递给子视图的,说明父视图会在一定程度上决定子视图的大小。但是最外层的根视图,它的widthMeasureSpec和heightMeasureSpec又是从哪里得到的呢?这就需要去分析ViewRoot中的源码了.

关于视图的measure过程可以阅读以下LinearLayout源码。

 

 

Layout

measure过程确定视图的大小,而layout过程确定视图的位置。
[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. public void layout(int l, int t, int r, int b) {  
  2.         if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {  
  3.             onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);  
  4.             mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;  
  5.         }  
  6.   
  7.         int oldL = mLeft;  
  8.         int oldT = mTop;  
  9.         int oldB = mBottom;  
  10.         int oldR = mRight;  
  11.   
  12.         boolean changed = isLayoutModeOptical(mParent) ?  
  13.                 setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);  
  14.   
  15.         if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {  
  16.             onLayout(changed, l, t, r, b);  
  17.             mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;  
  18.   
  19.             ListenerInfo li = mListenerInfo;  
  20.             if (li != null && li.mOnLayoutChangeListeners != null) {  
  21.                 ArrayList<OnLayoutChangeListenerlistenersCopy =  
  22.                         (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();  
  23.                 int numListeners = listenersCopy.size();  
  24.                 for (int i = 0; i numListeners; ++i) {  
  25.                     listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);  
  26.                 }  
  27.             }  
  28.         }  
  29.   
  30.         mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;  
  31.         mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;  
  32.     }  
 函数中参数l、t、r、b是指view的左、上、右、底的位置,通过这几个参数来确定view在Windows的位置。

在layout函数中,重载了一个空函数

 

 

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {    
  2.   }    

这个需要子类去实现的。

 

比如Linearlayout:

 

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. protected void onLayout(boolean changed, int l, int t, int r, int b) {    
  2.      if (mOrientation == VERTICAL) {    
  3.          layoutVertical();    
  4.      } else {    
  5.          layoutHorizontal();    
  6.      }    
  7.  }   


具体实现请自行看源码。

 

而在最后无论是layoutVertical还是layoutHorizontal都会掉一个setChildFrame方法来控制显示位置。

 

[html] view plain copy
 
 print?在CODE上查看代码片派生到我的代码片
  1. private void setChildFrame(View child, int left, int top, int width, int height) {            
  2.     child.layout(left, top, left + width, top + height);    
  3. }    

从上面看出,layout也是一个自上而下的过程,先设置父视图位置,在循环子视图,父视图位置一定程度上决定了子视图位置。

 

 

Draw

 
draw过程调用顺序在measure()和layout()之后,同样的,performTraversals()发起的draw过程最终会调用到mView的draw()函数,对于activity来说就是调用的PhoneWindow.DecorView。
 

 

 

*      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)


根据view源码的注释,

 

1,绘制背景

2,保存画布图层

3,调用了onDraw方法,子类中实现onDraw方法

4,使用的dispatchDraw方法

View或ViewGroup的子类不用再重载ViewGroup中该方法,因为它已经有了默认而且标准的view系统流程。dispatchDraw()内部for循环调用drawChild()分别绘制每一个子视图,而drawChild()内部又会调用draw()函数完成子视图的内部绘制工作。

有兴趣的可以看看onDraw的源码。

posted @ 2017-04-21 22:45  天涯海角路  阅读(227)  评论(0)    收藏  举报