标签:
这里有两个自定义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, "width:" + 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时才可以确定大小。
标签:
原文地址:http://www.cnblogs.com/gatsbydhn/p/5158951.html