1 childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
2 childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
3 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
mView.draw(canvas);
......
1 /** 2 * Figures out the measure spec for the root view in a window based on it's 3 * layout params. 4 * 5 * @param windowSize 6 * The available width or height of the window 7 * 8 * @param rootDimension 9 * The layout params for one dimension (width or height) of the 10 * window. 11 * 12 * @return The measure spec to use to measure the root view. 13 */ 14 private static int getRootMeasureSpec(int windowSize, int rootDimension) { 15 int measureSpec; 16 switch (rootDimension) { 17 18 case ViewGroup.LayoutParams.MATCH_PARENT: 19 // Window can't resize. Force root view to be windowSize. 20 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); 21 break; 22 case ViewGroup.LayoutParams.WRAP_CONTENT: 23 // Window can resize. Set max size for root view. 24 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); 25 break; 26 default: 27 // Window wants to be an exact size. Force root view to be that size. 28 measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); 29 break; 30 } 31 return measureSpec; 32 }
/** * <p> * This is called to find out how big a view should be. The parent * supplies constraint information in the width and height parameters. * </p> * * <p> * The actual measurement work of a view is performed in * {@link #onMeasure(int, int)}, called by this method. Therefore, only * {@link #onMeasure(int, int)} can and must be overridden by subclasses. * </p> * * * @param widthMeasureSpec Horizontal space requirements as imposed by the * parent * @param heightMeasureSpec Vertical space requirements as imposed by the * parent * * @see #onMeasure(int, int) */
1 public final void measure(int widthMeasureSpec, int heightMeasureSpec) { 2 boolean optical = isLayoutModeOptical(this); 3 if (optical != isLayoutModeOptical(mParent)) { 4 Insets insets = getOpticalInsets(); 5 int oWidth = insets.left + insets.right; 6 int oHeight = insets.top + insets.bottom; 7 widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth); 8 heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight); 9 } 10 11 // Suppress sign extension for the low bytes 12 long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL; 13 if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2); 14 15 if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || 16 widthMeasureSpec != mOldWidthMeasureSpec || 17 heightMeasureSpec != mOldHeightMeasureSpec) { 18 19 // first clears the measured dimension flag 20 mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; 21 22 resolveRtlPropertiesIfNeeded(); 23 24 int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : 25 mMeasureCache.indexOfKey(key); 26 if (cacheIndex < 0 || sIgnoreMeasureCache) { 27 // measure ourselves, this should set the measured dimension flag back 28 onMeasure(widthMeasureSpec, heightMeasureSpec); 29 mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; 30 } else { 31 long value = mMeasureCache.valueAt(cacheIndex); 32 // Casting a long to int drops the high 32 bits, no mask needed 33 setMeasuredDimensionRaw((int) (value >> 32), (int) value); 34 mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; 35 } 36 37 // flag not set, setMeasuredDimension() was not invoked, we raise 38 // an exception to warn the developer 39 if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) { 40 throw new IllegalStateException("View with id " + getId() + ": " 41 + getClass().getName() + "#onMeasure() did not set the" 42 + " measured dimension by calling" 43 + " setMeasuredDimension()"); 44 } 45 46 mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; 47 } 48 49 mOldWidthMeasureSpec = widthMeasureSpec; 50 mOldHeightMeasureSpec = heightMeasureSpec; 51 52 mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | 53 (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension 54 }
仔细看measure方法声明可以看到该方法是final的,也就是子view不能对其进行重写,根据注释可以知道:为整个View树计算实际的大小,然后设置实际的高和宽,每个View控件的实际宽高都是由父视图和自身决定的。实际的测量是在onMeasure方法进行,所以在View的子类需要重写onMeasure方法,这是因为measure方法是final的,不允许重载,所以View子类只能通过重载onMeasure来实现自己的测量逻辑
该方法中的两个参数都是父类传递进来的,表示父类的规格;
下面看一下View的onMeasure源码:
1 /** 2 * <p> 3 * Measure the view and its content to determine the measured width and the 4 * measured height. This method is invoked by {@link #measure(int, int)} and 5 * should be overridden by subclasses to provide accurate and efficient 6 * measurement of their contents. 7 * </p> 8 * 9 * <p> 10 * <strong>CONTRACT:</strong> When overriding this method, you 11 * <em>must</em> call {@link #setMeasuredDimension(int, int)} to store the 12 * measured width and height of this view. Failure to do so will trigger an 13 * <code>IllegalStateException</code>, thrown by 14 * {@link #measure(int, int)}. Calling the superclass' 15 * {@link #onMeasure(int, int)} is a valid use. 16 * </p> 17 * 18 * <p> 19 * The base class implementation of measure defaults to the background size, 20 * unless a larger size is allowed by the MeasureSpec. Subclasses should 21 * override {@link #onMeasure(int, int)} to provide better measurements of 22 * their content. 23 * </p> 24 * 25 * <p> 26 * If this method is overridden, it is the subclass's responsibility to make 27 * sure the measured height and width are at least the view's minimum height 28 * and width ({@link #getSuggestedMinimumHeight()} and 29 * {@link #getSuggestedMinimumWidth()}). 30 * </p> 31 * 32 * @param widthMeasureSpec horizontal space requirements as imposed by the parent. 33 * The requirements are encoded with 34 * {@link android.view.View.MeasureSpec}. 35 * @param heightMeasureSpec vertical space requirements as imposed by the parent. 36 * The requirements are encoded with 37 * {@link android.view.View.MeasureSpec}. 38 * 39 * @see #getMeasuredWidth() 40 * @see #getMeasuredHeight() 41 * @see #setMeasuredDimension(int, int) 42 * @see #getSuggestedMinimumHeight() 43 * @see #getSuggestedMinimumWidth() 44 * @see android.view.View.MeasureSpec#getMode(int) 45 * @see android.view.View.MeasureSpec#getSize(int) 46 */ 47 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 48 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), 49 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); 50 }
代码中的注释对于onMeasure的重写有详细的说明,该方法是由measure触发的,子类必须重写以提供精确而有效的测量
当重写该方法时必须调用setMeasuredDimension(int, int)来存储这个view测量的宽和高,否则会报异常,调用超类的onMeasure方法可以正常运行,不过是按照系统即超类的测量方式进行的
对于非ViewGroup的view,通过调用上面默认的onMeasure就可以实现view的测量,也可以重载onMeasure并调用setMeasuredDimension来设置任意大小的布局,但建议一般不这样做,后面会分析原因
从上面的代码可以看出,默认的onMeasure只实现了调用setMeasuredDimension方法,该方法对View的成员变量mMeasuredWidth和mMeasuredHeight变量赋值,measure的主要目的就是对View树中的每个View的mMeasuredWidth和mMeasuredHeight进行赋值,所以一旦这两个变量被赋值意味着该View的测量工作结束
默认实现中参数是通过调用getDefaultSize方法来传递的,接下来看一下该方法的源码:
1 public static int getDefaultSize(int size, int measureSpec) { 2 int result = size; 3 //通过MeasureSpec解析获取mode与size 4 int specMode = MeasureSpec.getMode(measureSpec); 5 int specSize = MeasureSpec.getSize(measureSpec); 6 7 switch (specMode) { 8 case MeasureSpec.UNSPECIFIED: 9 result = size; 10 break; 11 case MeasureSpec.AT_MOST: 12 case MeasureSpec.EXACTLY: 13 result = specSize; 14 break; 15 } 16 return result; 17 }
可以看到如果specMode等于AT_MOST或EXACTLY就返回specSize,这是系统默认的格式
回过头继续看上面onMeasure方法,其中getDefaultSize参数的widthMeasureSpec和heightMeasureSpec都是由父View传递进来的。getSuggestedMinimumWidth与getSuggestedMinimumHeight都是View的方法,具体如下:
1 /** 2 * Returns the suggested minimum width that the view should use. This 3 * returns the maximum of the view's minimum width) 4 * and the background's minimum width 5 * ({@link android.graphics.drawable.Drawable#getMinimumWidth()}). 6 * <p> 7 * When being used in {@link #onMeasure(int, int)}, the caller should still 8 * ensure the returned width is within the requirements of the parent. 9 * 10 * @return The suggested minimum width of the view. 11 */ 12 protected int getSuggestedMinimumWidth() { 13 return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); 14 } 15 16 17 protected int getSuggestedMinimumHeight() { 18 return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); 19 20 }
可以看到:建议的最小宽度和高度都是由View的Background尺寸与通过设置View的miniXXX属性共同决定的
到这里就完成了view的measure过程
对于一个界面可能包括很多次measure,因为对于一个布局而言,一般会包括很多子view,每个子视图都需要measure,根据view的继承结构可以知道可以嵌套的view都是继承于ViewGroup的,故而在ViewGroup中定义了很多方法:measureChildren, measureChild, measureChildWithMargins,来对子视图进行测量,measureChildren内部实质只是循环调用measureChild,measureChild和measureChildWithMargins的区别就是是否把margin和padding也作为子视图的大小
下面首先来看一下measureChildren的源码:
1 protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { 2 final int size = mChildrenCount; 3 final View[] children = mChildren; 4 for (int i = 0; i < size; ++i) { 5 final View child = children[i]; 6 if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { 7 measureChild(child, widthMeasureSpec, heightMeasureSpec); 8 } 9 } 10 }
可以看到循环遍历子视图,然后第7行调用measureChild方法,和上面讲的一致,接下来先来看一下measureChild的源码:
1 protected void measureChild(View child, int parentWidthMeasureSpec, 2 int parentHeightMeasureSpec) { 3 final LayoutParams lp = child.getLayoutParams(); 4 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, 5 mPaddingLeft + mPaddingRight, lp.width); 6 final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, 7 mPaddingTop + mPaddingBottom, lp.height); 8 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 9 }
可以看到,在第4行和第6行分别调用了getChildMeasureSpec()方法来去计算子视图的MeasureSpec,计算的依据就是布局文件中定义的MATCH_PARENT、WRAP_CONTENT等值,这个方法的内部细节就不再贴出;然后在第8行调用子视图的measure方法,并把计算出的MeasureSpec传递进去,这样就和前面分析单独view的测量过程是一样的了,至此就可以明白整个测量过程了,接着再来看一下measureChilldWithMargins源码:
1 /** 2 * Ask one of the children of this view to measure itself, taking into 3 * account both the MeasureSpec requirements for this view and its padding 4 * and margins. The child must have MarginLayoutParams The heavy lifting is 5 * done in getChildMeasureSpec. 6 * 7 * @param child The child to measure 8 * @param parentWidthMeasureSpec The width requirements for this view 9 * @param widthUsed Extra space that has been used up by the parent 10 * horizontally (possibly by other children of the parent) 11 * @param parentHeightMeasureSpec The height requirements for this view 12 * @param heightUsed Extra space that has been used up by the parent 13 * vertically (possibly by other children of the parent) 14 */ 15 protected void measureChildWithMargins(View child, 16 int parentWidthMeasureSpec, int widthUsed, 17 int parentHeightMeasureSpec, int heightUsed) { 18 //获取子视图的LayoutParams 19 final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); 20 //调整MeasureSpec 21 //通过这两个参数以及子视图本身的LayoutParams来共同决定子视图的测量规格 22 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, 23 mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin 24 + widthUsed, lp.width); 25 final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, 26 mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin 27 + heightUsed, lp.height); 28 //调运子View的measure方法,子View的measure中会回调子View的onMeasure方法 29 child.measure(childWidthMeasureSpec, childHeightMeasureSpec); 30 }
可以看出和上面的measureChild方法类似,唯一不同的就是加上了左右边距margin,刚才前面没有对getChildMeasureSpec方法源码进行分析,这里为了全面理解,还是来查看一下源码:
1 public static int getChildMeasureSpec(int spec, int padding, int childDimension) { 2 //获取当前Parent View的Mode和Size 3 int specMode = MeasureSpec.getMode(spec); 4 int specSize = MeasureSpec.getSize(spec); 5 //获取Parent size与padding差值(也就是Parent剩余大小),若差值小于0直接返回0 6 int size = Math.max(0, specSize - padding); 7 //定义返回值存储变量 8 int resultSize = 0; 9 int resultMode = 0; 10 //依据当前Parent的Mode进行switch分支逻辑 11 switch (specMode) { 12 // Parent has imposed an exact size on us 13 //默认Root View的Mode就是EXACTLY 14 case MeasureSpec.EXACTLY: 15 if (childDimension >= 0) { 16 //如果child的layout_width属性在xml或者java中给予具体大于等于0的数值 17 //设置child的size为真实layout_width属性值,mode为EXACTLY 18 resultSize = childDimension; 19 resultMode = MeasureSpec.EXACTLY; 20 } else if (childDimension == LayoutParams.MATCH_PARENT) { 21 //如果child的layout_width属性在xml或者java中给予MATCH_PARENT 22 // Child wants to be our size. So be it. 23 //设置child的size为size,mode为EXACTLY 24 resultSize = size; 25 resultMode = MeasureSpec.EXACTLY; 26 } else if (childDimension == LayoutParams.WRAP_CONTENT) { 27 //如果child的layout_width属性在xml或者java中给予WRAP_CONTENT 28 //设置child的size为size,mode为AT_MOST 29 // Child wants to determine its own size. It can't be 30 // bigger than us. 31 resultSize = size; 32 resultMode = MeasureSpec.AT_MOST; 33 } 34 break; 35 ...... 36 //其他Mode分支类似 37 } 38 //将mode与size通过MeasureSpec方法整合为32位整数返回 39 return MeasureSpec.makeMeasureSpec(resultSize, resultMode); 40 }
可以看见,getChildMeasureSpec的逻辑是通过其父View提供的MeasureSpec参数得到specMode和specSize,然后根据计算出来的specMode以及子View的childDimension(layout_width或layout_height)来计算自身的measureSpec,如果其本身包含子视图,则计算出来的measureSpec将作为调用其子视图measure函数的参数,同时也作为自身调用setMeasuredDimension的参数,如果其不包含子视图则默认情况下最终会调用onMeasure的默认实现,并最终调用到setMeasuredDimension。
所以可以看见onMeasure的参数其实就是这么计算出来的。同时从上面的分析可以看出来,最终决定View的measure大小是View的setMeasuredDimension方法,所以我们可以通过setMeasuredDimension设定死值来设置View的mMeasuredWidth和mMeasuredHeight的大小,但是一个好的自定义View应该会根据子视图的measureSpec来设置mMeasuredWidth和mMeasuredHeight的大小,这样的灵活性更大,所以这也就是上面分析onMeasure时说View的onMeasure最好不要重写死值的原因
可以看见当通过setMeasuredDimension方法最终设置完成View的measure之后View的mMeasuredWidth和mMeasuredHeight成员才会有具体的数值,所以如果我们自定义的View或者使用现成的View想通过getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值
通过上面的分析,其实view的measure过程就是从顶层向下依次测量子视图的过程,即调用子视图的measure方法(measure又会回调onMeasure方法)。
核心点:
- MeasureSpec(View的内部类)测量规格为int型,值由高16位规格模式specMode和低16位具体尺寸specSize组成。其中specMode只有三种值:
MeasureSpec.EXACTLY //确定模式,父View希望子View的大小是确定的,由specSize决定;
MeasureSpec.AT_MOST //最多模式,父View希望子View的大小最多是specSize指定的值;
MeasureSpec.UNSPECIFIED //未指定模式,父View完全依据子View的设计值来决定;
-
View的measure方法是final的,不允许重载,View子类只能重载onMeasure来完成自己的测量逻辑。
-
最顶层DecorView测量时的MeasureSpec是由ViewRootImpl中getRootMeasureSpec方法确定的(LayoutParams宽高参数均为MATCH_PARENT,specMode是EXACTLY,specSize为物理屏幕大小)。
-
ViewGroup类提供了measureChild,measureChild和measureChildWithMargins方法,简化了父子View的尺寸计算。
-
只要是ViewGroup的子类就必须要求LayoutParams继承子MarginLayoutParams,否则无法使用layout_margin参数。
-
View的布局大小由父View和子View共同决定。
-
使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。
由上综述即分析完了整个测量过程
接下来我们来分析layout的过程:
首先大致说一下layout的执行过程:子视图的具体位置是相对于父视图的。View的onLayout是一个空方法,而ViewGroup的onLayout是一个抽象方法,所以如果自定义的View要继承于ViewGroup就必须重写该方法
由最开始的ViewRootImpl的performTraversals方法中可以看出在measure执行完毕后就会执行layout:
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
这里先来看一下layout的源码:
1 public void layout(int l, int t, int r, int b) { 2 int oldL = mLeft; 3 int oldT = mTop; 4 int oldB = mBottom; 5 int oldR = mRight; 6 boolean changed = setFrame(l, t, r, b); 7 if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) { 8 if (ViewDebug.TRACE_HIERARCHY) { 9 ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT); 10 } 11 onLayout(changed, l, t, r, b); 12 mPrivateFlags &= ~LAYOUT_REQUIRED; 13 if (mOnLayoutChangeListeners != null) { 14 ArrayList<OnLayoutChangeListener> listenersCopy = 15 (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone(); 16 int numListeners = listenersCopy.size(); 17 for (int i = 0; i < numListeners; ++i) { 18 listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); 19 } 20 } 21 } 22 mPrivateFlags &= ~FORCE_LAYOUT; 23 }
第6行调用setFrame方法,传入新的view坐标参数,返回一个boolean值,用来决定是否改变了,如果改变了就需要重新布局,第11行调用onLayout,到这里你会发现和上面分析measure的过程很类似,也是会回调,接着我们就来看一下onLayout的源码,结果会发现方法实现为空:因为onLayout()过程是为了确定视图在布局中所在的位置,而这个操作应该是由布局来完成的,即父视图决定子视图的显示位置
那我们再来看一下ViewGroup的onLayout方法实现,你会发现是个抽象方法
protected abstract void onLayout(boolean changed, int l, int t, int r, int b);
所以ViewGroup的所有子视图都必须重写该方法,事实上,LinearLayout和RelativeLayout都是重写了这个方法,然后在内部按照各自的规则对子视图进行布局的
那既然这样,我们要知道onLayout的编写实现就需要来参考一下系统控件的实现了,这里以LinearLayout为例:
1 @RemoteView 2 public class LinearLayout extends ViewGroup { 3 4 @Override 5 protected void onLayout(boolean changed, int l, int t, int r, int b) { 6 if (mOrientation == VERTICAL) { 7 layoutVertical(l, t, r, b); 8 } else { 9 layoutHorizontal(l, t, r, b); 10 } 11 } 12 ..... 13 }
可以看到对于LinearLayout的onLayout方法内部会根据布局方向来调用不同的方法,这也正说明了我们再使用LinearLayout时设置方向了
这里我们就来分析Vertical的情况吧:
1 /** 2 * Position the children during a layout pass if the orientation of this 3 * LinearLayout is set to {@link #VERTICAL}. 4 * 5 * @see #getOrientation() 6 * @see #setOrientation(int) 7 * @see #onLayout(boolean, int, int, int, int) 8 * @param left 9 * @param top 10 * @param right 11 * @param bottom 12 */ 13 void layoutVertical(int left, int top, int right, int bottom) { 14 final int paddingLeft = mPaddingLeft; 15 16 int childTop; 17 int childLeft; 18 19 // Where right end of child should go 计算父窗口推荐的子View宽度 20 final int width = right - left; 21 int childRight = width - mPaddingRight; //计算父窗口推荐的子View右侧位置 22 23 // Space available for child child可使用空间大小 24 int childSpace = width - paddingLeft - mPaddingRight; 25 // 通过ViewGroup的getChildCount方法获取ViewGroup的子View个数 26 final int count = getVirtualChildCount(); 27 // 获取Gravity属性设置 28 final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 29 final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK; 30 //依据majorGravity计算childTop的位置值 31 switch (majorGravity) { 32 case Gravity.BOTTOM: 33 // mTotalLength contains the padding already 34 childTop = mPaddingTop + bottom - top - mTotalLength; 35 break; 36 37 // mTotalLength contains the padding already 38 case Gravity.CENTER_VERTICAL: 39 childTop = mPaddingTop + (bottom - top - mTotalLength) / 2; 40 break; 41 42 case Gravity.TOP: 43 default: 44 childTop = mPaddingTop; 45 break; 46 } 47 //开始遍历整个ViewGroup 48 for (int i = 0; i < count; i++) { 49 final View child = getVirtualChildAt(i); 50 if (child == null) { 51 childTop += measureNullChild(i); 52 } else if (child.getVisibility() != GONE) {//LinearLayout中其子视图显示的宽和高由measure过程来决定的 53 final int childWidth = child.getMeasuredWidth(); 54 final int childHeight = child.getMeasuredHeight(); 55 //获取子View的LayoutParams 56 final LinearLayout.LayoutParams lp = 57 (LinearLayout.LayoutParams) child.getLayoutParams(); 58 59 int gravity = lp.gravity; 60 if (gravity < 0) { 61 gravity = minorGravity; 62 } 63 final int layoutDirection = getLayoutDirection(); 64 final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection); 65 switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { //依据不同的absoluteGravity计算childLeft位置 66 case Gravity.CENTER_HORIZONTAL: 67 childLeft = paddingLeft + ((childSpace - childWidth) / 2) 68 + lp.leftMargin - lp.rightMargin; 69 break; 70 71 case Gravity.RIGHT: 72 childLeft = childRight - childWidth - lp.rightMargin; 73 break; 74 75 case Gravity.LEFT: 76 default: 77 childLeft = paddingLeft + lp.leftMargin; 78 break; 79 } 80 81 if (hasDividerBeforeChildAt(i)) { 82 childTop += mDividerHeight; 83 } 84 85 childTop += lp.topMargin;////通过垂直排列计算调用child的layout设置child的位置 86 setChildFrame(child, childLeft, childTop + getLocationOffset(child), 87 childWidth, childHeight); 88 childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child); 89 90 i += getChildrenSkipCount(child, i); 91 } 92 } 93 }
从上面分析的ViewGroup子类LinearLayout的onLayout实现代码可以看出,一般情况下layout过程会参考measure过程中计算得到的mMeasuredWidth和mMeasuredHeight来安排子View在父View中显示的位置,但这不是必须的,measure过程得到的结果可能完全没有实际用处,特别是对于一些自定义的ViewGroup,其子View的个数、位置和大小都是固定的,这时候我们可以忽略整个measure过程,只在layout函数中传入的4个参数来安排每个子View的具体位置。
到这里就不得不提getWidth()、getHeight()和getMeasuredWidth()、getMeasuredHeight()这两对方法之间的区别(上面分析measure过程已经说过getMeasuredWidth()、getMeasuredHeight()必须在onMeasure之后使用才有效)。可以看出来getWidth()与getHeight()方法必须在layout(int l, int t, int r, int b)执行之后才有效。那我们看下View源码中这些方法的实现吧,如下:
1 public final int getMeasuredWidth() { 2 return mMeasuredWidth & MEASURED_SIZE_MASK; 3 } 4 5 public final int getMeasuredHeight() { 6 return mMeasuredHeight & MEASURED_SIZE_MASK; 7 } 8 9 public final int getWidth() { 10 return mRight - mLeft; 11 } 12 13 public final int getHeight() { 14 return mBottom - mTop; 15 } 16 17 public final int getLeft() { 18 return mLeft; 19 } 20 21 public final int getRight() { 22 return mRight; 23 } 24 25 public final int getTop() { 26 return mTop; 27 } 28 29 public final int getBottom() { 30 return mBottom; 31 }
layout总结:
整个layout过程比较容易理解,从上面分析可以看出layout也是从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。具体layout核心主要有以下几点:
-
View.layout方法可被重载,ViewGroup.layout为final的不可重载,ViewGroup.onLayout为abstract的,子类必须重载实现自己的位置逻辑。
-
measure操作完成后得到的是对每个View经测量过的measuredWidth和measuredHeight,layout操作完成之后得到的是对每个View进行位置分配后的mLeft、mTop、mRight、mBottom,这些值都是相对于父View来说的。
-
凡是layout_XXX的布局属性基本都针对的是包含子View的ViewGroup的,当对一个没有父容器的View设置相关layout_XXX属性是没有任何意义的(前面《Android应用setContentView与LayoutInflater加载解析机制源码分析》也有提到过)。
-
使用View的getWidth()和getHeight()方法来获取View测量的宽高,必须保证这两个方法在onLayout流程之后被调用才能返回有效值。
那分析了layout的具体原理,那接下来附上一个简单的实例来看一下实际应用中如何使用onLayout:这一块实例是引用郭大神的博文中的
1 public class SimpleLayout extends ViewGroup { 2 3 public SimpleLayout(Context context, AttributeSet attrs) { 4 super(context, attrs); 5 } 6 7 @Override 8 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 9 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 10 if (getChildCount() > 0) { 11 View childView = getChildAt(0); 12 measureChild(childView, widthMeasureSpec, heightMeasureSpec); 13 } 14 } 15 16 @Override 17 protected void onLayout(boolean changed, int l, int t, int r, int b) { 18 if (getChildCount() > 0) { 19 View childView = getChildAt(0); 20 childView.layout(0, 0, childView.getMeasuredWidth(), childView.getMeasuredHeight()); 21 } 22 } 23 24 }
上面的例子只包含一个子view,onLayout的实现也很简单,就是判断子视图个数不为空,然后获取子视图,根据measure测量的宽高直接调用layout方法即可
具体显示的位置可以*改变layout中的参数
最后来分析draw的详细过程:
由最开始的ViewRootImpl的performTraversals()方法可以看出在执行完measure和layout之后就要执行draw方法了(ViewRoot中的代码会继续执行并创建出一个Canvas对象,然后调用View的draw()方法来执行具体的绘制工作),由前面measure和layout的比较我们知道两者整体执行过程很相似,都会在递归执行measure或layout 之中回调相应的onMeasure或onLayout方法,于是到这里我们可以猜想draw是不是也类似呢?实际上draw的过程要相对复杂一些,除了会调用onDraw方法之外,还有一些其他的方法需要调用,接下来我们就源码来具体分析一下:
这里我们首先来画一下整个view树结构的遍历过程:
接下来先来看一下draw的源码:代码较长
1 /** 2 * Manually render this view (and all of its children) to the given Canvas. 3 * The view must have already done a full layout before this function is 4 * called. When implementing a view, implement 5 * {@link #onDraw(android.graphics.Canvas)} instead of overriding this method. 6 * If you do need to override this method, call the superclass version. 7 * 8 * @param canvas The Canvas to which the View is rendered. 9 */ 10 @CallSuper 11 public void draw(Canvas canvas) { 12 final int privateFlags = mPrivateFlags; 13 final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE && 14 (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState); 15 mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; 16 17 /* 18 * Draw traversal performs several drawing steps which must be executed 19 * in the appropriate order: 20 * 21 * 1. Draw the background 22 * 2. If necessary, save the canvas' layers to prepare for fading 23 * 3. Draw view's content 24 * 4. Draw children 25 * 5. If necessary, draw the fading edges and restore layers 26 * 6. Draw decorations (scrollbars for instance) 27 */ 28 29 // Step 1, draw the background, if needed 30 int saveCount; 31 32 if (!dirtyOpaque) { 33 drawBackground(canvas); 34 } 35 36 // skip step 2 & 5 if possible (common case) 37 final int viewFlags = mViewFlags; 38 boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0; 39 boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0; 40 if (!verticalEdges && !horizontalEdges) { 41 // Step 3, draw the content 42 if (!dirtyOpaque) onDraw(canvas); 43 44 // Step 4, draw the children 45 dispatchDraw(canvas); 46 47 // Overlay is part of the content and draws beneath Foreground 48 if (mOverlay != null && !mOverlay.isEmpty()) { 49 mOverlay.getOverlayView().dispatchDraw(canvas); 50 } 51 52 // Step 6, draw decorations (foreground, scrollbars) 53 onDrawForeground(canvas); 54 55 // we're done... 56 return; 57 } 58 59 /* 60 * Here we do the full fledged routine... 61 * (this is an uncommon case where speed matters less, 62 * this is why we repeat some of the tests that have been 63 * done above) 64 */ 65 66 boolean drawTop = false; 67 boolean drawBottom = false; 68 boolean drawLeft = false; 69 boolean drawRight = false; 70 71 float topFadeStrength = 0.0f; 72 float bottomFadeStrength = 0.0f; 73 float leftFadeStrength = 0.0f; 74 float rightFadeStrength = 0.0f; 75 76 // Step 2, save the canvas' layers 77 int paddingLeft = mPaddingLeft; 78 79 final boolean offsetRequired = isPaddingOffsetRequired(); 80 if (offsetRequired) { 81 paddingLeft += getLeftPaddingOffset(); 82 } 83 84 int left = mScrollX + paddingLeft; 85 int right = left + mRight - mLeft - mPaddingRight - paddingLeft; 86 int top = mScrollY + getFadeTop(offsetRequired); 87 int bottom = top + getFadeHeight(offsetRequired); 88 89 if (offsetRequired) { 90 right += getRightPaddingOffset(); 91 bottom += getBottomPaddingOffset(); 92 } 93 94 final ScrollabilityCache scrollabilityCache = mScrollCache; 95 final float fadeHeight = scrollabilityCache.fadingEdgeLength; 96 int length = (int) fadeHeight; 97 98 // clip the fade length if top and bottom fades overlap 99 // overlapping fades produce odd-looking artifacts 100 if (verticalEdges && (top + length > bottom - length)) { 101 length = (bottom - top) / 2; 102 } 103 104 // also clip horizontal fades if necessary 105 if (horizontalEdges && (left + length > right - length)) { 106 length = (right - left) / 2; 107 } 108 109 if (verticalEdges) { 110 topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength())); 111 drawTop = topFadeStrength * fadeHeight > 1.0f; 112 bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength())); 113 drawBottom = bottomFadeStrength * fadeHeight > 1.0f; 114 } 115 116 if (horizontalEdges) { 117 leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength())); 118 drawLeft = leftFadeStrength * fadeHeight > 1.0f; 119 rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength())); 120 drawRight = rightFadeStrength * fadeHeight > 1.0f; 121 } 122 123 saveCount = canvas.getSaveCount(); 124 125 int solidColor = getSolidColor(); 126 if (solidColor == 0) { 127 final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG; 128 129 if (drawTop) { 130 canvas.saveLayer(left, top, right, top + length, null, flags); 131 } 132 133 if (drawBottom) { 134 canvas.saveLayer(left, bottom - length, right, bottom, null, flags); 135 } 136 137 if (drawLeft) { 138 canvas.saveLayer(left, top, left + length, bottom, null, flags); 139 } 140 141 if (drawRight) { 142 canvas.saveLayer(right - length, top, right, bottom, null, flags); 143 } 144 } else { 145 scrollabilityCache.setFadeColor(solidColor); 146 } 147 148 // Step 3, draw the content 149 if (!dirtyOpaque) onDraw(canvas); 150 151 // Step 4, draw the children 152 dispatchDraw(canvas); 153 154 // Step 5, draw the fade effect and restore layers 155 final Paint p = scrollabilityCache.paint; 156 final Matrix matrix = scrollabilityCache.matrix; 157 final Shader fade = scrollabilityCache.shader; 158 159 if (drawTop) { 160 matrix.setScale(1, fadeHeight * topFadeStrength); 161 matrix.postTranslate(left, top); 162 fade.setLocalMatrix(matrix); 163 p.setShader(fade); 164 canvas.drawRect(left, top, right, top + length, p); 165 } 166 167 if (drawBottom) { 168 matrix.setScale(1, fadeHeight * bottomFadeStrength); 169 matrix.postRotate(180); 170 matrix.postTranslate(left, bottom); 171 fade.setLocalMatrix(matrix); 172 p.setShader(fade); 173 canvas.drawRect(left, bottom - length, right, bottom, p); 174 } 175 176 if (drawLeft) { 177 matrix.setScale(1, fadeHeight * leftFadeStrength); 178 matrix.postRotate(-90); 179 matrix.postTranslate(left, top); 180 fade.setLocalMatrix(matrix); 181 p.setShader(fade); 182 canvas.drawRect(left, top, left + length, bottom, p); 183 } 184 185 if (drawRight) { 186 matrix.setScale(1, fadeHeight * rightFadeStrength); 187 matrix.postRotate(90); 188 matrix.postTranslate(right, top); 189 fade.setLocalMatrix(matrix); 190 p.setShader(fade); 191 canvas.drawRect(right - length, top, right, bottom, p); 192 } 193 194 canvas.restoreToCount(saveCount); 195 196 // Overlay is part of the content and draws beneath Foreground 197 if (mOverlay != null && !mOverlay.isEmpty()) { 198 mOverlay.getOverlayView().dispatchDraw(canvas); 199 } 200 201 // Step 6, draw decorations (foreground, scrollbars) 202 onDrawForeground(canvas); 203 }