开发者社区> 知与谁同> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

Android View绘制的三大流程

简介:
+关注继续查看
本文讲的是Android View绘制的三大流程,View的工作流程主要是指measure、layout、draw这三大流程,即测量、布局和绘制,其中measure确定View的测量宽高,layout根据测量的宽高确定View在其父View中的四个顶点的位置,而draw则将View绘制到屏幕上,这样通过ViewGroup的递归遍历,一个View树就展现在屏幕上了。说的简单,下面带大家一步一步从源码中分析:

Android的View是树形结构的:

基本概念

在介绍View的三大流程之前,我们必须先介绍一些基本的概念,才能更好地理解这整个过程。

Window的概念

Window表示的是一个窗口的概念,它是站在WindowManagerService角度上的一个抽象的概念,Android中所有的视图都是通过Window来呈现的,不管是Activity、Dialog还是Toast,只要有View的地方就一定有Window。

这里需要注意的是,这个抽象的Window概念和PhoneWindow这个类并不是同一个东西,PhoneWindow表示的是手机屏幕的抽象,它充当Activity和DecorView之间的媒介,就算没有PhoneWindow也是可以展示View的。

抛开一切,仅站在WindowManagerService的角度上,Android的界面就是由一个个Window层叠展现的,而Window又是一个抽象的概念,它并不是实际存在的,它是以View的形式存在,这个View就是DecorView。

关于Window这方面的内容,我们这里先了解一个大概

DecorView的概念

DecorView是整个Window界面的最顶层View,View的测量、布局、绘制、事件分发都是由DecorView往下遍历这个View树。DecorView作为顶级View,一般情况下它内部会包含一个竖直方向的LinearLayout,在这个LinearLayout里面有上下两个部分(具体情况和Android的版本及主题有关),上面是【标题栏】,下面是【内容栏】。在Activity中我们通过setContentView所设置的布局文件其实就是被加载到【内容栏】中的,而内容栏的id是content,因此指定布局的方法叫setContent().

ViewRoot的概念

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


  1. WindowManagerGlobal.java 
  2.  
  3. root = new ViewRootImpl(view.getContext(), display);   
  4. root.setView(view, wparams, panelParentView);  

Java

View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout和draw三个过程才能最终将一个View绘制出来,大致流程如下图:

Measure测量

为了更好地理解View的测量过程,我们还需要理解MeasureSpec,它是View的一个内部类,它表示对View的测量规格。MeasureSpec代表一个32位int值,高2位代表SpecMode(测量模式),低30位代表SpecSize(测量大小),我们可以看看它的具体实现:


  1. MeasureSpec.java 
  2.  
  3. public static class MeasureSpec {   
  4.         private static final int MODE_SHIFT = 30; 
  5.         private static final int MODE_MASK  = 0x3 << MODE_SHIFT; 
  6.  
  7.         /** 
  8.           * UNSPECIFIED 模式: 
  9.           * 父View不对子View有任何限制,子View需要多大就多大 
  10.           */  
  11.         public static final int UNSPECIFIED = 0 << MODE_SHIFT; 
  12.  
  13.         /** 
  14.           * EXACTYLY 模式: 
  15.           * 父View已经测量出子Viwe所需要的精确大小,这时候View的最终大小 
  16.           * 就是SpecSize所指定的值。对应于match_parent和精确数值这两种模式 
  17.           */  
  18.         public static final int EXACTLY     = 1 << MODE_SHIFT; 
  19.  
  20.         /** 
  21.           * AT_MOST 模式: 
  22.           * 子View的最终大小是父View指定的SpecSize值,并且子View的大小不能大于这个值, 
  23.           * 即对应wrap_content这种模式 
  24.           */  
  25.         public static final int AT_MOST     = 2 << MODE_SHIFT; 
  26.  
  27.         //将size和mode打包成一个32位的int型数值 
  28.         //高2位表示SpecMode,测量模式,低30位表示SpecSize,某种测量模式下的规格大小 
  29.         public static int makeMeasureSpec(int sizeint mode) { 
  30.             if (sUseBrokenMakeMeasureSpec) { 
  31.                 return size + mode; 
  32.             } else { 
  33.                 return (size & ~MODE_MASK) | (mode & MODE_MASK); 
  34.             } 
  35.         } 
  36.  
  37.         //将32位的MeasureSpec解包,返回SpecMode,测量模式 
  38.         public static int getMode(int measureSpec) { 
  39.             return (measureSpec & MODE_MASK); 
  40.         } 
  41.  
  42.         //将32位的MeasureSpec解包,返回SpecSize,某种测量模式下的规格大小 
  43.         public static int getSize(int measureSpec) { 
  44.             return (measureSpec & ~MODE_MASK); 
  45.         } 
  46.         //... 
  47.     }  

Java

MeasureSpec通过将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,并提供了打包和解包的方法。

SpecMode有三种类型,每一类都表示特殊的含义:

UNSPECIFIED

父容器不对View有任何限制,要多大就给多大,这种情况一般用于系统内部,表示一种测量的状态;

EXACTLY

父容器已经检测出View所需的精确大小,这个时候View的最终打消就是SpecSize所指定的值。它对应于LayoutParams中的match_parent和具体数值这两种模式。

AT_MOST

父容器指定了一个可用大小即SpecSize,View的大小不能大于这个值,具体是什么值要看不同View的具体实现。它对应于LayoutParams中wrap_content。

View的MeasureSpec是由父容器的MeasureSpec和自己的LayoutParams决定的,但是对于DecorView来说有点不同,因为它没有父类。在ViewRootImpl中的measureHierarchy方法中有如下一段代码展示了DecorView的MeasureSpec的创建过程,其中desiredWindowWidth和desireWindowHeight是屏幕的尺寸大小:

ViewGroup的measure


  1. childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); 
  2.  
  3. childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); 
  4.  
  5. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);  

Java

再看看getRootMeasureSpec方法:


  1. private static int getRootMeasureSpec(int windowSize, int rootDimension) { 
  2.         int measureSpec; 
  3.         switch (rootDimension) { 
  4.  
  5.         case ViewGroup.LayoutParams.MATCH_PARENT: 
  6.             // Window can't resize. Force root view to be windowSize. 
  7.             measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); 
  8.             break; 
  9.         case ViewGroup.LayoutParams.WRAP_CONTENT: 
  10.             // Window can resize. Set max size for root view
  11.             measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); 
  12.             break; 
  13.         default
  14.             // Window wants to be an exact sizeForce root view to be that size
  15.             measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); 
  16.             break; 
  17.         } 
  18.         return measureSpec; 
  19.     }  

Java

通过以上代码,DecorView的MeasureSpec的产生过程就很明确了,因为DecorView是FrameLyaout的子类,属于ViewGroup,对于ViewGroup来说,除了完成自己的measure过程外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。和View不同的是,ViewGroup是一个抽象类,他没有重写View的onMeasure方法,这里很好理解,因为每个具体的ViewGroup实现类的功能是不同的,如何测量应该让它自己决定,比如LinearLayout和RelativeLayout。

因此在具体的ViewGroup中需要遍历去测量子View,这里我们看看ViewGroup中提供的测量子View的measureChildWithMargins方法:


  1. protected void measureChildWithMargins(View child, 
  2.             int parentWidthMeasureSpec, int widthUsed, 
  3.             int parentHeightMeasureSpec, int heightUsed) { 
  4.         final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 
  5.  
  6.         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, 
  7.                 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin 
  8.                         + widthUsed, lp.width); 
  9.         final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, 
  10.                 mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin 
  11.                         + heightUsed, lp.height); 
  12.  
  13.         child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 
  14.     }  

Java

上述方法会对子元素进行measure,在调用子元素的measure方法之前会先通过getChildMeasureSpec方法来得到子元素的MeasureSpec。从代码上看,子元素的MeasureSpec的创建与父容器的MeasureSpec和本身的LayoutParams有关,此外和View的margin和父类的padding有关,现在看看getChildMeasureSpec的具体实现:


  1. ViewGroup.java 
  2.  
  3. public static int getChildMeasureSpec(int spec, int padding, int childDimension) {   
  4.     int specMode = MeasureSpec.getMode(spec); 
  5.     int specSize = MeasureSpec.getSize(spec); 
  6.  
  7.     int size = Math.max(0, specSize - padding); 
  8.  
  9.     int resultSize = 0; 
  10.     int resultMode = 0; 
  11.  
  12.     switch (specMode) { 
  13.     // Parent has imposed an exact size on us 
  14.     case MeasureSpec.EXACTLY: 
  15.         if (childDimension >= 0) { 
  16.             resultSize = childDimension; 
  17.             resultMode = MeasureSpec.EXACTLY; 
  18.         } else if (childDimension == LayoutParams.MATCH_PARENT) { 
  19.             // Child wants to be our size. So be it. 
  20.             resultSize = size
  21.             resultMode = MeasureSpec.EXACTLY; 
  22.         } else if (childDimension == LayoutParams.WRAP_CONTENT) { 
  23.             // Child wants to determine its own size. It can't be 
  24.             // bigger than us. 
  25.             resultSize = size
  26.             resultMode = MeasureSpec.AT_MOST; 
  27.         } 
  28.         break; 
  29.  
  30.     // Parent has imposed a maximum size on us 
  31.     case MeasureSpec.AT_MOST: 
  32.         if (childDimension >= 0) { 
  33.             // Child wants a specific size... so be it 
  34.             resultSize = childDimension; 
  35.             resultMode = MeasureSpec.EXACTLY; 
  36.         } else if (childDimension == LayoutParams.MATCH_PARENT) { 
  37.             // Child wants to be our size, but our size is not fixed. 
  38.             // Constrain child to not be bigger than us. 
  39.             resultSize = size
  40.             resultMode = MeasureSpec.AT_MOST; 
  41.         } else if (childDimension == LayoutParams.WRAP_CONTENT) { 
  42.             // Child wants to determine its own size. It can't be 
  43.             // bigger than us. 
  44.             resultSize = size
  45.             resultMode = MeasureSpec.AT_MOST; 
  46.         } 
  47.         break; 
  48.  
  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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size
  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 = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size
  64.             resultMode = MeasureSpec.UNSPECIFIED; 
  65.         } 
  66.         break; 
  67.     } 
  68.     //noinspection ResourceType 
  69.     return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 
  70.  

Java

上述代码根据父类的MeasureSpec和自身的LayoutParams创建子元素的MeasureSpec,具体过程同学们自行分析,最终的创建规则如下表:

ViewGroup在遍历完子View后,需要根据子元素的测量结果来决定自己最终的测量大小,并调用setMeasuredDimension方法保存测量宽高值。


  1. setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),heightSizeAndState); 

Java

这里调用了resolveSizeAndState来确定最终的大小,主要是保证测量的大小不能超过父容器的最大剩余空间maxWidth,这里我们看看它里面的实现:


  1. public static int resolveSizeAndState(int sizeint measureSpec, int childMeasuredState) { 
  2.         final int specMode = MeasureSpec.getMode(measureSpec); 
  3.         final int specSize = MeasureSpec.getSize(measureSpec); 
  4.         final int result; 
  5.         switch (specMode) { 
  6.             case MeasureSpec.AT_MOST: 
  7.                 if (specSize < size) { 
  8.                     result = specSize | MEASURED_STATE_TOO_SMALL; 
  9.                 } else { 
  10.                     result = size
  11.                 } 
  12.                 break; 
  13.             case MeasureSpec.EXACTLY: 
  14.                 result = specSize; 
  15.                 break; 
  16.             case MeasureSpec.UNSPECIFIED: 
  17.             default
  18.                 result = size
  19.         } 
  20.         return result | (childMeasuredState & MEASURED_STATE_MASK); 
  21.     }  

Java

关于具体ViewGroup的onMeasure过程这里不做分析,由于每种布局的测量方式不一样,不可能逐个分析,但在它们的onMeasure里面的步骤是有一定规律的:

1.根据各自的测量规则遍历Children元素,调用getChildMeasureSpec方法得到Child的measureSpec;

2.调用Child的measure方法;

3.调用setMeasuredDimension确定最终的大小。

View的measure

View的measure过程由其measure方法来完成,measure方法是一个final类型的方法,这意味着子类不能重写此方法,在View的measure方法里面会去调用onMeasure方法,我们这里只要看onMeasure的实现即可,如下:


  1. View.java 
  2.  
  3.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 
  4.         setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), 
  5.                 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); 
  6.     }  

Java

代码很简单,我们继续看看getDefaultSize方法的实现:


  1. View.java 
  2.  
  3.     public static int getDefaultSize(int sizeint measureSpec) { 
  4.         int result = size
  5.         int specMode = MeasureSpec.getMode(measureSpec); 
  6.         int specSize = MeasureSpec.getSize(measureSpec); 
  7.  
  8.         switch (specMode) { 
  9.         case MeasureSpec.UNSPECIFIED: 
  10.             result = size
  11.             break; 
  12.         case MeasureSpec.AT_MOST: 
  13.         case MeasureSpec.EXACTLY: 
  14.             result = specSize; 
  15.             break; 
  16.         } 
  17.         return result; 
  18.     } 

Java

从上述代码可以得出,View的宽/高由specSize决定,直接继承View的自定义控件需要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent。

上述就是View的measure大致过程,在measure完成之后,通过getMeasuredWidth/Height方法就可以获得测量后的宽高,这个宽高一般情况下就等于View的最终宽高了,因为View的layout布局的时候就是根据measureWidth/Height来设置宽高的,除非在layout中修改了measure值。

Layout布局

Layout的作用是ViewGroup用来确定子元素的位置,当ViewGroup的位置被确定后,它在onLayout中会遍历所有的子元素并调用其layout方法。简单的来说就是,layout方法确定View本身的位置,而onLayout方法则会确定所有子元素的位置。

先看看View的layout方法:


  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.  
  18.             if (shouldDrawRoundScrollbar()) { 
  19.                 if(mRoundScrollbarRenderer == null) { 
  20.                     mRoundScrollbarRenderer = new RoundScrollbarRenderer(this); 
  21.                 } 
  22.             } else { 
  23.                 mRoundScrollbarRenderer = null
  24.             } 
  25.  
  26.             mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; 
  27.  
  28.             ListenerInfo li = mListenerInfo; 
  29.             if (li != null && li.mOnLayoutChangeListeners != null) { 
  30.                 ArrayList<OnLayoutChangeListener> listenersCopy = 
  31.                         (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); 
  32.                 int numListeners = listenersCopy.size(); 
  33.                 for (int i = 0; i < numListeners; ++i) { 
  34.                     listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); 
  35.                 } 
  36.             } 
  37.         } 
  38.  
  39.         mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; 
  40.         mPrivateFlags3 |= PFLAG3_IS_LAID_OUT; 
  41.     }  

因微信字数限制,请点击原文链接查看完整内容

总结

到这里,View的measure、layout、draw三大流程就说完了,这里做一下总结:

  • 如果是自定义ViewGroup的话,需要重写onMeasure方法,在onMeasure方法里面遍历测量子元素,同理onLayout方法也是一样,最后实现onDraw方法绘制自己;
  • 如果自定义View的话,则需要从写onMeasure方法,处理wrap_content的情况,不需要处理onLayout,最后实现onDraw方法绘制自己; 

本文作者:佚名

来源:51CTO

原文标题:Android View绘制的三大流程

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Android绘制竖直虚线完美解决方案—自定义View
开发中我们经常会遇到绘制虚线的需求,一般我们使用一个drawable文件即可实现,下面我会先列举常规drawable文件的实现方式。
0 0
Android进阶之绘制-自定义View完全掌握(四)
Android进阶之绘制-自定义View完全掌握(四)
0 0
Android进阶之绘制-自定义View完全掌握(三)
Android进阶之绘制-自定义View完全掌握(三)
0 0
Android进阶之绘制-自定义View完全掌握(二)
Android进阶之绘制-自定义View完全掌握(二)
0 0
Android进阶之绘制-自定义View完全掌握(一)
Android进阶之绘制-自定义View完全掌握(一)
0 0
Android自定义控件 | View绘制原理(画什么?)
从源码的角度分析“绘制(draw)”。View绘制只决定绘制的顺序,具体绘制内容由各个子View自己决定。
0 0
Android自定义控件 | View绘制原理(画在哪?)
从源码的角度分析“定位(layout)”。 位置都是相对的,比如“我在你的右边”、“你在广场的西边”。为了表明位置,总是需要一个参照物。。。
0 0
Android自定义控件 | View绘制原理(画多大?)
这一篇将以源码中的几个关键函数为线索分析“测量(measure)”。 如果想直接看结论可以移步到第三篇末尾。 真正的测量工作在onMeasure()中进行。。。
0 0
Android 开发进阶: 自定义 View 1-1 绘制基础
Android 开发进阶: 自定义 View 1-1 绘制基础
0 0
Android窗口管理分析(4):Android View绘制内存的分配、传递、使用
Android窗口管理分析(4):Android View绘制内存的分配、传递、使用
0 0
+关注
文章
问答
文章排行榜
最热
最新
相关电子书
更多
Android组件化实现
立即下载
蚂蚁聚宝Android秒级编译—— Freeline
立即下载
Android插件化:从入门到放弃
立即下载