3.2 自定義控件基礎(chǔ) 之 View的測(cè)量

本文對(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 那么在繪制之前,我們還需要思考一下,系統(tǒng)是如何繪制出這些View的,相信大家曾經(jīng)玩過這樣一個(gè)游戲:一個(gè)人蒙...
    AndYMJ閱讀 367評(píng)論 2 3
  • 引言 本章內(nèi)容較多,先養(yǎng)養(yǎng)眼 大家知道,自定義View有三個(gè)重要的步驟:measure,layout,draw。而...
    SnowDragonYY閱讀 2,076評(píng)論 1 11
  • 【Android 自定義View】 [TOC] 自定義View基礎(chǔ) 接觸到一個(gè)類,你不太了解他,如果貿(mào)然翻閱源碼只...
    Rtia閱讀 4,126評(píng)論 1 14
  • View的繪制和事件處理是兩個(gè)重要的主題,上一篇《圖解 Android事件分發(fā)機(jī)制》已經(jīng)把事件的分發(fā)機(jī)制講得比較詳...
    Kelin閱讀 121,400評(píng)論 100 846
  • Android開發(fā)藝術(shù)探索筆記 SpecMode UNSPECIFIED,表示一種測(cè)量狀態(tài),對(duì)View的大小不做限...
    YangGui閱讀 733評(píng)論 0 3

友情鏈接更多精彩內(nèi)容