Android-实例理解View measure过程
2016-01-25 22:39
661 查看
这里有两个自定义view,HorizontalScrollViewEx(作用类似水平方向的viewpager),CircleView(简单的画一个圆)。
代码如下:
布局文件:activity_main.xml
问题:
布局文件中父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)中进行,进入该方法:
第7行对第一个子View调用measureChild(child, widthMeasureSpec, heightMeasureSpec)
这里的getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);返回第一个子View的MeasureSpec,该方法如下:
子View的MeasureSpec由父View的MeasureSpec和自身的LayoutParams 共同决定。这里父view高的MeasureSpec.Mode为AT_MOST,子View高的LayoutParams 为math_parent所以子View高的MeasureSpec也为AT_MOST。
接下来第10行调用第一个子View的measure()方法:
第33行调用onMeasure(widthMeasureSpec, heightMeasureSpec);对于CircleView我们自定义了该方法:
在这里为高设置了我们自定义的值mCustomHeight=400,最终circleView的测量高度为400。
这样HorizontalScrollViewEx的高度也就确定为400。
结论:
自定义Veiw是需要在onMeasure方法中处理AT_MOST这种情况。像TextView的onMeasrue实现中,都对AT_MOST进行了处理。这样当出现parent view为wrap_content,child view为math_parent时才可以确定大小。
代码如下:
public class HorizontalScrollViewEx extends ViewGroup { private static final String TAG = "HorizontalScrollViewEx"; private int mChildrenSize; private int mChildWidth; private int mChildIndex; // 分别记录上次滑动的坐标 private int mLastX = 0; private int mLastY = 0; // 分别记录上次滑动的坐标(onInterceptTouchEvent) private int mLastXIntercept = 0; private int mLastYIntercept = 0; private Scroller mScroller; private VelocityTracker mVelocityTracker; public HorizontalScrollViewEx(Context context) { super(context); init(); } public HorizontalScrollViewEx(Context context, AttributeSet attrs) { super(context, attrs); init(); } public HorizontalScrollViewEx(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); init(); } private void init() { mScroller = new Scroller(getContext()); mVelocityTracker = VelocityTracker.obtain(); } @Override public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { intercepted = false; if (!mScroller.isFinished()) { mScroller.abortAnimation(); intercepted = true; } break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; if (Math.abs(deltaX) > Math.abs(deltaY)) { intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } Log.d(TAG, "intercepted=" + intercepted); mLastX = x; mLastY = y; mLastXIntercept = x; mLastYIntercept = y; return intercepted; } @Override public boolean onTouchEvent(MotionEvent event) { mVelocityTracker.addMovement(event); int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: { if (!mScroller.isFinished()) { mScroller.abortAnimation(); } break; } case MotionEvent.ACTION_MOVE: { int deltaX = x - mLastX; int deltaY = y - mLastY; scrollBy(-deltaX, 0); break; } case MotionEvent.ACTION_UP: { int scrollX = getScrollX(); int scrollToChildIndex = scrollX / mChildWidth; mVelocityTracker.computeCurrentVelocity(1000); float xVelocity = mVelocityTracker.getXVelocity(); if (Math.abs(xVelocity) >= 50) { mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1; } else { mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth; } mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1)); int dx = mChildIndex * mChildWidth - scrollX; smoothScrollBy(dx, 0); mVelocityTracker.clear(); break; } default: break; } mLastX = x; mLastY = y; return true; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int measuredWidth = 0; int measuredHeight = 0; final int childCount = getChildCount(); Log.e(TAG, "width:" + MeasureSpec.getSize(widthMeasureSpec)); //测量子view的大小 measureChildren(widthMeasureSpec, heightMeasureSpec); int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); if (childCount == 0) { setMeasuredDimension(0, 0); } else if (heightSpecMode == MeasureSpec.AT_MOST) { Log.e(TAG, "heightMode:"+heightSpecMode); final View childView = getChildAt(0); //TODO layout_height为wrap_content ==> specMode为AT_MOST,将使用childView.getMeasuredHeight()作为其高度 setMeasuredDimension(300, childView.getMeasuredHeight()); } else if (widthSpecMode == MeasureSpec.AT_MOST) { Log.e(TAG, "widthMode:"+widthSpecMode); final View childView = getChildAt(0); measuredWidth = childView.getMeasuredWidth() * childCount; setMeasuredDimension(measuredWidth, heightSpaceSize); } else { Log.e(TAG, "heightMode:"+heightSpecMode); Log.e(TAG, "widthMode:"+widthSpecMode); final View childView = getChildAt(0); measuredWidth = childView.getMeasuredWidth() * childCount; measuredHeight = childView.getMeasuredHeight(); setMeasuredDimension(measuredWidth, measuredHeight); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int childLeft = 0; final int childCount = getChildCount(); mChildrenSize = childCount; for (int i = 0; i < childCount; i++) { final View childView = getChildAt(i); if (childView.getVisibility() != View.GONE) { final int childWidth = childView.getMeasuredWidth(); mChildWidth = childWidth; childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight()); childLeft += childWidth; } } } private void smoothScrollBy(int dx, int dy) { mScroller.startScroll(getScrollX(), 0, dx, 0, 500); invalidate(); } @Override public void computeScroll() { if (mScroller.computeScrollOffset()) { scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); postInvalidate(); } } @Override protected void onDetachedFromWindow() { mVelocityTracker.recycle(); super.onDetachedFromWindow(); } }
public class CircleView extends View { private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); private final int mCustomWidth = 400; private final int mCustomHeight = 400; public CircleView(Context context) { super(context); mPaint.setColor(Color.RED); } public CircleView(Context context, AttributeSet attrs) { super(context, attrs); mPaint.setColor(Color.RED); } public CircleView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); mPaint.setColor(Color.RED); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); //宽高都是AT_MOST if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mCustomWidth, mCustomHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mCustomWidth, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, mCustomHeight); } } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); int width = getWidth(); int height = getHeight(); int radius = Math.min(width, height) / 2; canvas.drawCircle(width / 2, height / 2, radius, mPaint); } }
布局文件:activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.dhn.customview.HorizontalScrollViewEx android:layout_width="wrap_content" android:layout_height="wrap_content"> <com.dhn.customview.CircleView android:layout_width="100dp" android:layout_height="match_parent" /> <TextView android:layout_width="100dp" android:layout_height="match_parent" android:background="#AAff0000" android:text="t1" /> <TextView android:layout_width="100dp" android:layout_height="match_parent" android:background="#AAff00" android:text="t1" /> </com.dhn.customview.HorizontalScrollViewEx> </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)中进行,进入该方法:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
第7行对第一个子View调用measureChild(child, widthMeasureSpec, heightMeasureSpec)
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
这里的getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);返回第一个子View的MeasureSpec,该方法如下:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding); int resultSize = 0; int resultMode = 0; switch (specMode) { // Parent has imposed an exact size on us case MeasureSpec.EXACTLY: if (childDimension >= 0) { resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size. So be it. resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent has imposed a maximum size on us case MeasureSpec.AT_MOST: if (childDimension >= 0) { // Child wants a specific size... so be it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size, but our size is not fixed. // Constrain child to not be bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size. It can't be // bigger than us. resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // Parent asked to see how big we want to be case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) { // Child wants a specific size... let him have it resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // Child wants to be our size... find out how big it should // be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) { // Child wants to determine its own size.... find out how // big it should be resultSize = 0; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
子View的MeasureSpec由父View的MeasureSpec和自身的LayoutParams 共同决定。这里父view高的MeasureSpec.Mode为AT_MOST,子View高的LayoutParams 为math_parent所以子View高的MeasureSpec也为AT_MOST。
接下来第10行调用第一个子View的measure()方法:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int oWidth = insets.left + insets.right; int oHeight = insets.top + insets.bottom; widthMeasureSpec = MeasureSpec.adjust(widthMeasureSpec, optical ? -oWidth : oWidth); heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight); } // Suppress sign extension for the low bytes long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL; if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2); final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; final boolean isExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY; final boolean matchingSize = isExactly && getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec) && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec); if (forceLayout || !matchingSize && (widthMeasureSpec != mOldWidthMeasureSpec || heightMeasureSpec != mOldHeightMeasureSpec)) { // first clears the measured dimension flag mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET; resolveRtlPropertiesIfNeeded(); int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key); if (cacheIndex < 0 || sIgnoreMeasureCache) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } else { long value = mMeasureCache.valueAt(cacheIndex); // Casting a long to int drops the high 32 bits, no mask needed setMeasuredDimensionRaw((int) (value >> 32), (int) value); mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT; } // flag not set, setMeasuredDimension() was not invoked, we raise // an exception to warn the developer if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) { throw new IllegalStateException("onMeasure() did not set the" + " measured dimension by calling" + " setMeasuredDimension()"); } mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; } mOldWidthMeasureSpec = widthMeasureSpec; mOldHeightMeasureSpec = heightMeasureSpec; mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 | (long) mMeasuredHeight & 0xffffffffL); // suppress sign extension }
第33行调用onMeasure(widthMeasureSpec, heightMeasureSpec);对于CircleView我们自定义了该方法:
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); //宽高都是AT_MOST if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mCustomWidth, mCustomHeight); } else if (widthSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(mCustomWidth, heightSpecSize); } else if (heightSpecMode == MeasureSpec.AT_MOST) { setMeasuredDimension(widthSpecSize, mCustomHeight); } }
在这里为高设置了我们自定义的值mCustomHeight=400,最终circleView的测量高度为400。
这样HorizontalScrollViewEx的高度也就确定为400。
结论:
自定义Veiw是需要在onMeasure方法中处理AT_MOST这种情况。像TextView的onMeasrue实现中,都对AT_MOST进行了处理。这样当出现parent view为wrap_content,child view为math_parent时才可以确定大小。
相关文章推荐
- Android使用Hessian远程调用WebService服务端
- Android的DiskLruCache硬盘缓存技术
- android 打电话权限问题报错 java.lang.SecurityException android studio 发短信
- Android 切换日夜间模式
- Unable to resolve target 'android-14' 解决方法
- Mac下搭建android开发环境
- 使用WakeLock使Android应用程序保持后台唤醒
- Android自定义ListView的Item无法响应OnItemClick的解决办法(转)
- 如何让Android自适应不同分辨率或不同屏幕大小的layout布局
- androidStudio创建layout-large文件
- Android Studio多渠道打包和代码混淆教程
- android 百度定位API使用教程
- Android条码扫描
- android soundpool 參数说明
- Android 上滑上拉菜单SlidingDrawer 不全屏显示的方法
- Android 上滑上拉菜单SlidingDrawer 不全屏显示的方法
- Android studio 如何让包有层次显示
- Android TV Input Framework(TIF)--2 构建TV input list
- Android的文本和输入---拼写检查器
- android单元测试