源码分析Android中View的绘制流程
来源:互联网 发布:ae cc 2014 for mac 编辑:程序博客网 时间:2024/05/22 16:41
在开发中自定义控件的使用是比较频繁的,而自定义控件的基础之一就是View的测量以及绘制。这篇文章从源码的角度简要分析一下View的测量绘制。
在了解View绘制流程之前,必须先要了解一个类,MeasureSpec,它是View的内部类,专门来进行对测量的数据和类型进行打包和解包,看一下源码就会清楚不少:
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; public static final int UNSPECIFIED = 0 << MODE_SHIFT; public static final int EXACTLY = 1 << MODE_SHIFT; public static final int AT_MOST = 2 << MODE_SHIFT; public static int makeMeasureSpec(int size, int mode) { return size + mode; } public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } public static String toString(int measureSpec) { int mode = getMode(measureSpec); int size = getSize(measureSpec); StringBuilder sb = new StringBuilder("MeasureSpec: "); if (mode == UNSPECIFIED) sb.append("UNSPECIFIED "); else if (mode == EXACTLY) sb.append("EXACTLY "); else if (mode == AT_MOST) sb.append("AT_MOST "); else sb.append(mode).append(" "); sb.append(size); return sb.toString(); } }
类还是比较简单,使用getMode和getSize方法对一个32位的int值进行解包,解释出前2位和后30位分别作为测量模式和大小的标注。使用makeMeasureSpec来进行打包。这里暂时只需要知道可以用一个int值来存储这两种类型的数据就可以了,后面还会接触到。
ViewGroup中存放了许多的View,那么对View的测量一定会经过ViewGroup,在ViewGroup中的measureChildren方法完成了对子View的测量,来看一下:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
在方法中只是遍历了一下然后调用了measureChild方法,进入看一下:
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
可以看到方法中生成了子View的MeasureSpec,然后简单的调用了子View的measure方法。来看一下getChildMeasureSpec方法:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); 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) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it。 resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
先解释一下参数,spec是当前ViewGroup的MeasureSpec,padding是当前ViewGroup的padding,childDimension是子View的大小(可以为MATCH_PARENT和WRAP_CONTENT)。
当ViewGroup的MeasureSpec是EXACTLY时:
①子View的大小是大于等于0的,那么给子View设置的大小就是childDimension,MeasureSpec为EXACTLY。
②子View的大小是MATCH_PARENT,给子View设置的大小是当前ViewGroup大小-padding大小,MeasureSpec为EXACTLY。
③子View的大小是WRAP_CONTENT,给子View设置的大小是当前ViewGroup的大小-padding大小,MeasureSpec为AT_MOST。
当ViewGroup的MeasureSpec是AT_MOST时:
①子View的大小是大于等于0的,那么给子View设置的大小就是childDimension,MeasureSpec为EXACTLY。
②子View的大小是MATCH_PARENT,给子View设置的大小是当前ViewGroup大小-padding大小,MeasureSpec为AT_MOST。
③子View的大小是WRAP_CONTENT,给子View设置的大小是当前ViewGroup的大小-padding大小,MeasureSpec为AT_MOST。
剩下的一种情况是ViewGroup的MeasureSpec为UNSPECIFIED,这种情况基本不会出现,在这里就不深入考虑了。了解了这些后ViewGroup对子View的测量就完成了,可以看到,子View宽高的测量值是由父ViewGroup和其自身的LayoutParams共同决定的。看完了ViewGroup对子View的分发测量再来看一下View的measure方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec) { // first clears the measured dimension flag mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) { throw new IllegalStateException("onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; }
在这里参数就是ViewGroup中创建的,因为这个方法是final的,我们并不能修改,所以我们只看一下能修改的onMeasure方法:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
在方法中直接调用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中的LayoutParams设置的大小为什么,它的MeasureSpec都会是AT_MOST和EXACTLY中的一个,那么它最后的测量大小必定等于specSize。并且还由刚才的分析可以知道,当我们把子View指定为WRAP_CONTENT时它的默认大小就是覆盖了整个ViewGroup。大家可以简单的做个实验,在这里就不贴实验的代码了。
如果想要让WRAP_CONTENT起作用,我们需要在onMeasure中加入逻辑的判断以确定其大小。
在onMeasure方法结束后,调用了setMeasuredDimension方法为mMeasuredWidth和mMeasuredHeight赋值:
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { mMeasuredWidth = measuredWidth; mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
我们可以调用getMeasuredWidth对这个测量结果进行获取(但是必须在onMeasure调用结束后),getMeasuredHeight同理。
既然已经测量过了,现在就来看一下layout方法:
ViewGroup的layout方法也是简单的调用了父类View的layout方法,所以这里只看一下View的方法。
public void layout(int l, int t, int r, int b) { int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; boolean changed = setFrame(l, t, r, b); if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED; ListenerInfo li = mListenerInfo; if (li != null && li.mOnLayoutChangeListeners != null) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); int numListeners = listenersCopy.size(); for (int i = 0; i < numListeners; ++i) { listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); } } } mPrivateFlags &= ~PFLAG_FORCE_LAYOUT; }
在layout中首先判断了一下View的大小位置是否发生了改变,并且在setFram方法时将传入的参数赋给本地变量进行保存,而后调用onLayout方法去处理子View,因为onLayout方法和具体布局相关,所以在View和ViewGroup中都没有对其给出实现。
layout基本看完了,下面就是和绘画相关的draw。
public void draw(Canvas canvas) { final int privateFlags = mPrivateFlags; final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 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) */ // Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { final Drawable background = mBackground; if (background != null) { final int scrollX = mScrollX; final int scrollY = mScrollY; if (mBackgroundSizeChanged) { background.setBounds(0, 0, mRight - mLeft, mBottom - mTop); mBackgroundSizeChanged = false; } if ((scrollX | scrollY) == 0) { background.draw(canvas); } else { canvas.translate(scrollX, scrollY); background.draw(canvas); canvas.translate(-scrollX, -scrollY); } } } // skip step 2 & 5 if possible (common case) final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; if (!verticalEdges && !horizontalEdges) { // Step 3, draw the content if (!dirtyOpaque) onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); // we're done... return; }}
注释写的也比较清楚,绘制背景->绘制自己->绘制子View->绘制ScrollBar,值得一提的是在View中dispatchDraw方法是空的,在ViewGroup中给出了实现。
- 源码分析Android中View的绘制流程
- Android中View绘制流程分析
- Android 中View的绘制机制源码分析 三
- Android 中View的绘制机制源码分析 一
- Android 中View的绘制机制源码分析 二
- Android 中View的绘制机制源码分析 四
- Android 中View的绘制机制源码分析
- Android应用层View绘制流程与源码分析
- Android应用层View绘制流程与源码分析
- Android应用层View绘制流程与源码分析
- Android应用层View绘制流程与源码分析
- Android应用层View绘制流程与源码分析
- Android应用层View绘制流程与源码分析
- Android应用层View绘制流程与源码分析
- Android应用层View绘制流程与源码分析
- Android应用层View绘制流程与源码分析
- Android应用层View绘制流程与源码分析
- Android应用层View绘制流程与源码分析
- Android 坑
- Web服务器工作原理概述
- webview显示https协议内容
- 01Redis入门指南笔记(简介、安装、配置)
- Android Jni开发环境搭建完整版
- 源码分析Android中View的绘制流程
- 堆和栈经典
- 二维码封装库
- 一步一步熟知设计模式--初识
- UIMenuController的使用,对UILabel拷贝以及定制菜单
- 同步技术之Monitor
- Ubuntu 14.04 LTS Server安装配置Bugzilla
- 20151130如何更好地限制一个UITextField的输入长度
- Object类学习笔记