关于自定义View,我们一般会重写onDraw方法来实现视图绘制,而很少重写onMeasure方法,但是其实onMeasure也同样重要,尤其是当自定义View是提供给别人用,因为这种情况下View可能会处于各种不同的父容器中,以及设置不同的属性,下面看看某些情况下,重写和不重写onMeasure方法的差别,本文涉及的所有代码都在文章末尾。
首先创建一个自定义View,代码如下
1class CustomView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
2
3 private val paint: Paint = Paint()
4 private val radius:Float
5
6 init {
7 val typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView)
8 radius = typedArray.getDimension(R.styleable.CustomView_radius, 0f)
9 typedArray.recycle()
10 }
11
12 override fun onDraw(canvas: Canvas) {
13 super.onDraw(canvas)
14 paint.color = Color.GREEN
15 canvas.drawCircle(width / 2f, height / 2f, radius, paint)
16 }
17}
功能是可自定义半径,在View中画一个圆,我们想实现根据半径来决定View的大小,XML代码如下:
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 xmlns:tools="http://schemas.android.com/tools"
6 android:layout_width="match_parent"
7 android:layout_height="match_parent"
8 tools:context=".MainActivity">
9
10 <com.example.customview.CustomView
11 android:layout_width="wrap_content"
12 android:layout_height="wrap_content"
13 app:radius="100dp"
14 android:background="@color/black"
15 app:layout_constraintBottom_toBottomOf="parent"
16 app:layout_constraintLeft_toLeftOf="parent"
17 app:layout_constraintRight_toRightOf="parent"
18 app:layout_constraintTop_toTopOf="parent" />
19
20</androidx.constraintlayout.widget.ConstraintLayout>
根据代码预期一个是一个居中带黑色背景的圆形,是实际效果却不是这样,对比图如下。
这是因为在测量宽高的时候默认并没有依据半径来决定,下面看看onMeasure的定义和默认实现。
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
}
默认会调用到父类的onMeasure方法,这里的父类是View,下面是View中onMeasure的实现。
1protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2 setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
3 getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
4}
5
6public static int getDefaultSize(int size, int measureSpec) {
7 int result = size;
8 int specMode = MeasureSpec.getMode(measureSpec);
9 int specSize = MeasureSpec.getSize(measureSpec);
10
11 switch (specMode) {
12 case MeasureSpec.UNSPECIFIED:
13 result = size;
14 break;
15 case MeasureSpec.AT_MOST:
16 case MeasureSpec.EXACTLY:
17 result = specSize;
18 break;
19 }
20 return result;
21}
View里面调用了getDefaultSize,在这个示例中测量模式是MeasureSpec.AT_MOST,所以最后的测量大小是父视图给的最大尺寸,自然也就充满全屏了。为了实现上述的效果,我们可以通过重写onMeasure方法,根据半径来决定测量的大小,重写后的onMeasure实现
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val expectSize = 2 * radius.toInt()
val width = resolveSize(expectSize, widthMeasureSpec)
val height = resolveSize(expectSize, heightMeasureSpec)
setMeasuredDimension(width, height)
}
修改后就实现了期望的效果。
如果想在View中添加padding,通常我们会在XML中添加padding属性,XML代码如下
1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout
3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 xmlns:tools="http://schemas.android.com/tools"
6 android:layout_width="match_parent"
7 android:layout_height="match_parent"
8 tools:context=".MainActivity">
9
10 <com.example.customview.CustomView
11 android:layout_width="wrap_content"
12 android:layout_height="wrap_content"
13 app:radius="100dp"
14 android:padding="10dp"
15 android:background="@color/black"
16 app:layout_constraintBottom_toBottomOf="parent"
17 app:layout_constraintLeft_toLeftOf="parent"
18 app:layout_constraintRight_toRightOf="parent"
19 app:layout_constraintTop_toTopOf="parent" />
20
21</androidx.constraintlayout.widget.ConstraintLayout>
预期黑色背景与圆形之间会有一个10dp的间距,但是运行后发现并没有达到预期,对比图如下
这是因为自定义View的padding在测量的时候需要开发者自己处理,将onMeasure改成如下即可实现预期效果。
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val expectSize = 2 * radius.toInt()
val width = resolveSize(expectSize, widthMeasureSpec) + paddingLeft + paddingRight
val height = resolveSize(expectSize, heightMeasureSpec) + paddingTop + paddingBottom
setMeasuredDimension(width, height)
}
项目完整代码如下
文件 | 大小 | 修改时间 |
---|---|---|
app | 2021年11月04日 | |
build.gradle | 642 B | 2021年11月04日 |
gradle/wrapper | 2021年11月04日 | |
gradle.properties | 1 kB | 2021年11月04日 |
gradlew | 5 kB | 2021年11月04日 |
gradlew.bat | 2 kB | 2021年11月04日 |
local.properties | 434 B | 2021年11月04日 |
settings.gradle | 46 B | 2021年11月04日 |