简单研究Android View绘制一 测量过程
2015-07-27 16:52:58
一、如何通过继承ViewGroup来实现自定义View?首先得搞清楚Android时如何绘制View的,参考Android官方文档:How Android Draws Views
以下翻译摘自:http://blog.****.net/linghu_java/article/details/23882681,这也是一片好文章,推荐大家看看~
When an Activity receives focus, it will be requested to draw its layout. The Android framework will handle the procedure for drawing, but the Activity must provide the root node of its layout hierarchy.
当一个Activity获取焦点的时候,它就会被要求去画出它的布局。Android框架会处理绘画过程,但是Activity必须提供布局的根节点,在上面的图中,我们可以理解为最上面的ViewGroup,而实际上还有一个更深的root view。
Drawing begins with the root node of the layout. It is requested to measure and draw the layout tree. Drawing is handled by walking the tree and rendering each View that intersects the invalid region. In turn, each View group is responsible for requesting each of its children to be drawn (with the draw() method) and each View is responsible for drawing itself. Because the tree is traversed in-order, this means that parents will be drawn before (i.e., behind) their children, with siblings drawn in the order they appear in the tree.
绘画开始于布局的根节点,要求测量并且画出整个布局树。绘画通过遍历整个树来完成,不可见的区域的View被放弃。每个ViewGroup负责要求它的子View去绘画,每个子View则负责去绘画自己。因为布局树是顺序遍历的,这意味着父View在子View之前被画出来(这个符合常理,后面解释)。
注解:假设一个TextView设置为(FILL_PAREMT, FILL_PARENT),则很明显必须先画出父View的尺寸,才能去画出这个TextView,而且从上至下也就是先画父View再画子View,显示的时候才正常,否则父View会挡住子View的显示。说白了,不是子View你想要多大就能有多大的空间给你,前提是父View得先知道自己有权利使用多大的空间。
Drawing the layout is a two pass process: a measure pass and a layout pass. The measuring pass is implemented in measure(int, int) and is a top-down traversal of the View tree. Each View pushes dimension specifications down the tree during the recursion. At the end of the measure pass, every View has stored its measurements. The second pass happens in layout(int, int, int, int) and is also top-down. During this pass each parent is responsible for positioning all of its children using the sizes computed in the measure pass.
布局绘画涉及两个过程:测量过程和布局过程。测量过程通过measure方法实现,是View树自顶向下的遍历,每个View在循环过程中将尺寸细节往下传递,当测量过程完成之后,所有的View都存储了自己的尺寸。第二个过程则是通过方法layout来实现的,也是自顶向下的。在这个过程中,每个父View负责通过计算好的尺寸放置它的子View。
注解:这和前面说的一样,一个过程是用来丈量尺寸的,一个过程是用来摆放位置的。
When a View's measure() method returns, its getMeasuredWidth() and getMeasuredHeight() values must be set, along with those for all of that View's descendants. A View's measured width and measured height values must respect the constraints imposed by the View's parents. This guarantees that at the end of the measure pass, all parents accept all of their children's measurements. A parent View may call measure() more than once on its children. For example, the parent may measure each child once with unspecified dimensions to find out how big they want to be, then call measure() on them again with actual numbers if the sum of all the children's unconstrained sizes is too big or too small (i.e., if the children don't agree among themselves as to how much space they each get, the parent will intervene and set the rules on the second pass).
当一个View的measure()方法返回的时候,它的getMeasuredWidth和getMeasuredHeight方法的值一定是被设置好的。它所有的子节点同样被设置好。一个View的测量宽和测量高一定要遵循父View的约束,这保证了在测量过程结束的时候,所有的父View可以接受子View的测量值。一个父View或许会多次调用子View的measure()方法。举个例子,父View会使用不明确的尺寸去丈量看看子View到底需要多大,当子View总的尺寸太大或者太小的时候会再次使用实际的尺寸去调用onmeasure().
二、结合自己写的代码框架,分析一下绘制的过程
这是一个布局文件:
1 <com.test.touch.MyLinear2 xmlns:andro 2 android:layout_width="300dp" 3 android:layout_height="200dp" 4 android:paddingTop="22dp" 5 android:layout_marginLeft="10dp" > 6 7 <TextView 8 android: 9 android:layout_width="match_parent" 10 android:layout_height="100dp" 11 android:background="#f00" 12 android:layout_marginLeft="10dp" 13 android:text="Hello World Text" /> 14 15 <com.test.touch.MyTextView 16 android:layout_width="match_parent" 17 android:layout_height="100dp" 18 android:background="#0f0" 19 android:paddingTop="22dp" 20 android:text="Hello World" /> 21 22 </com.test.touch.MyLinear2>
MyLinear2.java代码框架:
1 public class MyLinear2 extends ViewGroup { 2 private static final String TAG = "David_MyLinear2"; 3 4 public MyLinear2(Context context) { 5 super(context); 6 } 7 8 public MyLinear2(Context context, AttributeSet attrs) { 9 super(context, attrs); 10 } 11 12 @Override 13 protected void onLayout(boolean changed, int l, int t, int r, int b) { 14 15 } 16 17 @Override 18 public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { 19 Log.e(TAG, "generateLayoutParams attrs"); 20 return new MarginLayoutParams(getContext(), attrs); 21 } 22 23 @Override 24 protected ViewGroup.LayoutParams generateDefaultLayoutParams() { 25 Log.e(TAG, "generateDefaultLayoutParams"); 26 return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 27 } 28 29 @Override 30 protected boolean checkLayoutParams(LayoutParams p) { 31 return super.checkLayoutParams(p); 32 } 33 34 @Override 35 protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 36 Log.e(TAG, "generateLayoutParams p"); 37 return new MarginLayoutParams(p); 38 } 39 40 @Override 41 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 42 int measuredHeight = measureHeight(heightMeasureSpec); 43 int measuredWidth = measureWidth(widthMeasureSpec); 44 Log.e(TAG, "onMeasure measuredHeight = " + measuredHeight); 45 Log.e(TAG, "onMeasure measuredWidth = " + measuredWidth); 46 setMeasuredDimension(measuredWidth, measuredHeight); 47 measureChildren(widthMeasureSpec, heightMeasureSpec); 48 } 49 50 private int measureHeight(int measureSpec) { 51 52 int specMode = MeasureSpec.getMode(measureSpec); 53 int specSize = MeasureSpec.getSize(measureSpec); 54 55 // Default size if no limits are specified. 56 57 int result = 500; 58 if (specMode == MeasureSpec.AT_MOST){ 59 // Calculate the ideal size of your 60 // control within this maximum size. 61 // If your control fills the available 62 // space return the outer bound. 63 64 result = specSize; 65 } else if (specMode == MeasureSpec.EXACTLY){ 66 // If your control can fit within these bounds return that value. 67 result = specSize; 68 } 69 return result; 70 } 71 72 private int measureWidth(int measureSpec) { 73 int specMode = MeasureSpec.getMode(measureSpec); 74 int specSize = MeasureSpec.getSize(measureSpec); 75 76 // Default size if no limits are specified. 77 int result = 500; 78 if (specMode == MeasureSpec.AT_MOST){ 79 // Calculate the ideal size of your control 80 // within this maximum size. 81 // If your control fills the available space 82 // return the outer bound. 83 result = specSize; 84 } else if (specMode == MeasureSpec.EXACTLY){ 85 // If your control can fit within these bounds return that value. 86 result = specSize; 87 } 88 return result; 89 } 90 }
MyTextView.java代码框架:
1 public class MyTextView extends TextView { 2 private static final String TAG = "David__MyTextView"; 3 4 public MyTextView(Context context, AttributeSet attrs) { 5 super(context, attrs); 6 } 7 8 public MyTextView(Context context) { 9 super(context); 10 } 11 12 @Override 13 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 14 15 int measuredHeight = measureHeight(heightMeasureSpec); 16 int measuredWidth = measureWidth(widthMeasureSpec); 17 Log.e(TAG, "measuredHeight = " + measuredHeight); 18 Log.e(TAG, "measuredWidth = " + measuredWidth); 19 setMeasuredDimension(measuredWidth, measuredHeight); 20 } 21 22 private int measureHeight(int measureSpec) { 23 *** 24 } 25 26 private int measureWidth(int measureSpec) { 27 *** 28 } 29 }
既然前面说到View的绘制涉及到两个过程:测量过程和布局过程,而且他们是有先后顺序的,那么我们先分析测量过程。
三、测量过程
View.java中有measure()、onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法,measure()被设计成了final方法,这就意味着此方法不允许被重写。measure()中调用了onMeasure()方法,所以onMeasure()是可以用来被子类重写的,源码如下,有兴趣的可以展开看看。
measure()源码:
1 /** 2 * <p> 3 * This is called to find out how big a view should be. The parent 4 * supplies constraint information in the width and height parameters. 5 * </p> 6 * 7 * <p> 8 * The actual measurement work of a view is performed in 9 * {@link #onMeasure(int, int)}, called by this method. Therefore, only 10 * {@link #onMeasure(int, int)} can and must be overridden by subclasses. 11 * </p> 12 * 13 * 14 * @param widthMeasureSpec Horizontal space requirements as imposed by the 15 * parent 16 * @param heightMeasureSpec Vertical space requirements as imposed by the 17 * parent 18 * 19 * @see #onMeasure(int, int) 20 */ 21 public final void measure(int widthMeasureSpec, int heightMeasureSpec) { 22 if (DBG_SYSTRACE_MEASURE){ 23 Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure : " + getClass().getSimpleName()); 24 } 25 boolean optical = isLayoutModeOptical(this); 26 if (optical != isLayoutModeOptical(mParent)) { 27 Insets insets = getOpticalInsets(); 28 int oWidth = insets.left + insets.right; 29 int oHeight = insets.top + insets.bottom; 30 widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth); 31 heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight); 32 } 33 if (DBG_LAYOUT) { 34 Xlog.d(VIEW_LOG_TAG, "veiw measure start, this =" + this + ", widthMeasureSpec = " + 35 MeasureSpec.toString(widthMeasureSpec) + ", heightMeasureSpec = " + MeasureSpec.toString(heightMeasureSpec) 36 + ", mOldWidthMeasureSpec = " + MeasureSpec.toString(mOldWidthMeasureSpec) + ", mOldHeightMeasureSpec = " 37 + MeasureSpec.toString(mOldHeightMeasureSpec)); 38 } 39 40 // Suppress sign extension for the low bytes 41 long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL; 42 if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2); 43 44 if ((mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT || 45 widthMeasureSpec != mOldWidthMeasureSpec || 46 heightMeasureSpec != mOldHeightMeasureSpec) { 47 48 // first clears the measured dimension flag 49 mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; 50 51 resolveRtlPropertiesIfNeeded(); 52 53 int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : 54 mMeasureCache.indexOfKey(key); 55 if (cacheIndex < 0 || sIgnoreMeasureCache) { 56 // measure ourselves, this should set the measured dimension flag back 57 /// M: Monitor onMeasue time if longer than 3s print log. 58 long logTime = System.currentTimeMillis(); 59 onMeasure(widthMeasureSpec, heightMeasureSpec); 60 long nowTime = System.currentTimeMillis(); 61 if (nowTime - logTime > DBG_TIMEOUT_VALUE) { 62 Xlog.d(VIEW_LOG_TAG, "[ANR Warning]onMeasure time too long, this =" + this + "time =" + (nowTime - logTime)); 63 } 64 if (DBG_LAYOUT) { 65 Xlog.d(VIEW_LOG_TAG, "veiw measure end, this =" + this + ", mMeasuredWidth = " 66 + mMeasuredWidth + ", mMeasuredHeight = " + mMeasuredHeight + ", time =" + (nowTime - logTime) + " ms"); 67 } 68 mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; 69 } else { 70 long value = mMeasureCache.valueAt(cacheIndex); 71 // Casting a long to int drops the high 32 bits, no mask needed 72 setMeasuredDimension((int) (value >> 32), (int) value); 73 mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; 74 } 75 76 // flag not set, setMeasuredDimension() was not invoked, we raise 77 // an exception to warn the developer 78 if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) { 79 throw new IllegalStateException("onMeasure() did not set the" 80 + " measured dimension by calling" 81 + " setMeasuredDimension()"); 82 } 83 84 mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; 85 } 86 87 mOldWidthMeasureSpec = widthMeasureSpec; 88 mOldHeightMeasureSpec = heightMeasureSpec; 89 90 mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | 91 (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension 92 if (DBG_SYSTRACE_MEASURE){ 93 Trace.traceEnd(Trace.TRACE_TAG_VIEW); 94 } 95 }
onMeasure(int widthMeasureSpec, int heightMeasureSpec)源码:
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 overriden 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 } 51 52 /** 53 * <p>This method must be called by {@link #onMeasure(int, int)} to store the 54 * measured width and measured height. Failing to do so will trigger an 55 * exception at measurement time.</p> 56 * 57 * @param measuredWidth The measured width of this view. May be a complex 58 * bit mask as defined by {@link #MEASURED_SIZE_MASK} and 59 * {@link #MEASURED_STATE_TOO_SMALL}. 60 * @param measuredHeight The measured height of this view. May be a complex 61 * bit mask as defined by {@link #MEASURED_SIZE_MASK} and 62 * {@link #MEASURED_STATE_TOO_SMALL}. 63 */ 64 protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { 65 boolean optical = isLayoutModeOptical(this); 66 if (optical != isLayoutModeOptical(mParent)) { 67 Insets insets = getOpticalInsets(); 68 int opticalWidth = insets.left + insets.right; 69 int opticalHeight = insets.top + insets.bottom; 70 71 measuredWidth += optical ? opticalWidth : -opticalWidth; 72 measuredHeight += optical ? opticalHeight : -opticalHeight; 73 } 74 mMeasuredWidth = measuredWidth; 75 mMeasuredHeight = measuredHeight; 76 77 mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; 78 }