本篇文章会从源码(基于Android 6.0)角度分析Android中View的绘制流程,侧重于对总体流程的分析,对一些难以理解的点加以重点阐述,目的是把View绘制的整个流程把握好,而对于特定实现细节则能够往后再对相应源码进行研读。
在进行实际的分析以前,咱们先来看下面这张图:html
咱们来对上图作出简单解释:DecorView是一个应用窗口的根容器,它本质上是一个FrameLayout。DecorView有惟一一个子View,它是一个垂直LinearLayout,包含两个子元素,一个是TitleView(ActionBar的容器),另外一个是ContentView(窗口内容的容器)。关于ContentView,它是一个FrameLayout(android.R.id.content),咱们日常用的setContentView就是设置它的子View。上图还表达了每一个Activity都与一个Window(具体来讲是PhoneWindow)相关联,用户界面则由Window所承载。java
Window即窗口,这个概念在Android Framework中的实现为android.view.Window这个抽象类,这个抽象类是对Android系统中的窗口的抽象。在介绍这个类以前,咱们先来看看究竟什么是窗口呢?node
实际上,窗口是一个宏观的思想,它是屏幕上用于绘制各类UI元素及响应用户输入事件的一个矩形区域。一般具有如下两个特色:android
在Android系统中,窗口是独占一个Surface实例的显示区域,每一个窗口的Surface由WindowManagerService分配。咱们能够把Surface看做一块画布,应用能够经过Canvas或OpenGL在其上面做画。画好以后,经过SurfaceFlinger将多块Surface按照特定的顺序(即Z-order)进行混合,然后输出到FrameBuffer中,这样用户界面就得以显示。canvas
android.view.Window这个抽象类能够看作Android中对窗口这一宏观概念所作的约定,而PhoneWindow这个类是Framework为咱们提供的Android窗口概念的具体实现。接下来咱们先来介绍一下android.view.Window这个抽象类。缓存
这个抽象类包含了三个核心组件:app
下面咱们来看一下Android中Window的具体实现(也是惟一实现)——PhoneWindow。ide
前面咱们提到了,PhoneWindow这个类是Framework为咱们提供的Android窗口的具体实现。咱们平时调用setContentView()方法设置Activity的用户界面时,实际上就完成了对所关联的PhoneWindow的ViewTree的设置。咱们还能够经过Activity类的requestWindowFeature()方法来定制Activity关联PhoneWindow的外观,这个方法实际上作的是把咱们所请求的窗口外观特性存储到了PhoneWindow的mFeatures成员中,在窗口绘制阶段生成外观模板时,会根据mFeatures的值绘制特定外观。布局
在分析setContentView()方法前,咱们须要明确:这个方法只是完成了Activity的ContentView的建立,而并无执行View的绘制流程。
当咱们自定义Activity继承自android.app.Activity时候,调用的setContentView()方法是Activity类的,源码以下:this
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); . . . }
getWindow()方法会返回Activity所关联的PhoneWindow,也就是说,实际上调用到了PhoneWindow的setContentView()方法,源码以下:
@Override public void setContentView(int layoutResID) { if (mContentParent == null) { // mContentParent即为上面提到的ContentView的父容器,若为空则调用installDecor()生成 installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { // 具备FEATURE_CONTENT_TRANSITIONS特性表示开启了Transition // mContentParent不为null,则移除decorView的全部子View mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { // 开启了Transition,作相应的处理,咱们不讨论这种状况 // 感兴趣的同窗能够参考源码 . . . } else { // 通常状况会来到这里,调用mLayoutInflater.inflate()方法来填充布局 // 填充布局也就是把咱们设置的ContentView加入到mContentParent中 mLayoutInflater.inflate(layoutResID, mContentParent); } . . . // cb即为该Window所关联的Activity final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { // 调用onContentChanged()回调方法通知Activity窗口内容发生了改变 cb.onContentChanged(); } . . . }
在上面咱们看到了,PhoneWindow的setContentView()方法中调用了LayoutInflater的inflate()方法来填充布局,这个方法的源码以下:
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); } public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) { final Resources res = getContext().getResources(); . . . final XmlResourceParser parser = res.getLayout(resource); try { return inflate(parser, root, attachToRoot); } finally { parser.close(); } }
在PhoneWindow的setContentView()方法中传入了decorView做为LayoutInflater.inflate()的root参数,咱们能够看到,经过层层调用,最终调用的是inflate(XmlPullParser, ViewGroup, boolean)方法来填充布局。这个方法的源码以下:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) { synchronized (mConstructorArgs) { . . . final Context inflaterContext = mContext; final AttributeSet attrs = Xml.asAttributeSet(parser); Context lastContext = (Context) mConstructorArgs[0]; mConstructorArgs[0] = inflaterContext; View result = root; try { // Look for the root node. int type; // 一直读取xml文件,直到遇到开始标记 while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } // 最早遇到的不是开始标记,报错 if (type != XmlPullParser.START_TAG) { throw new InflateException(parser.getPositionDescription() + ": No start tag found!"); } final String name = parser.getName(); . . . // 单独处理<merge>标签,不熟悉的同窗请参考官方文档的说明 if (TAG_MERGE.equals(name)) { // 若包含<merge>标签,父容器(即root参数)不可为空且attachRoot须为true,不然报错 if (root == null || !attachToRoot) { throw new InflateException("<merge /> can be used only with a valid " + "ViewGroup root and attachToRoot=true"); } // 递归地填充布局 rInflate(parser, root, inflaterContext, attrs, false); } else { // temp为xml布局文件的根View final View temp = createViewFromTag(root, name, inflaterContext, attrs); ViewGroup.LayoutParams params = null; if (root != null) { . . . // 获取父容器的布局参数(LayoutParams) params = root.generateLayoutParams(attrs); if (!attachToRoot) { // 若attachToRoot参数为false,则咱们只会将父容器的布局参数设置给根View temp.setLayoutParams(params); } } // 递归加载根View的全部子View rInflateChildren(parser, temp, attrs, true); . . . if (root != null && attachToRoot) { // 若父容器不为空且attachToRoot为true,则将父容器做为根View的父View包裹上来 root.addView(temp, params); } // 若root为空或是attachToRoot为false,则以根View做为返回值 if (root == null || !attachToRoot) { result = temp; } } } catch (XmlPullParserException e) { . . . } catch (Exception e) { . . . } finally { . . . } return result; } }
在上面的源码中,首先对于布局文件中的<merge>标签进行单独处理,调用rInflate()方法来递归填充布局。这个方法的源码以下:
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { // 获取当前标记的深度,根标记的深度为0 final int depth = parser.getDepth(); int type; while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { // 不是开始标记则继续下一次迭代 if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); // 对一些特殊标记作单独处理 if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { if (parser.getDepth() == 0) { throw new InflateException("<include /> cannot be the root element"); } // 对<include>作处理 parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { throw new InflateException("<merge /> must be the root element"); } else { // 对通常标记的处理 final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params=viewGroup.generateLayoutParams(attrs); // 递归地加载子View rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); } } if (finishInflate) { parent.onFinishInflate(); } }
咱们能够看到,上面的inflate()和rInflate()方法中都调用了rInflateChildren()方法,这个方法的源码以下:
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { rInflate(parser, parent, parent.getContext(), attrs, finishInflate); }
从源码中咱们能够知道,rInflateChildren()方法实际上调用了rInflate()方法。
到这里,setContentView()的总体执行流程咱们就分析完了,至此咱们已经完成了Activity的ContentView的建立与设置工做。接下来,咱们开始进入正题,分析View的绘制流程。
在介绍View的绘制前,首先咱们须要知道是谁负责执行View绘制的整个流程。实际上,View的绘制是由ViewRoot来负责的。每一个应用程序窗口的decorView都有一个与之关联的ViewRoot对象,这种关联关系是由WindowManager来维护的。
那么decorView与ViewRoot的关联关系是在何时创建的呢?答案是Activity启动时,ActivityThread.handleResumeActivity()方法中创建了它们二者的关联关系。这里咱们不具体分析它们创建关联的时机与方式,感兴趣的同窗能够参考相关源码。下面咱们直入主题,分析一下ViewRoot是如何完成View的绘制的。
当创建好了decorView与ViewRoot的关联后,ViewRoot类的requestLayout()方法会被调用,以完成应用程序用户界面的初次布局。实际被调用的是ViewRootImpl类的requestLayout()方法,这个方法的源码以下:
@Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { // 检查发起布局请求的线程是否为主线程 checkThread(); mLayoutRequested = true; scheduleTraversals(); } }
上面的方法中调用了scheduleTraversals()方法来调度一次完成的绘制流程,该方法会向主线程发送一个“遍历”消息,最终会致使ViewRootImpl的performTraversals()方法被调用。下面,咱们以performTraversals()为起点,来分析View的整个绘制流程。
View的整个绘制流程能够分为如下三个阶段:
此阶段的目的是计算出控件树中的各个控件要显示其内容的话,须要多大尺寸。起点是ViewRootImpl的measureHierarchy()方法,这个方法的源码以下:
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp, final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) { // 传入的desiredWindowXxx为窗口尺寸 int childWidthMeasureSpec; int childHeightMeasureSpec; boolean windowSizeMayChange = false; . . . boolean goodMeasure = false; if (!goodMeasure) { childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) { windowSizeMayChange = true; } } return windowSizeMayChange; }
上面的代码中调用getRootMeasureSpec()方法来获取根MeasureSpec,这个根MeasureSpec表明了对decorView的宽高的约束信息。继续分析以前,咱们先来简单地介绍下MeasureSpec的概念。
MeasureSpec是一个32位整数,由SpecMode和SpecSize两部分组成,其中,高2位为SpecMode,低30位为SpecSize。SpecMode为测量模式,SpecSize为相应测量模式下的测量尺寸。View(包括普通View和ViewGroup)的SpecMode由本View的LayoutParams结合父View的MeasureSpec生成。
SpecMode的取值可为如下三种:
传入performMeasure()方法的MeasureSpec的SpecMode为EXACTLY,SpecSize为窗口尺寸。
performMeasure()方法的源码以下:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { . . . try { mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); } finally { . . . } }
上面代码中的mView即为decorView,也就是说会转向对View.measure()方法的调用,这个方法的源码以下:
/** * 调用这个方法来算出一个View应该为多大。参数为父View对其宽高的约束信息。 * 实际的测量工做在onMeasure()方法中进行 */ public final void measure(int widthMeasureSpec, int heightMeasureSpec) { . . . // 判断是否须要从新布局 // 若mPrivateFlags中包含PFLAG_FORCE_LAYOUT标记,则强制从新布局 // 好比调用View.requestLayout()会在mPrivateFlags中加入此标记 final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec; final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY; final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec); final boolean needsLayout = specChanged && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize); // 须要从新布局 if (forceLayout || needsLayout) { . . . // 先尝试从缓从中获取,若forceLayout为true或是缓存中不存在或是 // 忽略缓存,则调用onMeasure()从新进行测量工做 int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); . . . } else { // 缓存命中,直接从缓存中取值便可,没必要再测量 long value = mMeasureCache.valueAt(cacheIndex); // Casting a long to int drops the high 32 bits, no mask needed setMeasuredDimensionRaw((int) (value >> 32), (int) value); . . . } . . . } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension }
从measure()方法的源码中咱们能够知道,只有如下两种状况之一,才会进行实际的测量工做:
对于decorView来讲,实际执行测量工做的是FrameLayout的onMeasure()方法,该方法的源码以下:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); . . . int maxHeight = 0; int maxWidth = 0; int childState = 0; for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (mMeasureAllChildren || child.getVisibility() != GONE) { measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); final LayoutParams lp = (LayoutParams) child.getLayoutParams(); maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); . . . } } // Account for padding too maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground(); maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground(); // Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Check against our foreground's minimum height and width final Drawable drawable = getForeground(); if (drawable != null) { maxHeight = Math.max(maxHeight, drawable.getMinimumHeight()); maxWidth = Math.max(maxWidth, drawable.getMinimumWidth()); } setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); . . . }
FrameLayout是ViewGroup的子类,后者有一个View[]类型的成员变量mChildren,表明了其子View集合。经过getChildAt(i)能获取指定索引处的子View,经过getChildCount()能够得到子View的总数。
在上面的源码中,首先调用measureChildWithMargins()方法对全部子View进行了一遍测量,并计算出全部子View的最大宽度和最大高度。然后将获得的最大高度和宽度加上padding,这里的padding包括了父View的padding和前景区域的padding。而后会检查是否设置了最小宽高,并与其比较,将二者中较大的设为最终的最大宽高。最后,若设置了前景图像,咱们还要检查前景图像的最小宽高。
通过了以上一系列步骤后,咱们就获得了maxHeight和maxWidth的最终值,表示当前容器View用这个尺寸就可以正常显示其全部子View(同时考虑了padding和margin)。然后咱们须要调用resolveSizeAndState()方法来结合传来的MeasureSpec来获取最终的测量宽高,并保存到mMeasuredWidth与mMeasuredHeight成员变量中。
从以上代码的执行流程中,咱们能够看到,容器View经过measureChildWithMargins()方法对全部子View进行测量后,才能获得自身的测量结果。也就是说,对于ViewGroup及其子类来讲,要先完成子View的测量,再进行自身的测量(考虑进padding等)。
接下来咱们来看下ViewGroup的measureChildWithMargins()方法的实现:
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) { final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 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); }
由以上代码咱们能够知道,对于ViewGroup来讲,它会调用child.measure()来完成子View的测量。传入ViewGroup的MeasureSpec是它的父View用于约束其测量的,那么ViewGroup自己也须要生成一个childMeasureSpec来限制它的子View的测量工做。这个childMeasureSpec就由getChildMeasureSpec()方法生成。接下来咱们来分析这个方法:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { // spec为父View的MeasureSpec // padding为父View在相应方向的已用尺寸加上父View的padding和子View的margin // childDimension为子View的LayoutParams的值 int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); // 如今size的值为父View相应方向上的可用大小 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: if (childDimension >= 0) { // 表示子View的LayoutParams指定了具体大小值(xx dp) resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // 子View想和父View同样大 resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // 子View想本身决定其尺寸,但不能比父View大 resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // 子View指定了具体大小 resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // 子View想跟父View同样大,可是父View的大小未固定下来 // 因此指定约束子View不能比父View大 resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // 子View想要本身决定尺寸,但不能比父View大 resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; . . . } //noinspection ResourceType return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
上面的方法展示了根据父View的MeasureSpec和子View的LayoutParams生成子View的MeasureSpec的过程, 子View的LayoutParams表示了子View的期待大小。这个产生的MeasureSpec用于指导子View自身的测量结果的肯定。
在上面的代码中,咱们能够看到当ParentMeasureSpec的SpecMode为EXACTLY时,表示父View对子View指定了确切的宽高限制。此时根据子View的LayoutParams的不一样,分如下三种状况:
当ParentMeasureSpec的SpecMode为AT_MOST时,咱们也能够根据子View的LayoutParams的不一样来分三种状况讨论:
在measureChildWithMargins()方法中,获取了知道子View测量的MeasureSpec后,接下来就要调用child.measure()方法,并把获取到的childMeasureSpec传入。这时便又会调用onMeasure()方法,若此时的子View为ViewGroup的子类,便会调用相应容器类的onMeasure()方法,其余容器View的onMeasure()方法与FrameLayout的onMeasure()方法执行过程类似。
下面会咱们回到FrameLayout的onMeasure()方法,当递归地执行完全部子View的测量工做后,会调用resolveSizeAndState()方法来根据以前的测量结果肯定最终对FrameLayout的测量结果并存储起来。View类的resolveSizeAndState()方法的源码以下:
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) { final int specMode = MeasureSpec.getMode(measureSpec); final int specSize = MeasureSpec.getSize(measureSpec); final int result; switch (specMode) { case MeasureSpec.AT_MOST: if (specSize < size) { // 父View给定的最大尺寸小于彻底显示内容所需尺寸 // 则在测量结果上加上MEASURED_STATE_TOO_SMALL result = specSize | MEASURED_STATE_TOO_SMALL; } else { result = size; } break; case MeasureSpec.EXACTLY: // 若specMode为EXACTLY,则不考虑size,result直接赋值为specSize result = specSize; break; case MeasureSpec.UNSPECIFIED: default: result = size; } return result | (childMeasuredState & MEASURED_STATE_MASK); }
对于普通View,会调用View类的onMeasure()方法来进行实际的测量工做,该方法的源码以下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
对于普通View(非ViewgGroup)来讲,只需完成自身的测量工做便可。以上代码中经过setMeasuredDimension()方法设置测量的结果,具体来讲是以getDefaultSize()方法的返回值来做为测量结果。getDefaultSize()方法的源码以下:
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; }
由以上代码咱们能够看到,View的getDefaultSize()方法对于AT_MOST和EXACTLY这两种状况都返回了SpecSize做为result。因此若咱们的自定义View直接继承了View类,咱们就要本身对wrap_content (对应了AT_MOST)这种状况进行处理,不然对自定义View指定wrap_content就和match_parent效果同样了。
layout阶段的基本思想也是由根View开始,递归地完成整个控件树的布局(layout)工做。
咱们把对decorView的layout()方法的调用做为布局整个控件树的起点,实际上调用的是View类的layout()方法,源码以下:
public void layout(int l, int t, int r, int b) { // l为本View左边缘与父View左边缘的距离 // t为本View上边缘与父View上边缘的距离 // r为本View右边缘与父View左边缘的距离 // b为本View下边缘与父View上边缘的距离 . . . boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); . . . } . . . }
这个方法会调用setFrame()方法来设置View的mLeft、mTop、mRight和mBottom四个参数,这四个参数描述了View相对其父View的位置(分别赋值为l, t, r, b),在setFrame()方法中会判断View的位置是否发生了改变,若发生了改变,则须要对子View进行从新布局,对子View的局部是经过onLayout()方法实现了。因为普通View( 非ViewGroup)不含子View,因此View类的onLayout()方法为空。所以接下来,咱们看看ViewGroup类的onLayout()方法的实现。
实际上ViewGroup类的onLayout()方法是abstract,这是由于不一样的布局管理器有着不一样的布局方式。
这里咱们以decorView,也就是FrameLayout的onLayout()方法为例,分析ViewGroup的布局过程:
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { layoutChildren(left, top, right, bottom, false /* no force left gravity */); } void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) { final int count = getChildCount(); final int parentLeft = getPaddingLeftWithForeground(); final int parentRight = right - left - getPaddingRightWithForeground(); final int parentTop = getPaddingTopWithForeground(); final int parentBottom = bottom - top - getPaddingBottomWithForeground(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); int childLeft; int childTop; int gravity = lp.gravity; if (gravity == -1) { gravity = DEFAULT_CHILD_GRAVITY; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK; switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = parentLeft + (parentRight - parentLeft - width) / 2 + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: if (!forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin; break; } case Gravity.LEFT: default: childLeft = parentLeft + lp.leftMargin; } switch (verticalGravity) { case Gravity.TOP: childTop = parentTop + lp.topMargin; break; case Gravity.CENTER_VERTICAL: childTop = parentTop + (parentBottom - parentTop - height) / 2 + lp.topMargin - lp.bottomMargin; break; case Gravity.BOTTOM: childTop = parentBottom - height - lp.bottomMargin; break; default: childTop = parentTop + lp.topMargin; } child.layout(childLeft, childTop, childLeft + width, childTop + height); } } }
在上面的方法中,parentLeft表示当前View为其子View显示区域指定的一个左边界,也就是子View显示区域的左边缘到父View的左边缘的距离,parentRight、parentTop、parentBottom的含义同理。肯定了子View的显示区域后,接下来,用一个for循环来完成子View的布局。
在确保子View的可见性不为GONE的状况下才会对其进行布局。首先会获取子View的LayoutParams、layoutDirection等一系列参数。上面代码中的childLeft表明了最终子View的左边缘距父View左边缘的距离,childTop表明了子View的上边缘距父View的上边缘的距离。会根据子View的layout_gravity的取值对childLeft和childTop作出不一样的调整。最后会调用child.layout()方法对子View的位置参数进行设置,这时便转到了View.layout()方法的调用,若子View是容器View,则会递归地对其子View进行布局。
到这里,layout阶段的大体流程咱们就分析完了,这个阶段主要就是根据上一阶段获得的View的测量宽高来肯定View的最终显示位置。显然,通过了measure阶段和layout阶段,咱们已经肯定好了View的大小和位置,那么接下来就能够开始绘制View了。
对于本阶段的分析,咱们以decorView.draw()做为分析的起点,也就是View.draw()方法,它的源码以下:
public void draw(Canvas canvas) { . . . // 绘制背景,只有dirtyOpaque为false时才进行绘制,下同 int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } . . . // 绘制自身内容 if (!dirtyOpaque) onDraw(canvas); // 绘制子View dispatchDraw(canvas); . . . // 绘制滚动条等 onDrawForeground(canvas); }
简单起见,在上面的代码中咱们省略了实现滑动时渐变边框效果相关的逻辑。实际上,View类的onDraw()方法为空,由于每一个View绘制自身的方式都不尽相同,对于decorView来讲,因为它是容器View,因此它自己并无什么要绘制的。dispatchDraw()方法用于绘制子View,显然普通View(非ViewGroup)并不能包含子View,因此View类中这个方法的实现为空。
ViewGroup类的dispatchDraw()方法中会依次调用drawChild()方法来绘制子View,drawChild()方法的源码以下:
protected boolean drawChild(Canvas canvas, View child, long drawingTime) { return child.draw(canvas, this, drawingTime); }
这个方法调用了View.draw(Canvas, ViewGroup,long)方法来对子View进行绘制。在draw(Canvas, ViewGroup, long)方法中,首先对canvas进行了一系列变换,以变换到将要被绘制的View的坐标系下。完成对canvas的变换后,便会调用View.draw(Canvas)方法进行实际的绘制工做,此时传入的canvas为通过变换的,在将被绘制View的坐标系下的canvas。
进入到View.draw(Canvas)方法后,会向以前介绍的同样,执行如下几步:
至此,整个View的绘制流程咱们就分析完了。若文中有叙述不清晰或是不许确的地方,但愿你们可以指出,谢谢你们:)
这篇讲解View的绘制比较全面,解决了不少疑惑,若是想仔细研究View的measure()、layout()、draw(),能够看这篇:http://www.cnblogs.com/jycboy/p/6066654.html。这两篇看完就会对view的绘制流程很清楚了。
《深刻理解Android(卷三)》
《Android开发艺术探索》
公共技术点之View的绘制流程