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
目录
相关文章
|
11天前
|
消息中间件 Android开发 索引
Android面试高频知识点(4) 详解Activity的启动流程
讲解Activity的启动流程了,Activity的启动流程相对复杂一下,涉及到了Activity中的生命周期方法,涉及到了Android体系的CS模式,涉及到了Android中进程通讯Binder机制等等, 首先介绍一下Activity,这里引用一下Android guide中对Activity的介绍:
25 4
|
26天前
|
缓存 搜索推荐 Android开发
安卓应用开发中的自定义View组件实践
【9月更文挑战第10天】在安卓开发领域,自定义View是提升用户体验和实现界面个性化的重要手段。本文将通过一个实际案例,展示如何在安卓项目中创建和使用自定义View组件,包括设计思路、实现步骤以及可能遇到的问题和解决方案。文章不仅提供了代码示例,还深入探讨了自定义View的性能优化技巧,旨在帮助开发者更好地掌握这一技能。
|
28天前
|
Android开发
Android中SurfaceView的双缓冲机制和普通View叠加问题解决办法
本文介绍了 Android 平台上的 SurfaceView,这是一种高效的图形渲染控件,尤其适用于视频播放、游戏和图形动画等场景。文章详细解释了其双缓冲机制,该机制通过前后缓冲区交换来减少图像闪烁,提升视觉体验。然而,SurfaceView 与普通 View 叠加时可能存在 Z-Order 不一致、同步问题及混合渲染难题。文中提供了使用 TextureView、调整 Z-Order 和创建自定义组合控件等多种解决方案。
61 9
|
1月前
|
Android开发 容器
Android经典实战之如何获取View和ViewGroup的中心点
本文介绍了在Android中如何获取`View`和`ViewGroup`的中心点坐标,包括计算相对坐标和屏幕上的绝对坐标,并提供了示例代码。特别注意在视图未完成测量时可能出现的宽高为0的问题及解决方案。
27 7
|
11天前
|
Android开发 开发者
Android面试之Activity启动流程简述
每个Android开发者都熟悉的Activity,但你是否了解它的启动流程呢?本文将带你深入了解。启动流程涉及四个关键角色:Launcher进程、SystemServer的AMS、应用程序的ActivityThread及Zygote进程。核心在于AMS与ActivityThread间的通信。文章详细解析了从Launcher启动Activity的过程,包括通过AIDL获取AMS、Zygote进程启动以及ActivityThread与AMS的通信机制。接着介绍了如何创建Application及Activity的具体步骤。整体流程清晰明了,帮助你更深入理解Activity的工作原理。
16 0
|
2月前
|
XML 搜索推荐 Android开发
安卓开发中的自定义View组件实践
【8月更文挑战第30天】探索Android世界,自定义View是提升应用界面的关键。本文以简洁的语言带你了解如何创建自定义View,从基础到高级技巧,一步步打造个性化的UI组件。
|
2月前
|
Android开发
我的Android进阶修炼:安卓启动流程之init(1)
本文深入分析了Android系统中的init进程,包括其源码结构、主要功能以及启动流程的详细注解,旨在帮助读者理解init作为用户空间的1号进程在Android启动过程中的关键作用。
32 1
|
2月前
|
API Android开发 开发者
Android经典实战之使用ViewCompat来处理View兼容性问题
本文介绍Android中的`ViewCompat`工具类,它是AndroidX库核心部分的重要兼容性组件,确保在不同Android版本间处理视图的一致性。文章列举了设置透明度、旋转、缩放、平移等功能,并提供了背景色、动画及用户交互等实用示例。通过`ViewCompat`,开发者可轻松实现跨版本视图操作,增强应用兼容性。
93 5
|
2月前
|
XML 前端开发 Android开发
Android面试高频知识点(3) 详解Android View的绘制流程
View的绘制和事件处理是两个重要的主题,上一篇《图解 Android事件分发机制》已经把事件的分发机制讲得比较详细了,这一篇是针对View的绘制,View的绘制如果你有所了解,基本分为measure、layout、draw 过程,其中比较难理解就是measure过程,所以本篇文章大幅笔地分析measure过程,相对讲得比较详细,文章也比较长,如果你对View的绘制还不是很懂,对measure过程掌握得不是很深刻,那么耐心点,看完这篇文章,相信你会有所收获的。
80 2
|
3月前
|
Java Android开发
android 设置系统时间的流程
android 设置系统时间的方法
252 2
下一篇
无影云桌面