目录
视图构建过程(二)
之前介绍过视图的四个构造方法以及用法,这是自定义控件的第一步,接下来要介绍视图的测量方法。
视图的测量方法
自定义控件的第二步是测量尺寸,需要重写onMeasure方法。控件的宽高在xml文件中分别由layout_width属性和layout_height属性规定。它们有3种赋值方式,match_parent、wrap_content与具体的数值。第一种与第三种的值获取比较简单,要么取上级视图的数值,要么取具体数值。比较难的是第二种,它需要知道视图自身的尺寸。Android提供了相关的测量方法,支持在不同情况下测量尺寸。需要测量的实体主要有三种,分别为文本尺寸。图形尺寸以及布局尺寸。
1.文本尺寸测量
文本尺寸分为文本的宽度和高度,需要根据文本的大小分别计算。文本宽度可以使用Paint类的measureText方法测量,示例代码如下。
java
Paint paint = new Paint();
paint.setTextSize(20);//设置文本的大小,参数单位为sp
paint.measureText("MeasureText");//设置测量文本,返回值单位为px
文本高度的计算较为复杂,计算高度用到FontMetrics类,该类提供了五个与高度相关的属性,如下表所示。
|---------|-------------|
| 距离属性 | 说明 |
| top | 行的顶部与基线的距离 |
| ascent | 字符的顶部与基线的距离 |
| descent | 字符的底部与基线的距离 |
| bottom | 行的底部与基线的距离 |
| leading | 行间距 |
示例代码如下。
XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="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=".CustomWidget.MeasureTextActivity"
android:orientation="vertical">
<TextView
android:id="@+id/textView_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/app_name"
android:textSize="20sp"/>
<TextView
android:id="@+id/textView_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
部分Java代码如下。
java
public class MeasureTextActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_measure_text);
TextView textView_1, textView_2;
textView_1 = findViewById(R.id.textView_1);
textView_2 = findViewById(R.id.textView_2);
float width = getWidth(textView_1.getText().toString(), textView_1.getTextSize());
float height = getHeight(textView_1.getText().toString(), textView_1.getTextSize());
String string = String.format("上面文本的大小为%.0fpx,宽度为%.0fpx,高度为%.0fpx", textView_1.getTextSize(), width, height);
textView_2.setText(string);
}
public float getWidth(String text, float textSize) {
if (TextUtils.isEmpty(text)) {
return 0;
}
Paint paint = new Paint();
paint.setTextSize(textSize);
return paint.measureText(text);
}
public float getHeight(String text, float textSize) {
if (TextUtils.isEmpty(text)) {
return 0;
}
Paint paint = new Paint();
paint.setTextSize(textSize);
paint.measureText(text);
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
return fontMetrics.descent - fontMetrics.ascent;//返回值单位为px
}
}
效果图如下。

2.图形尺寸测量
图形尺寸测量较为简单,因为Android提供了现成的方法获取宽高。如果图形格式为Bitmap则调用getWidth方法或者getHeight方法获取宽高。若是Drawable格式就通过方法getIntrinsicWidth以及方法getIntrinsicHeight获取宽高。
3.布局尺寸测量
布局尺寸测量不同于上述两种尺寸,布局中可能有其他视图或者布局存在,而且还有padding与margin。逐个测量布局的内部控件是不现实的。View类提供了一种测量整体布局的思路,对应layout_width与layout_height的三种赋值方式,Android的视图基类同样提供了三种测量方式,具体说明如下表所示。
|-------------|--------------|------|
| 测量模式 | 宽高赋值方式 | 说明 |
| AT_MOST | MATCH_PARENT | 达到最大 |
| UNSPECIFIED | WRAP_CONTENT | 自适应 |
| EXACTLY | 具体dp值 | 精确尺寸 |
围绕这三种方式衍生了相关的度量方法,如ViewGroup类的getChildMeasureSpec方法,用于获取下级视图的测量规格、MeasureSpec类的makeMeasureSpec方法,根据指定参数指定测量规格、View类的measure方法,按照测量规格进行测量操作等。下面以线性布局为例,示例代码如下。
java
public float getLayoutHeight(View view){
LinearLayout llayout = (LinearLayout) view;
// 获得线性布局的布局参数
ViewGroup.LayoutParams params = llayout.getLayoutParams();
if (params == null) {
params = new ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
// 获得布局参数里面的宽度规格
int wdSpec = ViewGroup.getChildMeasureSpec(0, 0, params.width);
int htSpec;
if (params.height > 0) { // 高度大于0,说明这是明确的dp数值
// 按照精确数值的情况计算高度规格
htSpec = View.MeasureSpec.makeMeasureSpec(params.height, View.MeasureSpec.EXACTLY);
} else { // MATCH_PARENT=-1,WRAP_CONTENT=-2,所以二者都进入该分支
// 按照不确定的情况计算高度规则
htSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
}
llayout.measure(wdSpec, htSpec); // 重新丈量线性布局的宽高
// 获得并返回线性布局丈量之后的高度。调用getMeasuredWidth方法可获得宽度
return llayout.getMeasuredHeight();
}