一. layout过程
先来看看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); 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; if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) { mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; notifyEnterOrExitForAutoFillIfNeeded(true); } }
setFrame(l, t, r, b)
:设定View的4个顶点位置.onLayout(changed, l, t, r, b)
:ViewGroup确定其子元素的位置。View和ViewGroup都未实现该方法,普通的View无需实现,而ViewGroup的onLayout实现,则与布局类型有关。示例:线性布局的
onLayout
方法实现://LinearLayout protected void onLayout(boolean changed, int l, int t, int r, int b) { if (mOrientation == VERTICAL) { layoutVertical(l, t, r, b); } else { layoutHorizontal(l, t, r, b); } }
我们选择
VERTICAL
类型继续查看源码:void layoutVertical(int left, int top, int right, int bottom) { ... ... final int count = getVirtualChildCount(); for (int i = 0; i < count; i++) { final View child = getVirtualChildAt(i); if (child == null) { childTop += measureNullChild(i); } else if (child.getVisibility() != GONE) { final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight(); final LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) child.getLayoutParams(); int gravity = lp.gravity; if (gravity < 0) { gravity = minorGravity; } final int layoutDirection = getLayoutDirection(); final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); ... ... if (hasDividerBeforeChildAt(i)) { childTop += mDividerHeight; } childTop += lp.topMargin; setChildFrame(child, childLeft, childTop + getLocationOffset(child), childWidth, childHeight); childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); i += getChildrenSkipCount(child, i); } } }
- 可以看到,遍历子元素并调用
setChildFrame()
方法来指定子元素的位置。 setChildFrame()
将会调用子元素的layout方法:private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); }
而子元素的layout又会调用其onLayout,从而达到递归遍历整个View树的过程。
setChildFrame()
的参数:final int childWidth = child.getMeasuredWidth(); final int childHeight = child.getMeasuredHeight();
显然就是子View的测量宽高。
childTop
会随着子元素的遍历而主键增大,显然,后添加子View将会出现在更靠下的位置,这也是竖直方向’LinearLayout’的特性。
- 可以看到,遍历子元素并调用
再论View的
measureWidth
/measureHeight
与width
/height
: 基本无差首先看看
getWidth
和getHieght
:public final int getWidth() { return mRight - mLeft; } public final int getHeight() { return mBottom - mTop; }
显然,
getWidth
和getHieght
必须等到layout完成之后,才能获取到正确的值。- 前文总结过,测量宽高形成于View的measure过程,而最终宽高形成于layout过程。所以日常开发中,可以认为它们没有差别。
凡是总有例外,比如重写View的
layout
方法(皮一下很开心):public void layout(int l, int t, int r, int b){ super.layout(l,t,r + 10, b + 10); }
于是最终宽高就会比测量宽高多了10个像素。
二. draw过程
源码太长,这里贴下关键部分:
//View的draw方法 /* * 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) */ ... ... // 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); drawAutofilledHighlight(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); // Step 7, draw the default focus highlight drawDefaultFocusHighlight(canvas); if (debugDraw()) { debugDrawFocus(canvas); } // we're done... return; } ... ...
上面源码中的注释已经讲的很清楚了,基本的绘制流程是这样的:
- 绘制背景
- 如有必要,保存
canvas
的layers
准备渐变。 - 绘制View的内容
- 绘制children
- 如有必要,绘制渐变边缘并恢复
layers
- 绘制装饰,比如scrollbars.
绘制过程的传递,通过dispatchDraw来实现的:
// Step 4, draw the children dispatchDraw(canvas);
View中这个方法是个空的实现(普通的View也没有children),而ViewGroup则提供了实现。在ViewGroup的这个方法中,会遍历调用所有子View的draw方法。
特殊的方法:
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不打算绘制自己,立此flag以通知系统来做更进一步的优化工作。
- 普通的View默认关闭此flag,但ViewGroup则默认启用这个flag。
- 当我们自定义一个ViewGroup类型的容器View时,如果需要通过
onDraw
来绘制自己时,需要手动关闭此flag:setWillNotDraw(false)
。
View工作原理系列博客
View的工作原理(一):MeasureSpec
View的工作原理(二):measure
View的工作原理(三):layout与draw
View的工作原理(四):自定義View