本文對(duì)應(yīng)項(xiàng)目的碼云地址:https://gitee.com/wanchuanxy/AndroidHeroesTest/tree/master/3/SystemWidget
在現(xiàn)實(shí)生活中,如果我們?nèi)ギ嬕粋€(gè)圖形,就必須知道它的大小和位置。Android系統(tǒng)在繪制View之前也必須對(duì)View進(jìn)行測(cè)量,即告訴系統(tǒng)該畫一個(gè)多大的View。這個(gè)過程在onMeasure()方法中進(jìn)行。
Android系統(tǒng)給我們提供了一個(gè)設(shè)計(jì)短小精悍卻功能強(qiáng)大的類----MeasureSpec類,我們可通過它來測(cè)量View。MeasureSpec是一個(gè)32位的值,其中高2位為測(cè)量的模式,低30位為測(cè)量的大小,在計(jì)算中使用位運(yùn)算的原因是為了提高并優(yōu)化速率。
測(cè)量模式為以下三種。
EXACTLY
即精確值模式,當(dāng)我們將控件的layout_width或layout_height屬性設(shè)定為具體數(shù)值時(shí),比如android:layout_width="100dp",或者指定為match_parent屬性時(shí)(占據(jù)父View的大小),系統(tǒng)使用的是EXACTLY模式。AT_MOST
即最大值模式,當(dāng)控件的layout_width或layout_height屬性設(shè)定為wrap_content時(shí),控件大小一般隨著控件的子控件或內(nèi)容的變化而變化,此時(shí)控件的尺寸只要不超過父控件允許的最大尺寸即可。UNSPECIFIED
這個(gè)屬性比較奇怪——它不指定其大小測(cè)量模式,View想多大就多大,通常在自定義View時(shí)才會(huì)使用。
View類默認(rèn)的onMeasure()方法只支持EXACTLY模式,所以如果在自定義控件的時(shí)候不重寫onMeasure()方法的話,就只能使用EXACTLY模式。控件可以響應(yīng)你指定的具體寬高值或者是match_parent屬性。而如果要讓自定義View支持wrap_content屬性,那就必須重寫onMeasure()方法來指定wrap_content時(shí)的大小。
通過MeasureSpec這一個(gè)類,我們就獲取了View的測(cè)量模式和View想要繪制的大小。有了這些信息,我們就可以控制View最后顯示的大小。
下面來看一個(gè)簡(jiǎn)單的實(shí)例演示如何進(jìn)行View的測(cè)量。首先要重寫onMeasure()方法,該方法如下所示。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
在IDE中按住Ctrl+鼠標(biāo)左鍵點(diǎn)擊 super.onMeasure()查看源代碼。可以發(fā)現(xiàn)系統(tǒng)最終會(huì)調(diào)用setMeasuredDimension(int measuredWidth, int measuredHeight)方法將測(cè)量后的寬高值設(shè)置進(jìn)去,從而完成測(cè)量工作。所以在重寫onMeasure()方法后,最重要最的工作就是把測(cè)量后的寬高值作為參數(shù)傳給setMeasuredDimension()方法。
通過上面的分析,重寫的onMeasure()方法代碼如下所示。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(
measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
在onMeasure()方法中,我們調(diào)用自定義的measureWidth()方法和measureHeight()方法分別對(duì)寬高進(jìn)行重定義,參數(shù)則是寬和高的MeasureSpec對(duì)象,MeasureSpec對(duì)象根據(jù)前面的介紹可以知道它包含了測(cè)量的模式和測(cè)量值的大小。
下面我們就以measureWidth()方法為例,講解如何自定義測(cè)量值。
第一步,從MeasureSpec對(duì)象中提取出具體的測(cè)量模式和大小,代碼如下所示。
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
接下來通過判斷測(cè)量的模式,給出不同的測(cè)量值。
- 當(dāng)specMode為EXACTLY時(shí),直接使用指定的specSize即可;
- 當(dāng)specMode為其他兩種模式時(shí),需要給它一個(gè)默認(rèn)的大小。
- 特別地,如果指定wrap_content屬性,即AT_MOST模式,則需要提取出我們指定的大小與specSize中最小的一個(gè)來作為最后的測(cè)量值,
measureWidth()方法的代碼如下。這段代碼基本可以作為模板代碼。
private int measureWidth(int widthMeasureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(widthMeasureSpec);
int specSize = MeasureSpec.getSize(widthMeasureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
measureHeight()與measureWidth()方法基本一致,不再給出代碼,通過這兩個(gè)方法,我們就完成了對(duì)寬高值得自定義。最后可以在程序中驗(yàn)證以上分析。
- 在布局文件中首先指定確定的寬高值400px,即EXACTLY模式:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.imooc.systemwidget.TeachingView
android:layout_width="400px"
android:layout_height="400px" />
</LinearLayout>
程序效果如下圖所示:

-
當(dāng)指定寬高屬性為match_parent時(shí),運(yùn)行效果如下圖所示:
-
當(dāng)指定寬高屬性為wrap_content時(shí),如果不寫onMeasure()方法,那么系統(tǒng)就不知道該使用默認(rèn)多大尺寸。因此它就會(huì)默認(rèn)填充整個(gè)父布局,所以重寫onMeasure()方法的目的就是為了能夠給View一個(gè)wrap_content屬性下的默認(rèn)大小,其運(yùn)行效果如下圖所示:
布局代碼:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.imooc.systemwidget.TeachingView
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
在onDraw()方法中添加測(cè)試代碼(最下面三行):
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLUE);//繪制背景;將此行注釋,界面將為透明,看不到canvas的大小
//下面三行code乃調(diào)試用
int width = getWidth();
int height = getHeight();
Log.d("xys", "width : " + width + " height : " + height);
}
}
運(yùn)行后可見:

200即我們剛剛自定義的測(cè)量方法中的默認(rèn)值。
可以發(fā)現(xiàn),當(dāng)指定wrap_content屬性時(shí),View就會(huì)獲得一個(gè)默認(rèn)值200px,而不是再填充父布局了。
通過這個(gè)實(shí)例,相信大家對(duì)View的測(cè)量不再陌生了,它并不是什么高深莫測(cè)的東西,它的整個(gè)過程與我們?cè)谏钪芯_繪圖是一樣的。
本文對(duì)應(yīng)View.java 全文:
package com.imooc.systemwidget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
//思路:1.在onMeasure中對(duì)setMeasuredDimension做個(gè)自定義
// 2.繪制; 完; 注意布局xml寫法;
public class TeachingView extends View {
//三個(gè)重載構(gòu)造函數(shù)
public TeachingView(Context context) {
super(context);
}
public TeachingView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TeachingView(Context context, AttributeSet attrs,
int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec,
int heightMeasureSpec) {
// protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)
setMeasuredDimension(
//引用
measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}
//自定義兩個(gè)測(cè)量方法
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
private int measureHeight(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if (specMode == MeasureSpec.EXACTLY) {
result = specSize;
} else {
result = 200;
if (specMode == MeasureSpec.AT_MOST) {
result = Math.min(result, specSize);
}
}
return result;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLUE);//繪制背景;將此行注釋,界面將為透明,看不到canvas的大小
//下面三行code乃調(diào)試用
int width = getWidth();
int height = getHeight();
Log.d("xys", "width : " + width + " height : " + height);
}
}
內(nèi)容參考自Blankj

