上一偏文章主要了解了一下ViewRoot和DecorView 以及MeasureSpec ,这篇文章主要讲解View的三大流程;
measure过程
measure过程要分两种情况,一种是普通view,一种是viewgroup:
- 1.view的measure
view的measure方法是一个final类型的方法,意味着不能重写这个方法,在这个方法中调用了onMeasure方法,我们可以重写onMeasure方法,
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
...
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;
}
...
}
看看onMeasure方法的实现:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
调用setMeasuredDimension方法设置view的测量值,其中getSuggestedMinimumWidth()是系统默认提供的值,这里就不深究了,下面看看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;
}
在UNSPECIFIED模式下,返回系统默认值,EXACTLY和AT_MOST模式下,返回measureSpec中的值;这里也可以得到一个结论:当我们直接继承一个view的自定义控件需要重写onMeasure方法去设置wrap_content时的自身大小,否则在布局中使用wrap_content的效果与match_parent一样,原因是当view设置为wrap_content时,模式是AT_MOST,通过前一篇文章中的getChildMeasureSpec方法可以知道,view的specSize就是parentSize;重写onMeasure的代码思路如下:
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(viewWidth, viewHeight);
}
else if(widthMode == MeasureSpec.AT_MOST ){
setMeasuredDimension(viewWidth, heightSize);
}
else if(heightMode == MeasureSpec.AT_MOST ){
setMeasuredDimension(widthSize, viewHeight);
}
- 2.viewgroup的measure
对于viewgroup来说,出了完成自己的measure过程,还要遍历子view的measure方法,各个子view完成对自身的measure过程,与view不同的是,viewgroup是一个抽象类,它是通过measureChildren()方法开始measure,先看看measureChildren方法的代码:
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方法对每一个子view进行measure,
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);
}
measureChild方法先得到子view的MeasureSpec,然后再调用子view的measure方法;
view的measure过程是三大流程中最复杂的一个,measure完成以后,通过getMeasuredHeight()和getMeasuredWeight()可以得到view的测量高宽,但是在某些极端情况下,系统可能会多次measure才能得到最终的值,在这种情况下,在onMeasure中可能得到的值不是准确的。我们最好在onLayout中获取;还有一个需要注意的是,我们在onCreate、onStart和onResume中均得不到view的宽高,因为measure过程与activity的生命周期不是同步的;
layout过程
layout过程的作用是viewgroup来确定子元素的位置,当viewgroup的位置确定后,会遍历所有子元素并调用其layout方法,在layout方法中又会调用onLayout方法。
先看viewgroup的layout方法,
@Override
public final void layout(int l, int t, int r, int b) {
if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
if (mTransition != null) {
mTransition.layoutChange(this);
}
super.layout(l, t, r, b);
} else {
// record the fact that we noop'd it; request layout when transition finishes
mLayoutCalledWhileSuppressed = true;
}
}
可以看出是调用父类的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);
if (shouldDrawRoundScrollbar()) {
if(mRoundScrollbarRenderer == null) {
mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
}
} else {
mRoundScrollbarRenderer = null;
}
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;
}
首先通过setFrame确定view的四个顶点位置,此时父容器的位置也就确定了,接下来就会调用onLayout方法确定子元素的位置;
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
onLayout方法并没有给出具体的实现,当继承特定的组件时,不需要我们重写onLayout方法,继承viewgroup时则需要重写onLayout方法;
draw过程
draw的作用是将view绘制到屏幕上,view的绘制流程遵循以下几步:
- 绘制背景drawBackground(canvas);
- 绘制自己onDraw(canvas);
- 绘制子元素dispatchDraw(canvas);
- 绘制装饰onDrawForeground(canvas);
看看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;
}
...
}
view绘制过程的传递是通过 dispatchDraw(canvas)来实现的, dispatchDraw(canvas);会遍历所有的子元素的draw方法,view有一个特殊的方法:setWillNotDraw,先看一下源码;
/**
* If this view doesn't do any drawing on its own, set this flag to
* allow further optimizations. By default, this flag is not set on
* View, but could be set on some View subclasses such as ViewGroup.
*
* Typically, if you override {@link #onDraw(android.graphics.Canvas)}
* you should clear this flag.
*
* @param willNotDraw whether or not this View draw on its own
*/
public void setWillNotDraw(boolean willNotDraw) {
setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
}
意思是如果一个view不需要绘制任何内容,那么设置这个标记位为TRUE后,系统会进行优化,默认情况下标记位为FALSE。
以上就是view的三大流程。