赞
踩
相信大家都知道我们所看到的控件都是直接或者间接的继承View类,下面我们看一下它是如何绘制在我们视野里的。
要知道,每一个视图的绘制过程都必须经历三个最主要的阶段,即onMeasure()、onLayout()和onDraw(),下面我们逐个对这三个阶段展开进行探讨。
首先,我们要知道 遍历View树最终的入口函数为调用ViewRootImpl的performTraversals函数;所以我们来分析ViewRootImple中的源码(在编译器里面我们看不到这个类,要去下载的sdk里面的source里面找),观察performTraversals()方法(这个方法801行,我就不列出来了吧)可以发现如下代码:
private void performTraversals() {
// 记录ViewRootImpl管理的View树的根节点,final修饰,避免运行过程中修改
// cache mView since it is used so much below...
final View host = mView;
/** View树遍历的主要三个步骤measure、layout、draw **/
......
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
......
performDraw();
}
一. onMeasure()
onMeasure()方法顾名思义就是用于测量视图的大小的。
源码如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
就一句话,设置默认的高度和宽度,可是我们更想知道widthMeasureSpec,heightMeasureSpec这两个参数哪里来的?这就需要去分析ViewRootImple中的源码了,再仔细观察performTraversals()方法,可以发现如下代码:
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec=getRootMeasureSpec(mHeight,lp.height);
可以看到,这里调用了getRootMeasureSpec()方法去获取widthMeasureSpec和heightMeasureSpec的值,注意方法中传入的参数,其中lp.width和lp.height在创建ViewGroup实例的时候就被赋值了,它们都等于MATCH_PARENT。然后看下getRootMeasureSpec()方法中的代码,如下所示:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
// Window can't resize. Force root view to be windowSize.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
// Window can resize. Set max size for root view.
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
// Window wants to be an exact size. Force root view to be that size.
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
可以看到,我们使用MeasureSpec.makeMeasureSpec来组装一个MeasureSpec的值,当rootDimension参数等于MATCH_PARENT的时候,MeasureSpec的specMode就等于EXACTLY,当rootDimension等于WRAP_CONTENT的时候,MeasureSpec的specMode就等于AT_MOST。并且MATCH_PARENT和WRAP_CONTENT时的specSize都是等于windowSize的,也就意味着根视图总是会充满全屏的。
百科一下EXACTLY、 AT_MOST、 UNSPECIFIED:
MeasureSpec的值由specSize和specMode共同组成的,其中specSize记录的是大小,specMode记录的是规格。specMode一共有三种类型,如下所示:
1. EXACTLY
表示父视图希望子视图的大小应该是由specSize的值来决定的,系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
2. AT_MOST
表示子视图最多只能是specSize中指定的大小,开发人员应该尽可能小得去设置这个视图,并且保证不会超过specSize。系统默认会按照这个规则来设置子视图的大小,开发人员当然也可以按照自己的意愿设置成任意的大小。
3. UNSPECIFIED
表示开发人员可以将视图按照自己的意愿设置成任意的大小,没有任何限制。这种情况比较少见,不太会用到。
介绍了这么多MeasureSpec相关的内容,接下来我们看下View的measure()方法里面的代码吧,如下所示:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int oWidth = insets.left + insets.right;
int oHeight = insets.top + insets.bottom;
widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth);
heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);
}
// Suppress sign extension for the low bytes
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ||
widthMeasureSpec != mOldWidthMeasureSpec ||
heightMeasureSpec != mOldHeightMeasureSpec) {
// first clears the measured dimension flag
mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
resolveRtlPropertiesIfNeeded();
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 :
mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
// measure ourselves, this should set the measured dimension flag back
onMeasure(widthMeasureSpec, heightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
} 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);
mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
// 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("View with id " + getId() + ": "
+ getClass().getName() + "#onMeasure() did not set the"
+ " measured dimension by calling"
+ " setMeasuredDimension()");
}
mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
(long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
}
注意:此方法是final的,因此子类无法重写,也就是说无法改变View的measure框架,然后我们看到里面调用了onMeasure()方法,这里才是真正去测量并设置View大小的地方,默认会调用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;
}
二、onLayout()
measure过程结束后,视图的大小就已经测量好了,接下来就是确定视图的位置。就是layout的过程,
ViewRoot的performTraversals()方法的layout会在measure结束后继续执行,如下所示:
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
并调用View的layout()方法来执行此过程,
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
我们来看下View里面的layout方法:
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
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);
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;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
这里会根据mPrivateFlags3 和 PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT 的值判断需不需要再执行
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
然后再调用isLayoutModeOptical()方法来判断视图的大小是否发生过变化,以确定有没有必要对当前的视图进行重绘。
接下来才会调用onLayout()方法,我们看一下它的代码:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
没有任何的实现内容,而是让子类去重写,比如说LinearLayout、RelativeLayout等布局,都是重写了这个方法,然后在内部按照各自的规则对子视图进行布局的。
补充一点:我们在自定义布局的时候都重写了这个方法,会用到getWidth()、getHeight();他们和getMeasureWidth、getMeasureHeight的区别是啥?
getMeasureWidth()方法在measure()过程结束后就可以获取到了,而getWidth()方法要在layout()过程结束后才能获取到。
三. onDraw()
measure和layout的过程都结束后,接下来就进入到draw的过程了。在这里才真正地开始对视图进行绘制。
在ViewRootImpl中跟踪到:
private void performDraw() {
......
draw(fullRedrawNeeded);
......
}
继续
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface; //原始图像缓冲区(raw buffer)的一个句柄
........
mAttachInfo.mTreeObserver.dispatchOnDraw();
......
if (!drawSoftware(surface, attachInfo, yoff, scalingRequired, dirty)) {
return;
}
.......
}
这里的drawSoftware方法就是绘画了。(我们有看到优化app的内存的时候有这种做法:将背景图片放在非UI线程绘制,提升APP的效率,解决方案是将背景图片通过SurfaceView来绘制,这里的SurfaceView提供了一个专门用于绘制的surface)
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int yoff,
boolean scalingRequired, Rect dirty) {
.......
canvas = mSurface.lockCanvas(dirty); //在指定区域拿到canvas画笔
.......
mView.draw(canvas); //开始绘制我们的View
.......
surface.unlockCanvasAndPost(canvas); //回收surface
.......
}
然后调用View的draw()方法来执行具体的绘制工作。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) {
drawBackground(canvas);
}
// 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);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// we're done...
return;
}
//此处省略n行代码,
..........
}
很清晰了解到,这一步的作用是对视图的背景进行绘制。主要是drawBackground()这个方法,我们来看一下这个方法:
private void drawBackground(Canvas canvas) {
final Drawable background = mBackground;
if (background == null) {
return;
}
setBackgroundBounds();
// Attempt to use a display list if requested.
if (canvas.isHardwareAccelerated() && mAttachInfo != null
&& mAttachInfo.mHardwareRenderer != null) {
mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
final RenderNode renderNode = mBackgroundRenderNode;
if (renderNode != null && renderNode.isValid()) {
setBackgroundRenderNodeProperties(renderNode);
((DisplayListCanvas) canvas).drawRenderNode(renderNode);
return;
}
}
final int scrollX = mScrollX;
final int scrollY = mScrollY;
if ((scrollX | scrollY) == 0) {
background.draw(canvas);
} else {
canvas.translate(scrollX, scrollY);
background.draw(canvas);
canvas.translate(-scrollX, -scrollY);
}
}
在代码的第2行 获取到当前view的背景,是一个Drawable对象 ,
在代码的第6行 使用setBackgroundBounds()方法设置背景边界,
在代码的第23行 调用刚刚获取的Drawable对象 进行draw(canvas);
第三步:
if (!dirtyOpaque) onDraw(canvas);
一句话 调用onDraw方法绘制view的内容,由于不同的view内容不同,所以需要子类进行重写。
第四步:
dispatchDraw(canvas);
绘制子view,这里仍然需要当前layout的dispatchDraw方法来完成对各子view的绘制。
第六步:
onDrawForeground(canvas);
绘制装饰(前台,滚动条)。
第二步和第五步到底是啥呢,这里解释一下:
第二步是保存画布图层,
第五步是绘制渐变效果和恢复图层。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。