Android-实例理解View measure过程

这里有两个自定义view,HorizontalScrollViewEx(作用类似水平方向的viewpager),CircleView(简单的画一个圆)。

代码如下:

  1 public class HorizontalScrollViewEx extends ViewGroup {
  2     private static final String TAG = "HorizontalScrollViewEx";
  3 
  4     private int mChildrenSize;
  5     private int mChildWidth;
  6     private int mChildIndex;
  7 
  8     //     分别记录上次滑动的坐标
  9     private int mLastX = 0;
 10     private int mLastY = 0;
 11     // 分别记录上次滑动的坐标(onInterceptTouchEvent)
 12     private int mLastXIntercept = 0;
 13     private int mLastYIntercept = 0;
 14 
 15     private Scroller mScroller;
 16     private VelocityTracker mVelocityTracker;
 17 
 18     public HorizontalScrollViewEx(Context context) {
 19         super(context);
 20         init();
 21     }
 22 
 23     public HorizontalScrollViewEx(Context context, AttributeSet attrs) {
 24         super(context, attrs);
 25         init();
 26     }
 27 
 28     public HorizontalScrollViewEx(Context context, AttributeSet attrs,
 29                                   int defStyle) {
 30         super(context, attrs, defStyle);
 31         init();
 32     }
 33 
 34     private void init() {
 35         mScroller = new Scroller(getContext());
 36         mVelocityTracker = VelocityTracker.obtain();
 37     }
 38 
 39     @Override
 40     public boolean onInterceptTouchEvent(MotionEvent event) {
 41         boolean intercepted = false;
 42         int x = (int) event.getX();
 43         int y = (int) event.getY();
 44 
 45         switch (event.getAction()) {
 46             case MotionEvent.ACTION_DOWN: {
 47                 intercepted = false;
 48                 if (!mScroller.isFinished()) {
 49                     mScroller.abortAnimation();
 50                     intercepted = true;
 51                 }
 52                 break;
 53             }
 54             case MotionEvent.ACTION_MOVE: {
 55                 int deltaX = x - mLastXIntercept;
 56                 int deltaY = y - mLastYIntercept;
 57                 if (Math.abs(deltaX) > Math.abs(deltaY)) {
 58                     intercepted = true;
 59                 } else {
 60                     intercepted = false;
 61                 }
 62                 break;
 63             }
 64             case MotionEvent.ACTION_UP: {
 65                 intercepted = false;
 66                 break;
 67             }
 68             default:
 69                 break;
 70         }
 71 
 72         Log.d(TAG, "intercepted=" + intercepted);
 73         mLastX = x;
 74         mLastY = y;
 75         mLastXIntercept = x;
 76         mLastYIntercept = y;
 77 
 78         return intercepted;
 79     }
 80 
 81     @Override
 82     public boolean onTouchEvent(MotionEvent event) {
 83         mVelocityTracker.addMovement(event);
 84         int x = (int) event.getX();
 85         int y = (int) event.getY();
 86         switch (event.getAction()) {
 87             case MotionEvent.ACTION_DOWN: {
 88                 if (!mScroller.isFinished()) {
 89                     mScroller.abortAnimation();
 90                 }
 91                 break;
 92             }
 93             case MotionEvent.ACTION_MOVE: {
 94                 int deltaX = x - mLastX;
 95                 int deltaY = y - mLastY;
 96                 scrollBy(-deltaX, 0);
 97                 break;
 98             }
 99             case MotionEvent.ACTION_UP: {
100                 int scrollX = getScrollX();
101                 int scrollToChildIndex = scrollX / mChildWidth;
102                 mVelocityTracker.computeCurrentVelocity(1000);
103                 float xVelocity = mVelocityTracker.getXVelocity();
104                 if (Math.abs(xVelocity) >= 50) {
105                     mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
106                 } else {
107                     mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
108                 }
109                 mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
110                 int dx = mChildIndex * mChildWidth - scrollX;
111                 smoothScrollBy(dx, 0);
112                 mVelocityTracker.clear();
113                 break;
114             }
115             default:
116                 break;
117         }
118 
119         mLastX = x;
120         mLastY = y;
121         return true;
122     }
123 
124     @Override
125     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
126         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
127         int measuredWidth = 0;
128         int measuredHeight = 0;
129         final int childCount = getChildCount();
130         Log.e(TAG, "" + MeasureSpec.getSize(widthMeasureSpec));
131         //测量子view的大小
132         measureChildren(widthMeasureSpec, heightMeasureSpec);
133 
134         int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
135         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
136         int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
137         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
138         if (childCount == 0) {
139             setMeasuredDimension(0, 0);
140         } else if (heightSpecMode == MeasureSpec.AT_MOST) {
141             Log.e(TAG, "heightMode:"+heightSpecMode);
142             final View childView = getChildAt(0);
143 
144             //TODO layout_height为wrap_content ==> specMode为AT_MOST,将使用childView.getMeasuredHeight()作为其高度
145 
146             setMeasuredDimension(300, childView.getMeasuredHeight());
147         } else if (widthSpecMode == MeasureSpec.AT_MOST) {
148             Log.e(TAG, "widthMode:"+widthSpecMode);
149             final View childView = getChildAt(0);
150             measuredWidth = childView.getMeasuredWidth() * childCount;
151             setMeasuredDimension(measuredWidth, heightSpaceSize);
152         } else {
153             Log.e(TAG, "heightMode:"+heightSpecMode);
154             Log.e(TAG, "widthMode:"+widthSpecMode);
155             final View childView = getChildAt(0);
156             measuredWidth = childView.getMeasuredWidth() * childCount;
157             measuredHeight = childView.getMeasuredHeight();
158             setMeasuredDimension(measuredWidth, measuredHeight);
159         }
160     }
161 
162     @Override
163     protected void onLayout(boolean changed, int l, int t, int r, int b) {
164         int childLeft = 0;
165         final int childCount = getChildCount();
166         mChildrenSize = childCount;
167 
168         for (int i = 0; i < childCount; i++) {
169             final View childView = getChildAt(i);
170             if (childView.getVisibility() != View.GONE) {
171                 final int childWidth = childView.getMeasuredWidth();
172                 mChildWidth = childWidth;
173                 childView.layout(childLeft, 0, childLeft + childWidth,
174                         childView.getMeasuredHeight());
175                 childLeft += childWidth;
176             }
177         }
178     }
179 
180     private void smoothScrollBy(int dx, int dy) {
181         mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
182         invalidate();
183     }
184 
185     @Override
186     public void computeScroll() {
187         if (mScroller.computeScrollOffset()) {
188             scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
189             postInvalidate();
190         }
191     }
192 
193     @Override
194     protected void onDetachedFromWindow() {
195         mVelocityTracker.recycle();
196         super.onDetachedFromWindow();
197     }
198 }
 1 public class CircleView extends View {
 2 
 3     private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 4     private final int mCustomWidth = 400;
 5     private final int mCustomHeight = 400;
 6 
 7     public CircleView(Context context) {
 8         super(context);
 9 
10         mPaint.setColor(Color.RED);
11     }
12 
13     public CircleView(Context context, AttributeSet attrs) {
14         super(context, attrs);
15 
16         mPaint.setColor(Color.RED);
17     }
18 
19     public CircleView(Context context, AttributeSet attrs, int defStyleAttr) {
20         super(context, attrs, defStyleAttr);
21         mPaint.setColor(Color.RED);
22     }
23 
24     @Override
25     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
26         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
27 
28         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
29         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
30         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
31         int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
32 
33         //宽高都是AT_MOST
34         if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST)
35         {
36             setMeasuredDimension(mCustomWidth, mCustomHeight);
37         }
38         else if (widthSpecMode == MeasureSpec.AT_MOST)
39         {
40             setMeasuredDimension(mCustomWidth, heightSpecSize);
41         }
42         else if (heightSpecMode == MeasureSpec.AT_MOST)
43         {
44             setMeasuredDimension(widthSpecSize, mCustomHeight);
45         }
46 
47     }
48 
49     @Override
50     protected void onDraw(Canvas canvas) {
51         super.onDraw(canvas);
52 
53         int width = getWidth();
54         int height = getHeight();
55 
56         int radius = Math.min(width, height) / 2;
57 
58         canvas.drawCircle(width / 2, height / 2, radius, mPaint);
59     }
60 }

布局文件:activity_main.xml

 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
 2     android:layout_width="match_parent"
 3     android:layout_height="match_parent">
 4 
 5     <com.dhn.customview.HorizontalScrollViewEx
 6         android:layout_width="wrap_content"
 7         android:layout_height="wrap_content">
 8 
 9         <com.dhn.customview.CircleView
10             android:layout_width="100dp"
11             android:layout_height="match_parent" />
12 
13         <TextView
14             android:layout_width="100dp"
15             android:layout_height="match_parent"
16             android:background="#AAff0000"
17             android:text="t1" />
18 
19         <TextView
20             android:layout_width="100dp"
21             android:layout_height="match_parent"
22             android:background="#AAff00"
23             android:text="t1" />
24 
25     </com.dhn.customview.HorizontalScrollViewEx>
26 
27 </RelativeLayout>

问题:

布局文件中父View的高为wrap_content,子View为match_parent。这时它们的高度该如何确定呢?

因为HorizontalScrollViewEx的高设置为wrap_content所以它的高的SpecMode为AT_MOST(它的parent view和自身的LayoutParam在getChildMeasureSpec中计算得出),我们在自定义的onMeasure()方法中将它的高设置为第一个子View的测量高度(第146行),那么第一个子View的childView.getMeasuredHeight()为多少呢。子View的测量在132行measureChildren(widthMeasureSpec, heightMeasureSpec)中进行,进入该方法:

 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行对第一个子View调用measureChild(child, widthMeasureSpec, heightMeasureSpec)

 1     protected void measureChild(View child, int parentWidthMeasureSpec,
 2             int parentHeightMeasureSpec) {
 3         final LayoutParams lp = child.getLayoutParams();
 4 
 5         final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
 6                 mPaddingLeft + mPaddingRight, lp.width);
 7         final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
 8                 mPaddingTop + mPaddingBottom, lp.height);
 9 
10         child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
11     }

这里的getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);返回第一个子View的MeasureSpec,该方法如下:

 1 public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
 2         int specMode = MeasureSpec.getMode(spec);
 3         int specSize = MeasureSpec.getSize(spec);
 4 
 5         int size = Math.max(0, specSize - padding);
 6 
 7         int resultSize = 0;
 8         int resultMode = 0;
 9 
10         switch (specMode) {
11         // Parent has imposed an exact size on us
12         case MeasureSpec.EXACTLY:
13             if (childDimension >= 0) {
14                 resultSize = childDimension;
15                 resultMode = MeasureSpec.EXACTLY;
16             } else if (childDimension == LayoutParams.MATCH_PARENT) {
17                 // Child wants to be our size. So be it.
18                 resultSize = size;
19                 resultMode = MeasureSpec.EXACTLY;
20             } else if (childDimension == LayoutParams.WRAP_CONTENT) {
21                 // Child wants to determine its own size. It can't be
22                 // bigger than us.
23                 resultSize = size;
24                 resultMode = MeasureSpec.AT_MOST;
25             }
26             break;
27 
28         // Parent has imposed a maximum size on us
29         case MeasureSpec.AT_MOST:
30             if (childDimension >= 0) {
31                 // Child wants a specific size... so be it
32                 resultSize = childDimension;
33                 resultMode = MeasureSpec.EXACTLY;
34             } else if (childDimension == LayoutParams.MATCH_PARENT) {
35                 // Child wants to be our size, but our size is not fixed.
36                 // Constrain child to not be bigger than us.
37                 resultSize = size;
38                 resultMode = MeasureSpec.AT_MOST;
39             } else if (childDimension == LayoutParams.WRAP_CONTENT) {
40                 // Child wants to determine its own size. It can't be
41                 // bigger than us.
42                 resultSize = size;
43                 resultMode = MeasureSpec.AT_MOST;
44             }
45             break;
46 
47         // Parent asked to see how big we want to be
48         case MeasureSpec.UNSPECIFIED:
49             if (childDimension >= 0) {
50                 // Child wants a specific size... let him have it
51                 resultSize = childDimension;
52                 resultMode = MeasureSpec.EXACTLY;
53             } else if (childDimension == LayoutParams.MATCH_PARENT) {
54                 // Child wants to be our size... find out how big it should
55                 // be
56                 resultSize = 0;
57                 resultMode = MeasureSpec.UNSPECIFIED;
58             } else if (childDimension == LayoutParams.WRAP_CONTENT) {
59                 // Child wants to determine its own size.... find out how
60                 // big it should be
61                 resultSize = 0;
62                 resultMode = MeasureSpec.UNSPECIFIED;
63             }
64             break;
65         }
66         return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
67     }

子View的MeasureSpec由父View的MeasureSpec和自身的LayoutParams 共同决定。这里父view高的MeasureSpec.Mode为AT_MOST,子View高的LayoutParams 为math_parent所以子View高的MeasureSpec也为AT_MOST。

接下来第10行调用第一个子View的measure()方法:

 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         final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
16         final boolean isExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
17                 MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
18         final boolean matchingSize = isExactly &&
19                 getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) &&
20                 getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
21         if (forceLayout || !matchingSize &&
22                 (widthMeasureSpec != mOldWidthMeasureSpec ||
23                         heightMeasureSpec != mOldHeightMeasureSpec)) {
24 
25             // first clears the measured dimension flag
26             mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
27 
28             resolveRtlPropertiesIfNeeded();
29 
30             int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
31             if (cacheIndex < 0 || sIgnoreMeasureCache) {
32                 // measure ourselves, this should set the measured dimension flag back
33                 onMeasure(widthMeasureSpec, heightMeasureSpec);
34                 mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
35             } else {
36                 long value = mMeasureCache.valueAt(cacheIndex);
37                 // Casting a long to int drops the high 32 bits, no mask needed
38                 setMeasuredDimensionRaw((int) (value >> 32), (int) value);
39                 mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
40             }
41 
42             // flag not set, setMeasuredDimension() was not invoked, we raise
43             // an exception to warn the developer
44             if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
45                 throw new IllegalStateException("onMeasure() did not set the"
46                         + " measured dimension by calling"
47                         + " setMeasuredDimension()");
48             }
49 
50             mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
51         }
52 
53         mOldWidthMeasureSpec = widthMeasureSpec;
54         mOldHeightMeasureSpec = heightMeasureSpec;
55 
56         mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
57                 (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension
58     }

第33行调用onMeasure(widthMeasureSpec, heightMeasureSpec);对于CircleView我们自定义了该方法:

 1     @Override
 2     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 3         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
 4 
 5         int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
 6         int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
 7         int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
 8         int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
 9 
10         //宽高都是AT_MOST
11         if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST)
12         {
13             setMeasuredDimension(mCustomWidth, mCustomHeight);
14         }
15         else if (widthSpecMode == MeasureSpec.AT_MOST)
16         {
17             setMeasuredDimension(mCustomWidth, heightSpecSize);
18         }
19         else if (heightSpecMode == MeasureSpec.AT_MOST)
20         {
21             setMeasuredDimension(widthSpecSize, mCustomHeight);
22         }
23 
24     }

在这里为高设置了我们自定义的值mCustomHeight=400,最终circleView的测量高度为400。

这样HorizontalScrollViewEx的高度也就确定为400。

结论:

自定义Veiw是需要在onMeasure方法中处理AT_MOST这种情况。像TextView的onMeasrue实现中,都对AT_MOST进行了处理。这样当出现parent view为wrap_content,child view为math_parent时才可以确定大小。

原文地址:https://www.cnblogs.com/gatsbydhn/p/5158951.html