Android View体系从源代码解析View的layout和draw流程

别裁伪体亲风雅,转益多师是汝师。这篇文章主要讲述Android View体系从源代码解析View的layout和draw流程相关的知识,希望能为你提供帮助。
相关文章
Android View体系(一)视图坐标系
Android View体系(二)实现View滑动的六种方法
Android View体系(三)属性动画
Android View体系(四)从源代码解析Scroller
Android View体系(五)从源代码解析View的事件分发机制
Android View体系(六)从源代码解析Activity的构成
Android View体系(七)从源代码解析View的measure流程
前言
上一篇文章我们讲了View的measure的流程。接下来我们讲下View的layout和draw流程,假设你理解了View的measure的流程。那这篇文章自然就不在话下了。
1.View的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); 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; }

传进来里面的四个參数各自是View的四个点的坐标。它的坐标不是相对屏幕的原点,并且相对于它的父布局来说的。
l 和 t 是子控件左边缘和上边缘相对于父类控件左边缘和上边缘的距离。
r 和 b是子控件右边缘和下边缘相对于父类控件左边缘和上边缘的距离。来看看setFrame()方法里写了什么:
protected boolean setFrame(int left, int top, int right, int bottom) { boolean changed = false; if (DBG) { Log.d("View", this + " View.setFrame(" + left + "," + top + "," + right + "," + bottom + ")"); }if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { changed = true; // Remember our drawn bit int drawn = mPrivateFlags & PFLAG_DRAWN; int oldWidth = mRight - mLeft; int oldHeight = mBottom - mTop; int newWidth = right - left; int newHeight = bottom - top; boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight); // Invalidate our old position invalidate(sizeChanged); mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); ...省略 } return changed; }

在setFrame()方法里主要是用来设置View的四个顶点的值,也就是mLeft 、mTop、mRight和 mBottom的值。在调用setFrame()方法后,调用onLayout()方法:
protected void onLayout(boolean changed, int left, int top, int right, int bottom) { }

onLayout()方法没有去做什么,这个和onMeasure()方法相似,确定位置时依据不同的控件有不同的实现,所以在View和ViewGroup中均没有实现onLayout()方法。
既然这样,我们就来看看LinearLayout的onLayout()方法:
@Override 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); } }

layoutVertical做了什么呢?
void layoutVertical(int left, int top, int right, int bottom) { final int paddingLeft = mPaddingLeft; int childTop; int childLeft; // Where right end of child should go final int width = right - left; int childRight = width - mPaddingRight; // Space available for child int childSpace = width - paddingLeft - mPaddingRight; final int count = getVirtualChildCount(); final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; switch (majorGravity) { case Gravity.BOTTOM: // mTotalLength contains the padding already childTop = mPaddingTop + bottom - top - mTotalLength; break; // mTotalLength contains the padding already case Gravity.CENTER_VERTICAL: childTop = mPaddingTop + (bottom - top - mTotalLength) / 2; break; case Gravity.TOP: default: childTop = mPaddingTop; break; }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); switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { case Gravity.CENTER_HORIZONTAL: childLeft = paddingLeft + ((childSpace - childWidth) / 2) + lp.leftMargin - lp.rightMargin; break; case Gravity.RIGHT: childLeft = childRight - childWidth - lp.rightMargin; break; case Gravity.LEFT: default: childLeft = paddingLeft + lp.leftMargin; break; }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()方法:
private void setChildFrame(View child, int left, int top, int width, int height) { child.layout(left, top, left + width, top + height); }

在setChildFrame()方法中调用子元素的layout()方法来确定自己的位置。我们看到childTop这个值是逐渐增大的,这是为了在垂直方向,子元素是一个接一个排列的而不是重叠的。
2.View的draw流程
View的draw流程非常easy。先来看看View的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; // Step 1, draw the background, if needed int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } ...省略 // Step 2, save the canvas‘ layers int paddingLeft = mPaddingLeft; final boolean offsetRequired = isPaddingOffsetRequired(); if (offsetRequired) { paddingLeft += getLeftPaddingOffset(); } ...省略 // 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 final Paint p = scrollabilityCache.paint; final Matrix matrix = scrollabilityCache.matrix; final Shader fade = scrollabilityCache.shader; ...省略 // Step 6, draw decorations (scrollbars) onDrawScrollBars(canvas); if (mOverlay != null & & !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } }

从源代码的凝视我们看到draw流程有六个步骤。当中第2步和第5步能够跳过:
  1. 假设有设置背景。则绘制背景
  2. 保存canvas层
  3. 绘制自身内容
  4. 假设有子元素则绘制子元素
  5. 绘制效果
  6. 绘制装饰品(scrollbars)
【Android View体系从源代码解析View的layout和draw流程】好了,关于View的工作流程就说到这里了,接下来会讲到自己定义View。









    推荐阅读