好记性不如烂笔头
1、View里面不同的构造方法
View里面有四种不同的构造方法,分别是:
View(Context context)
View(Context context, AttributeSet attrs)
View(Context context, AttributeSet attrs, int defStyleAttr)
View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)
那我们都是什么时候用到这些构造方法呢?
- View(Context context)是当代码创建视图的时候使用的构造函数,通过参数中的context我们可以访问当前主题、资源等。
例如在Activity的setContentView中可以使用new View(this)来将自己的控件显示出来。 - View(Context context, AttributeSet attrs)用于layout文件实例化,会把XML内的参数通过AttributeSet带入到View内。就是当写在xml中的控件被实例化的时候会调用该方法。可以通过attrs获取属性。
2、重写onMeasure()方法
那我们为啥要重写onMeasure()呢?
<com.example.lifecircletest.MyView
android:id="@+id/my_view"
android:layout_height="100dp"
android:layout_width="wrap_content"
android:background="#ff0000"/>
MyView是我们自己继承view的一个类,现在还没有重写onMeasure,此时注意 android:layout_width=“wrap_content” 为wrap_content,就是将大小设置为刚好包裹控件内的内容,运行一下,可以看到。
高度的100dp倒是没啥问题,但是wrap_content咋变成match_parent呢?去看看View里面的onMeasure()方法吧。
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
可以看到在getDedaultSize中的AT_MOST情况下没有执行,会继续执行case MeasureSpec.EXACTLY:的内容,返回了specSize的值,那我现在有个疑问了,是谁调用了onMeasure()呢?一般子View的实际宽高是由其父视图和本身决定的,父视图是有义务测量其每一个子View,也就是说子View的measure方法其实是其上层的ViewGroup调用的,详细可以去看measureChild中的getChildMeasureSpec,总结一下就是子View的大小是由其父视图和本身决定的。
下面说一下上文中specMode的三个值是什么意思,我们可以通过MeasureSpec.getMode来获得widthMeasureSpec, heightMeasureSpec中低2位的模式,通过MeasureSpec.getSize获取后30位的值表示大小。模式有三种:
- MeasureSpec.UNSPECIFIED 是当没有指定大小时使用一个默认的大小
- MeasureSpec.AT_MOST 最大尺寸,控件的宽高为WRAP_CONTENT,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸
- MeasureSpec.EXACTLY 精准的尺寸,尺寸的值是多少,那么这个组件的高和宽就是多少。
知道这些后我们就可以重写onMeasure了。如果要实现wrap_content的话需要在onMeasure中进行实现。onMeasure中用setMeasuredDimension来设置大小。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getSize(100,widthMeasureSpec),getSize(100,heightMeasureSpec)); //设置大小的长宽
}
private int getSize(int measureSpec, int defaultSize){ //这个方法可以得到一个size或者一个自己设定的默认size
int mSize = defaultSize;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode){
case MeasureSpec.UNSPECIFIED:
mSize = defaultSize;
break;
case MeasureSpec.AT_MOST:
mSize = size;
break;
case MeasureSpec.EXACTLY:
mSize = size;
break;
}
return mSize;
}
3、重写onDraw()
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
paint.setColor(Color.BLUE);
paint.setAntiAlias(true);
paint.setTextSize(50);
canvas.drawText("hi man" , 100, 100 , paint); //绘制一个文字
}
利用onDraw中的canvas就可以画画了。还有其他很多drawXXX的方法的。
3、获取自定义的属性
3.1设置属性
我们可以在res/values/
创建一个attrs.xml的文件来保存我们的属性。
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="MyView">
<attr name="color" format="color"/>
</declare-styleable>
</resources>
declare-styleable中的name就是我们自定义的view,这里的名字是可以随便取的,但是最好和我们的类名字保持一致,在attr中可以申明一个属性的名字和属性所取的格式
有如上几种格式,也可以用<attr name="color" format="color||integer"/>
来让他有多种格式。设置好属性后就可以在我们的自定义view中来使用了。
首先在布局标签里加入xmlns:hb="http://schemas.android.com/apk/res-auto"
hb这个命名空间是可以自己设置的。
接下来就可以在自定义的view中使用了,hb:color="@color/colorPrimary"
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:hb="http://schemas.android.com/apk/res-auto"
android:orientation="horizontal"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.lifecircletest.MyView
android:id="@+id/my_view"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:background="@drawable/click"
hb:color="@color/colorPrimary"/>
</LinearLayout>
3.2取出属性
现在我们有了自己的自定义的属性了,像上面的例子,我们就可以不用android:background来设置我们的背景颜色了,那我们接下来该怎么做呢?
还记得第二个构造方法中有一个AttributeSet attrs的参数吗,我们的属性可以从这里面取出来。
private int mColor;
public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
paint = new Paint();
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.MyView);
mColor = array.getColor(R.styleable.MyView_color,Color.BLUE); //第二个参数是默认的颜色。就是没设置就用这个颜色。
array.recycle(); //最后记得recycle它
}
现在我们的onDraw中的Paint的颜色就可以直接使用上面获取到的mColor了。效果还是和之前一样,但是字体颜色我们可以在xml文件中设置了,如果没设置,就默认是蓝色。
4、onLayout
onlayout的重写在自定义viewGroup可以用到,可以控制child view 的布局。