/** * 文档描述: * View的Layout过程源码分析 * * 原创作者: * 谷哥的小弟 http://blog.csdn.net/lfdfhl * * 分析笔记: * View在经历过第一阶段的measure之后,进入到第二阶段layout. * 该阶段的目的是 * (1)设置View自身的大小和位置. * (2)设置View的子View大小和位置. * */ @SuppressWarnings({"unchecked"}) public void layout(int l, int t, int r, int b) { int oldL = mLeft; int oldT = mTop; int oldB = mBottom; int oldR = mRight; /** * 第一步: * 利用setFrame()判断View的尺寸是否发生了变化. * setFrame()的分析请参见下面的源码. * * 这一步就体现layout()的一个重要作用: * 设置View自身的大小和位置 */ boolean changed = setFrame(l, t, r, b); /** * 第二步: * View的尺寸发生了变化或者需要重新布局,进入该if代码段 */ if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); } /** * 第三步: * 调用View的onLayou()方法. * 这是一个非常重要的地方. * * 查看源码可以发现: * View和ViewGroup中默认的onLayout方法都是空方法. * * 一般而言: * 继承自View的子类不用覆写该layout(),重点应该关注onDraw(). * 继承自ViewGroup的子类不用覆写该layout(),重点应该关注onLayout() * 且在onLayout方法中依次循环子View,并调用子View的layout方法!!! * * 举个例子: * 在ViewGroupSubClass的onLayout()方法中常见如下代码: * int childCount = getChildCount(); * for (int i = 0; i < childCount; i++) { * View childView = getChildAt(i); * childView.layout(left, top, right, bottom); * } * * 所以如果继承自ViewGroup在布局阶段应该重点关注onLayout方法中!! * * 这一步就体现layout()的另外一个重要作用: * 设置View的子View大小和位置. * */ onLayout(changed, l, t, r, b); mPrivateFlags &= ~LAYOUT_REQUIRED; /** * 第四步: * 调用所有OnLayoutChangeListener接口的实现,通知View大小和位置发生了改变 . * 比如某个子view.addOnLayoutChangeListener(listener).这个listener就保存 * 在了mOnLayoutChangeListeners中. * 这个listener在开发中还是挺有用的,值得关注 */ if (mOnLayoutChangeListeners != null) { ArrayList<OnLayoutChangeListener> listenersCopy = (ArrayList<OnLayoutChangeListener>) 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()方法源码分析 * 该方法主要作用: * Assign a size and position to this view. * 为View指定大小和位置. * * 在View中表示View的大小和位置常常用到四个变量: * int left, int top, int right, int bottom * 这四个变量可以分别用以下方法获得: * getLeft(),getTop(),getRight(),getBottom() * * 但是请注意: * left 表示该View自身的左边距离parent的左边的距离 * right 表示该View自身的右边距离parent的左边的距离 * top 表示该View自身的上边距离parent的上边的距离 * bottom 表示该View自身的下边距离parent的上边的距离 * * 只要知道了这四个值也就自然确定了View的大小和坐标 * 其中宽高为: * int width = right - left; * int height = bottom - top; * 其中坐标为: * 左上角为(left,top),右下角为(right,bottom) * * * 注意该方法的返回值: * return true if the new size and position are different than the previous ones * 如果该View的尺寸发生了变化则返回true,否则返回false. * */ 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 + ")"); } /** * 将新旧left,right,top,bottom进行比较,若任意一对值不相等则将changed设置为true */ if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) { changed = true; int drawn = mPrivateFlags & PFLAG_DRAWN; /** * 第一步: * 计算View原先的宽和高 */ int oldWidth = mRight - mLeft; int oldHeight = mBottom - mTop; /** * 第二步: * 计算View现在的宽和高 */ int newWidth = right - left; int newHeight = bottom - top; /** * 第三步: * 比较View的新旧尺寸. * 如果尺寸发生了变化则设置sizeChanged为true. * 该值稍后会用到 */ boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight); /** * 第四步: * 刷新 */ invalidate(sizeChanged); /** * 第五步: * 将新得到的left,top,right,bottom保存到View的成员变量中 */ mLeft = left; mTop = top; mRight = right; mBottom = bottom; mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); mPrivateFlags |= PFLAG_HAS_BOUNDS; /** * 第六步: * 如果View的尺寸发生了变化则执行sizeChange() */ if (sizeChanged) { sizeChange(newWidth, newHeight, oldWidth, oldHeight); } if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) { mPrivateFlags |= PFLAG_DRAWN; invalidate(sizeChanged); invalidateParentCaches(); } mPrivateFlags |= drawn; mBackgroundSizeChanged = true; if (mForegroundInfo != null) { mForegroundInfo.mBoundsChanged = true; } notifySubtreeAccessibilityStateChangedIfNeeded(); } return changed; }