自定义View在Android开发中是个非常重要的模块,我们一直在使用
android:layout_width="match_parent"
android:layout_height="wrap_content"
这样的属性设置,但是你知道这些属性是怎么执行的吗?不同的设置是如何产生不同的效果?
下面我从源码分析下自定义View的第一步,View的测量,这是你看到的所有视图都要进行的一步,因为这就像我们现实当中的画图一样,你要先确定纸的大小,然后在画,自定义View看似复杂,整体上就2步,测量和绘制。
这里不按照国际惯例,先上图在分析上代码的过程,个人认为这样不太明白。
我们先看自定义View的代码:
/**
* 作者:dongcai on 2016/8/18 10:24
* 邮箱:lidongcaiocc@163.com
*/
public class MyView extends View {
//构造至少实现一个
public MyView(Context context) {
super(context);
}
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//设置最终的占位大小
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
//通过模式设置尺寸
private int measureHeight(int heightMeasureSpec) {
int finalSize = 0;
int mode = MeasureSpec.getMode(heightMeasureSpec);
int size = MeasureSpec.getSize(heightMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
finalSize = size;
} else {
finalSize = 350;
if (mode == MeasureSpec.AT_MOST) {
finalSize = Math.min(finalSize, size);
}
}
return finalSize;
}
//通过模式设置尺寸
private int measureWidth(int widthMeasureSpec) {
int finalSize = 0;
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
if (mode == MeasureSpec.EXACTLY) {
finalSize = size;
} else {
finalSize = 250;
if (mode == MeasureSpec.AT_MOST) {
finalSize = Math.min(finalSize, size);
}
}
return finalSize;
}
}
讲解:
继承View类,然后实现构造方法,至少实现一个,这里我实现两个:第一个表示可以在代码中实例这个View使用,第二个表示支持xml,方便使用。
然后重写onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法,该方法传进来两个值,这两个值是从这来
android:layout_width="wrap_content"
android:layout_height="wrap_content"
代码的话就从这
LinearLayout.LayoutParams.MATCH_PARENT;
LinearLayout.LayoutParams.WRAP_CONTENT;
通过这两个值确定自定义View的大小。
查看源码,这里调用的是setMeasuredDimension()方法,所以,直接调用这个方法
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
setMeasuredDimension()需要两个参数,宽和高,如何来确定呢?为了方便查看,分别写了那两个方法。
注释是通过模式设置尺寸,模式是什么?
Android系统给我们提供了一个设计短小精悍却功能强大的类—MeasureSpec类,通过它来帮助我们测量View。MeasureSpec类是一个32位的int值,,其中高2位为测量的模式,低30位为测量的大小。
测量的模式分3种,源码说明:
/**
* A MeasureSpec encapsulates the layout requirements passed from parent to child.
* Each MeasureSpec represents a requirement for either the width or the height.
* A MeasureSpec is comprised of a size and a mode. There are three possible
* modes:
* <dl>
* <dt>UNSPECIFIED</dt>没有约束,任何大小
* <dd>
* The parent has not imposed any constraint on the child. It can be whatever size
* it wants.
* </dd>
* <dt>EXACTLY</dt>默认模式,设置具体大小或者最大match_parent
* <dd>
* The parent has determined an exact size for the child. The child is going to be
* given those bounds regardless of how big it wants to be.
* </dd>
*
* <dt>AT_MOST</dt> 自由大小,只要不超过父控件的大小wrap_content
* <dd>
* The child can be as large as it wants up to the specified size.
* </dd>
* </dl>
所以,我们需要获取模式,然后根据不同的模式设置宽和高的范围:
先获取到模式和最大尺寸,这里获取到的size为设备最大尺寸
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
通过模式判断
if (mode == MeasureSpec.EXACTLY) {
如果是match_parent或具体值,那么直接赋值
finalSize = size;
} else {
否则,给默认值
finalSize = 250;
如果是wrap_content,就给最小值
if (mode == MeasureSpec.AT_MOST) {
finalSize = Math.min(finalSize, size);
}
}
分别运行效果如下:
<com.moliying.myplaystore.ui.MyView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#0f0"
/>
非常简单,只有背景色,这是默认显示的大小,如果在自定义View中改变默认值大小,这里也会改变。
<com.moliying.myplaystore.ui.MyView
android:layout_width="100dp"
android:layout_height="100dp"
android:background="#0f0"
/>
这是具体值,精确模式。
<com.moliying.myplaystore.ui.MyView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#0f0"
/>
这是最大模式,全屏显示
<com.moliying.myplaystore.ui.MyView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#0f0"
/>
这样显示就是宽度最大,高度默认值。
以上是个人对View测量的理解,如有错误,望指出,欢迎大家交流。