Android自定义控件onMeasure、onLayout介绍

2015-07-21· 3998 次浏览
这两个方法的执行顺序是,先执行onMeasure计算大小,然后执行onLayout来布局。 ## onMeasure 先说一下MeasureSpec.AT_MOST、MeasureSpec.EXACTLY、MeasureSpec.UNSPECIFIED; MeasureSpec.makeMeasureSpec(size, mode) MeasureSpec.EXACTLY:父视图希望子视图的大小应该是size中指定的。 MeasureSpec.AT_MOST:子视图的大小最多是size中指定的值,也就是说不建议子视图的大小超过size中给定的值。 MeasureSpec.UNSPECIFIED:我们可以随意指定视图的大小。 在onMeasure中计算子View的宽、高, ### 子View的宽、高为包裹内容 ```java for (int i = 1; i < getChildCount(); i++) { View childView = getChildAt(i); // 设定子view宽、高 childView.measure( MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(LayoutParams.WRAP_CONTENT, MeasureSpec.AT_MOST)); } ``` ### 指定子View的宽、高 ```java for (int i = 1; i < getChildCount(); i++) { View childView = getChildAt(i); // 设定子view宽、高 childView.measure( MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY)); } ``` 其中100可以自己随意修改,单位是px(像素),如果想使用dp单位的话 ```java //转换dip为px public static int convertDIP2PX(Context context, int dip) { float scale = context.getResources().getDisplayMetrics().density; return (int)(dipscale + 0.5f(dip>=0?1:-1)); } ``` ### 不设定子View的宽、高 ```java for (int i = 1; i < getChildCount(); i++) { View childView = getChildAt(i); // 设定子view宽、高 childView.measure( MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); } ``` 也可以这样写 ```java for (int i = 1; i < getChildCount(); i++) { View childView = getChildAt(i); // 设定子view宽、高 childView.measure( MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); } ``` 当然,如果想要让这个自定义ViewGroup的宽高包裹内容的话,只需要获取子view的宽、高来计算,然后调用setMeasuredDimension(int measuredWidth, int measuredHeight)来设置一下就OK了。 ```java //我这里每行只放一个View, @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int parentWidthSize = 0; int paretnHeightSize = 0; for (int i = 1; i < getChildCount(); i++) { View childView = getChildAt(i); // 设定子view宽、高 childView.measure( MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY)); parentWidthSize = childView.getMeasuredWidth(); paretnHeightSize += childView.getMeasuredHeight(); } setMeasuredDimension(parentWidthSize, paretnHeightSize); } ``` ## onLayout 直接上代码 ```java @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { for (int i = 1; i < getChildCount(); i++) { View childView = getChildAt(i); int childWidth = childView.getMeasuredWidth(); int childHeight = childView.getMeasuredHeight(); childView.layout(l, t, r, b); } } ``` 一步一步看 ``` childView.layout(l, t, r, b); ``` 其中的 l是childView在这个自定义Viewgroup中距离左边的距离, t是距离上边的距离, r是自定义Viewgroup中多宽, b是自定义Viewgroup中多高。 可以这样理解,**把这个子View看作一个矩形,那么就可以把l、t看作是矩形左上角的坐标,r、b看作是右下角的坐标,这样比较好理解**。 上面已经在onMeasure中计算过子View的宽高了,这里可以直接用,也就是这样写 ```java @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { for (int i = 1; i < getChildCount(); i++) { View childView = getChildAt(i); //这里获取上面计算过的 子View的宽、高 int childWidth = childView.getMeasuredWidth(); int childHeight = childView.getMeasuredHeight(); childView.layout(0, 0, childWidth , childHeight ); } } ```