View的绘制流程是从ViewRoot的performTraversals方法开始的,它经过measure、layout和draw三个过程才能最终将一个View绘制出来,其中measure用来测量View的宽和高,layout用来确定View在父容器中的放置位置,而draw则负责将View绘制在屏幕上。
在测量过程中,系统会将View的LayoutParams根据父容器所施加的规则转换成对应的MeasureSpec,然后再根据这个MeasureSpec来测量出View的宽和高。测量出来的宽和高不一定等于View最终的宽和高。
MeasureSpec将SpecMode和SpecSize打包成一个int值来避免过多的对象内存分配,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。
SpecMode有三类:
对于普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams来共同决定。
ViewGroup的measure方法会遍历每个子元素,并调用子元素内部的measure方法,measure源码如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
注:
在Activity的onCreate、onStart、onResume方法中均无法正确得到某个View的宽/高信息,这是因为View的measure过程和Activity的生命周期方法不是同步执行的,因此无法保证Activity执行了onCreate、onStart、onResume时某个View就已经测量完毕了,如果View还没有测量完毕,那么获得的宽/高就是0。
可以通过如下四个方法来解决这个问题:
示例代码请参考原书P190页
layout的作用是用来确定子视图在父视图中的位置。源码如下:
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 & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
}
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
if (mOnLayoutChangeListeners != null) {
ArrayList listenersCopy =
(ArrayList) 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 &= ~FORCE_LAYOUT;
}
通过setFrame()确定四个顶点的位置,进而确定View在父容器中的位置。
在View的默认实现中,View的测量宽/高和最终宽/高是相等的,只不过测量宽/高形成于View的measure过程,而最终宽/高形成于View的layout过程,即两者的赋值时机不同,测量宽/高的赋值时机稍微早一些。多数情况下可以认为View的测量宽/高就等于最终的宽/高,但对于在View的layout中改变了View的left、top、right、bottom四个属性时,得出的测量宽/高有可能和最终的宽/高不一致。
draw的过程很简单主要有以下几步:
源码如下
public void draw(Canvas canvas) {
/ * Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background if need
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children (dispatchDraw)
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
*/
//Step 1, draw the background, if needed
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
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);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// we're done...
return;
}
// Step 2, save the canvas' layers
...
// Step 3, draw the content
if (!dirtyOpaque)
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
// Step 6, draw decorations (scrollbars)
onDrawScrollBars(canvas);
}
注:
欢迎转载,转载请注明本文链接http://blog.csdn.net/l664675249/article/details/50774617