ViewGroup的measure流程 上一篇View的工作流程——measure流程 中了解到了View的measure与ViewGroup的measure流程有密不可分的联系,这次就把View的笔记做完。
ViewGroup作为容器除了完成自己的measure过程以外,还会遍历去调用所有子元素的measure方法,各个子元素再递归去执行这个过程。ViewGroup是一个抽象类没有重写onMeasure()方法,但是它提供了一个measureChildren方法。这个方法会遍历View去测量他们自身。
1 2 3 4 5 6 7 8 9 10 11 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); } } }
从上面的代码来看会对满足条件view调用measureChild方法,跟进去看看这个方法:
1 2 3 4 5 6 7 8 9 10 11 12 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); }
从上面的代码可以看到首先会拿到子元素的LayoutParams,然后通过getChildMeasureSpec方法来创建子元素的MeasureSpec,这个方法在前一篇中有过介绍,这里就不多赘述了。最后会调用子元素的measure方法并将创建的MeasureSpec传递给子元素。在这里就不具体结合实现onMeasure()的ViewGroup的子类来分析了,留待以后对ViewGroup进行更详细的学习的时候再说。
layout过程 layout过程主要涉及了两个方法,layout()和onLayout()。layout方法为view和它所有的子元素分配尺寸和位置。layout是Android布局机制(layout mechanism)的第二阶段。在这个阶段,每个parent对他的所有子元素都要调用layout方法去设置他们的位置。子类(派生类)不应该复写这个方法,有子元素的子类应该复写onLayout()方法。在onLayout方法里他们应该调用他们子元素的layout方法。
以上是layout方法的注释,在很多时候源码的注释=api文档,所以推荐各位经常阅读api文档。毕竟别人解析的再好,也是别人的,不如自己去阅读一手的资料,看看写源码的人给我们的一些建议。
稍微歪了一下题,接下来看看layout方法的源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 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; }
以上还是有很多代码理解不能,不过还好跟着书走一遍好了,代码中会通过判断layoutMode(大部分情况下都是返回false),最后通过setFrame方法来设置View四个顶点的位置。四个顶点一旦确定,那么View在父容器中的位置也就确定了。接着会调用onLayout方法,在ViewGroup里onLayout就是个抽象方法,找个实现的子类来看看,以下是LinearLayout的onLayout:
1 2 3 4 5 6 7 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); } }
从字面上来看就是根据LinearLayout的orientation来执行相应的layout,看一下layoutVertical方法的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 void layoutVertical (int left, int top, int right, int bottom) { final int paddingLeft = mPaddingLeft; int childTop; int childLeft; final int width = right - left; int childRight = width - mPaddingRight; 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: childTop = mPaddingTop + bottom - top - mTotalLength; break ; 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); } } }
在遍历子元素之前会先初始化childTop,接着遍历子元素,在处理之后会调用setChildFrame方法来为子元素指定对应的位置,而clipTop的值会不断的增大,这意味着之后的元素会被放到靠下的位置,这和vetical的LinearLayout符合。setChildFrame()中会调用子元素的layout,之前分析过layout方法会为自身和自身的子元素确定位置和尺寸信息。如果这个child也有子元素,那么就会递归调用onLayout而子元素的子元素又会调用layout方法……子子孙孙无穷尽了。玩笑,递归肯定有出口的,那就是最里层的元素。如此处理之后,整个View树就完成了layout过程。
draw 简单的来说draw就是将view绘制到屏幕上。在调用这个方法之前必须完成onlayout的过程。在自定义view的时候实现onDraw而不是重写draw。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 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; int saveCount; if (!dirtyOpaque) { drawBackground(canvas); } final int viewFlags = mViewFlags; boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0 ; boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0 ; if (!verticalEdges && !horizontalEdges) { if (!dirtyOpaque) onDraw(canvas); dispatchDraw(canvas); if (mOverlay != null && !mOverlay.isEmpty()) { mOverlay.getOverlayView().dispatchDraw(canvas); } onDrawForeground(canvas); return ; }
这个源码的注释还真是详细……主要的流程都已经在源码中标注了出来这里就不做过多的解释了。
画背景
有必要的话保存画布层级
画view的内容
画子元素
有必要的话画边缘恢复层级
画装饰
到这View的工作流程暂时就过了一遍了,终于为自定义View扫清了一个障碍,不过自定义View还需要更多的练习,光知道原理是没什么用的,还要回用合适的工具、方法来构建出自己想要的东西。