[Android] View 的測(cè)量及繪制

View 的顯示過(guò)程


一個(gè) View 經(jīng)過(guò)三步重點(diǎn)流程巷燥,最終才能顯示到屏幕上。分別是:測(cè)量号枕,布局缰揪,繪制。

其實(shí)很容易理解葱淳,一個(gè)圖形要想顯示在界面上钝腺,首先要進(jìn)行測(cè)量決定大小。然后要進(jìn)行布局赞厕,決定擺放的位置艳狐。最后就是進(jìn)行繪制,用線條和圖形描述出來(lái)皿桑。

如果要進(jìn)行自定義 View 的學(xué)習(xí)毫目,那么了解這些流程是必須的。

在 Android 中诲侮,View 同樣要經(jīng)過(guò)以上三個(gè)步驟镀虐,只是其中的布局通常是由父布局,一個(gè) ViewGroup 來(lái)決定沟绪,我們主要先來(lái)了解一下測(cè)量以及繪制的過(guò)程刮便。

測(cè)量


MeasureSpec

View 的測(cè)量是在 onMeasure() 方法中進(jìn)行。其中 Android 設(shè)計(jì)了一個(gè)類用來(lái)進(jìn)行測(cè)量 ---- MeasureSpec 類绽慈,這個(gè)類的值是一個(gè) 32 位的 int 值恨旱,高 2 位表示測(cè)量的模式,低 30 位表示測(cè)量的大小坝疼。

MeasureSpec 的三種模式

其中測(cè)量的模式分為以下三種:

  1. EXACTLY
    精確值模式窖杀,在手動(dòng)指定了控件的 layout_width 或 layout_heigth 屬性為具體數(shù)值的時(shí)候,或者指定為 match_parent 時(shí)裙士,系統(tǒng)使用的是 EXACTLY 模式。

  2. AT_MOST
    最大值模式管毙,在手動(dòng)指定了控件的 layout_width 或 layout_heigth 屬性為 wrap_content 的時(shí)候腿椎,控件大小一般隨著控件的子控件或內(nèi)容變化而變化,只要不超過(guò)父控件允許的最大尺寸即可夭咬。

  3. UNSPECIFIED
    表示開(kāi)發(fā)人員可以將視圖按照自己的意愿設(shè)置成任意的大小啃炸,沒(méi)有任何限制。這種情況比較少見(jiàn)卓舵,不太會(huì)用到南用。

View 類模式的 onMeasure() 方法只支持 EXACTLY 模式,所以如果自定義控件的時(shí)候不重寫(xiě) onMeasure() 方法的話,就只能使用 EXACTLY 模式裹虫≈壮埃控件可以響應(yīng)你指定的具體寬高值或者是 match_parent 屬性。但是如果需要 View 支持 wrap_content 屬性筑公,就必須重寫(xiě) onMeasure() 方法來(lái)指定 wrap_content 模式時(shí)的大小雳窟。

MeasureSpec 是怎么來(lái)的

關(guān)于這個(gè) MeasureSpec 是由父布局傳遞給子布局的布局要求,我通過(guò)代碼調(diào)試得到一些信息匣屡,我們來(lái)看一下封救。

在一個(gè) 1080 x 1920 的手機(jī)上,最外層使用一個(gè) LinearLayout 捣作, width 和 height 都使用 match_parent誉结,然后包裹了一個(gè)自定義的 View 。

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.shire.myapplication.MyView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#f2f2" />

</LinearLayout>

此時(shí)在 MyView 的 onMeasure() 中我獲取了 widthMeasureSpec 和 heightMeasureSpec 券躁,提取的結(jié)果為:
widthMeasureSpec 的 size 為:1080
heightMeasureSpec 的 size 為:1557

這里的 1080 就是頂層 LinearLayout 充滿屏幕的寬度惩坑,而 1557 就是頂層的 LinearLayout 除去狀態(tài)欄高度之后充滿屏幕的高度,由此可以得到 MeasureSpec 是傳過(guò)來(lái)的是父布局的大小嘱朽。

但是旭贬!如果你對(duì) View 進(jìn)行了自定義的大小,傳過(guò)來(lái)的就是你定義的大小搪泳。比如上面的 MyView 的 layout_width 更改為 100dp 稀轨,那么獲取到的結(jié)果就是:
widthMeasureSpec 的 size 為:300
heightMeasureSpec 的 size 為:1557

至于為什么 100dp 變成 300 這是 dp 轉(zhuǎn) px 的一個(gè)過(guò)程導(dǎo)致的,詳細(xì)的可以看我另一篇文章: Android開(kāi)發(fā)中dip岸军,dpi奋刽,density,px等詳解

分析 onMeasure

通過(guò) MeasureSpec 我們可以獲得測(cè)量模式以及大小艰赞,我們來(lái)看看部分源碼是如何進(jìn)行測(cè)量的佣谐。
我們先看 View 中的 onMeasure() 方法:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

在這里通過(guò) getDefaultSize() 來(lái)從 MeasureSpec 中獲取相應(yīng)的大小以及模式,最后轉(zhuǎn)換為一個(gè) int 類型給 setMeasuredDimension() 作為參數(shù)進(jìn)行最后測(cè)量的結(jié)果方妖,我們看看這個(gè) getDefaultSize()狭魂。

   public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

可以看到,首先通過(guò) MeasureSpec.getMode() 和 MeasureSpec.getSize() 取出模式和大小党觅,然后判斷模式雌澄。
可以看到在 AT_MOST 或 EXACTLY 模式下都是同樣的處理方式,這也說(shuō)明了上面所說(shuō)的杯瞻,View 在默認(rèn)情況下只支持 EXACTLY 模式镐牺,但是如果需要 View 支持 wrap_content 屬性,也就是 AT_MOST魁莉,就必須重寫(xiě) onMeasure() 方法來(lái)指定 AT_MOST 模式時(shí)的大小睬涧。

重寫(xiě) onMeasure

下面我們自定義一個(gè) View 來(lái)試試吧募胃,我們先新建一個(gè)空的自定義 View 。

package com.shire.myapplication;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;

public class MyView extends View {
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}

我們并沒(méi)有修改任何東西畦浓。接下來(lái)看看 XML 文件痹束,我們添加了一個(gè) myView 并設(shè)置了寬高為 wrap_content 背景是綠色便于觀察控件大小,那么在這個(gè)情況下的顯示效果會(huì)是怎么樣宅粥?

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <com.shire.myapplication.MyView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#f2f2" />
</LinearLayout>
QQ截圖20160128142829.png

如圖参袱,雖然我們?cè)O(shè)置了 wrap_content 屬性,當(dāng)時(shí)控件依然充滿了父控件秽梅,這就是我們上面說(shuō)的抹蚀,View 在默認(rèn)的情況下,是不支持 wrap_content 的企垦,而且在不設(shè)置指定的寬高的情況下會(huì)把父控件的寬高傳過(guò)來(lái)环壤,所以必須要重寫(xiě) onMeasure() 方法。

接下來(lái)看看應(yīng)該如何重寫(xiě) onMeasure() 方法 钞诡。根據(jù)源碼的方案郑现,是由 getDefaultSize() 方法來(lái)進(jìn)行測(cè)量,然后將結(jié)果給 setMeasuredDimension() 所以我們主要就是自定義一個(gè) “getDefaultSize()” 方法荧降。我們看下具體的代碼接箫。

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
       setMeasuredDimension(getSize(widthMeasureSpec),getSize(heightMeasureSpec));
    }

我們重寫(xiě)了 onMeasure() 方法,并自定義了一個(gè)測(cè)量方法 getSize() 朵诫,接下來(lái)就是看看 getSize() 中是如何寫(xiě)的辛友。

private int getSize(int  MeasureSpec) {
    //初始化一個(gè)返回值變量
    int result;
    //獲得測(cè)量模式
    int specMode = View.MeasureSpec.getMode(MeasureSpec);
    //獲得測(cè)量大小
    int specSize = View.MeasureSpec.getSize(MeasureSpec);
    //判斷模式是否是 EXACTLY
    if (specMode == View.MeasureSpec.EXACTLY) {
        //如果模式是 EXACTLY 則直接使用specSize的測(cè)量大小
        result  = specSize;
    }else{
        //如果是其他兩個(gè)模式,先設(shè)置一個(gè)默認(rèn)大小值 200
        result = 200;
        //如果是 AT_MOST 也就是 wrap_content 的話剪返,就取默認(rèn)值 200 和 specSize 中小的一個(gè)為準(zhǔn)废累。
        if (specMode == View.MeasureSpec.AT_MOST) {
            result = Math.min(result, specSize);
        }
    }
    return result;
}

在上面的代碼中,我們對(duì) AT_MOST 模式時(shí)的測(cè)量方式進(jìn)行了處理脱盲,接下來(lái)看看效果如何邑滨。還是同樣的 XML 文件。

QQ截圖20160128150118.png

可以看到钱反,現(xiàn)在 AT_MOST 模式已經(jīng)生效了掖看,當(dāng)我們?cè)谠O(shè)置 wrap_content 的時(shí)候不會(huì)再填充父布局,而是根據(jù)我們自定義的測(cè)量代碼進(jìn)行測(cè)量面哥,用了 200 這個(gè)默認(rèn)值乙各。

至此,對(duì) View 的測(cè)量算是簡(jiǎn)單的講解完成了幢竹,其實(shí)總結(jié)一句話,如果的你自定義 View 不需要使用 wrap_content恩静,就不用管 onMeasure() 方法焕毫。不然的話蹲坷,就需要重寫(xiě)。

繪制


View 的繪制過(guò)程是在 onDraw() 方法中邑飒,如果你去看源代碼循签,會(huì)發(fā)現(xiàn)這個(gè)方法是空的,但是子類可以繼承疙咸。想來(lái)也正常县匠,每個(gè)控件都有自己的表現(xiàn)方式,繪制方法撒轮,自然要自己來(lái)寫(xiě)這部分繪制的代碼乞旦。接下來(lái)我們繼續(xù)使用上面的 MyView 自定義繪制部分的代碼。

在 onDraw() 方法中题山,傳進(jìn)來(lái)了一個(gè) Canvas 對(duì)象兰粉,這個(gè)對(duì)象等于一塊畫(huà)布,我們可以在上面作畫(huà)顶瞳,那現(xiàn)在有了畫(huà)布玖姑,我們還需要一支筆,那就是 Paint 對(duì)象慨菱。

public class MyView extends View {
  //創(chuàng)建一個(gè)畫(huà)筆對(duì)象
  private Paint mPaint;
    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        //初始化畫(huà)筆對(duì)象
        mPaint = new Paint();
    }
    @Override
    protected void onDraw(Canvas canvas) {
        //設(shè)置畫(huà)筆的顏色為藍(lán)色
        mPaint.setColor(Color.BLUE);
        //使用畫(huà)筆畫(huà)一個(gè)矩形
        canvas.drawRect(0,0,50,50,mPaint);
        //設(shè)置畫(huà)筆的顏色的黃色
        mPaint.setColor(Color.YELLOW);
        //設(shè)置畫(huà)筆的字體大小為40
        mPaint.setTextSize(40);
        //使用畫(huà)筆寫(xiě)出一行字
        canvas.drawText("我可是用筆寫(xiě)的", 0, 80, mPaint);
    }
}

最后的效果

QQ截圖20160128161413.png

這只是簡(jiǎn)單的一個(gè)例子焰络,一般來(lái)說(shuō)一個(gè)控件的繪制過(guò)程是相當(dāng)復(fù)雜的,這個(gè)就要根據(jù)自己的情況來(lái)自定義了符喝。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末闪彼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子洲劣,更是在濱河造成了極大的恐慌备蚓,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件囱稽,死亡現(xiàn)場(chǎng)離奇詭異郊尝,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)战惊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)流昏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人吞获,你說(shuō)我怎么就攤上這事况凉。” “怎么了各拷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵刁绒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我烤黍,道長(zhǎng)知市,這世上最難降的妖魔是什么傻盟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮嫂丙,結(jié)果婚禮上娘赴,老公的妹妹穿的比我還像新娘。我一直安慰自己跟啤,他們只是感情好诽表,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著隅肥,像睡著了一般竿奏。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上武福,一...
    開(kāi)封第一講書(shū)人閱讀 49,760評(píng)論 1 289
  • 那天议双,我揣著相機(jī)與錄音,去河邊找鬼捉片。 笑死平痰,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的伍纫。 我是一名探鬼主播宗雇,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼莹规!你這毒婦竟也來(lái)了赔蒲?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤良漱,失蹤者是張志新(化名)和其女友劉穎舞虱,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體母市,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡矾兜,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了患久。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片椅寺。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蒋失,靈堂內(nèi)的尸體忽然破棺而出返帕,到底是詐尸還是另有隱情,我是刑警寧澤篙挽,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布荆萤,位于F島的核電站,受9級(jí)特大地震影響铣卡,放射性物質(zhì)發(fā)生泄漏观腊。R本人自食惡果不足惜邑闲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望梧油。 院中可真熱鬧,春花似錦州邢、人聲如沸儡陨。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)骗村。三九已至,卻和暖如春呀枢,著一層夾襖步出監(jiān)牢的瞬間胚股,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工裙秋, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留琅拌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓摘刑,卻偏偏與公主長(zhǎng)得像进宝,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子枷恕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容