自定义View关键点:onMeasure()、onDraw()
该函数目的是测量View宽高属性,虽然在xml中制定了View宽高,但当设置为wrap_content
或match_parent
时,此处获取宽高就需要由onMeasure()处理。
举例:
实现一个宽采用match_parent
,而高为100dp的正方形。
在不重写方法情况下,上述长宽通常是不同的,所以就需要在测量函数中调整长宽长度。
private int getMySize(int defaultSize, int measureSpec) {
int mySize = defaultSize;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (mode) {
case MeasureSpec.UNSPECIFIED: {//如果没有指定大小,就设置为默认大小
mySize = defaultSize;
break;
}
case MeasureSpec.AT_MOST: {//如果测量模式是最大取值为size
//我们将大小取最大值,你也可以取其他值
mySize = size;
break;
}
case MeasureSpec.EXACTLY: {//如果是固定的大小,那就不要去改变它
mySize = size;
break;
}
}
return mySize;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = getMySize(100, widthMeasureSpec);
int height = getMySize(100, heightMeasureSpec);
//进行正方形长宽判断设置
if (width < height) {
height = width;
} else {
width = height;
}
setMeasuredDimension(width, height);
}
布局:
<com.hc.studyview.MyView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#ff0000" />
onMeasure(int widthMeasureSpec, int heightMeasureSpec)
其中两个参数分别包含了宽度+测量模式,高度+测量模式
存放方式:一个int数据占32bit,前两个bit用于存放测量模式(只有三个模式,2bit足够存放),后30bit存放尺寸数据
其内容用内置类MeasureSpec可以直接获取:
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
测量模式:
match_parent
对应EXACTLY:当前尺寸即View应该用的尺寸(即父View剩余空间尺寸)
wrap_content
对应AT_MOST:当前尺寸是View可以取的最大尺寸
用户定的尺寸
对应EXACTLY:指定大小,不用再干涉
还有一个测量模式UNSPECIFIED:对当前View没有限制
上述控件需要放在LinearLayout中,不然setMeasuredDimension(width, height);
将无效,而此句是确定长宽的重要部分
在上述正方形View绘画一个圆形:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int r = getMeasuredWidth() / 2;
int x = r;
int y = r;
Paint paint = new Paint();
paint.setColor(Color.BLUE);
canvas.drawCircle(x, y, r, paint);
}
注:canvas.drawCircle(x, y, r, paint);
这一步前两个x,y是相对于当前View的位置,有文章内采用int centerX = getLeft() + r;
这样获取出来的圆心位置是相对于父View的布局,即当View在左上角,显示正常(起始点就在父View0,0处),而换位置后,球就会消失,即小球不会跟着View移动
可以通过自定义布局属性去在布局里给控件xml文件添加字段。
styles.xml
该文件定义具体字段
res下values下建立该文件
<resources>
<declare-styleable name="MyView">
<attr name="default_size" format="dimension" />
declare-styleable>
resources>
此时在布局文件中,控件就可以添加对应default_size属性:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:proper="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.view.MyView
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#ff0000"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
proper:default_size="100dp"/>
LinearLayout>
注意头段xmlns:proper用于将自定义属性与其余自带分别出来,即在控件中直接proper:default_size="100dp"
即可
通过上述设置,就可以在View中依据添加的字段获取其值:
public class MyView extends View {
private int defaultSize;
public MyView(Context context) {
super(context);
}
public MyView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView);
defaultSize = a.getDimensionPixelSize(R.styleable.MyView_default_size, 100);
a.recycle();
}
解析:
context.obtainStyledAttributes(attrs, R.styleable.MyView);
这里第二个参数就是在styles.xml里设置的属性集合名
defaultSize = a.getDimensionPixelSize(R.styleable.MyView_default_size, 100);
经过上述获得TypedArray后,即可通过字段获取在布局文件中设置好的值(第二个参数是获取失败后的默认值)