自定義控件-繪制順序

Android 里面的繪制都是按順序的盒卸,先繪制的內(nèi)容會被后繪制的蓋住悠菜。比如你在重疊的位置先畫圓再畫方频轿,和先畫方再畫圓所呈現(xiàn)出來的結(jié)果肯定是不同的:


繪制順序

1 super.onDraw() 前 or 后逆日?

自定義繪制的最基本形態(tài):繼承 View 類余黎,在 onDraw() 中完全自定義它的繪制。

一般我們把繪制代碼全都寫在了 super.onDraw() 的下面鸟雏。不過其實隆敢,繪制代碼寫在 super.onDraw() 的上面還是下面都無所謂,甚至崔慧,你把 super.onDraw() 這行代碼刪掉都沒關(guān)系,效果都是一樣的——因為在 View 這個類里穴墅,onDraw() 本來就是空實現(xiàn)

然而惶室,除了繼承 View 類,自定義繪制更為常見的情況是玄货,繼承一個具有某種功能的控件皇钞,去重寫它的 onDraw() ,在里面添加一些繪制代碼松捉,做出一個「進化版」的控件

而這種基于已有控件的自定義繪制夹界,就不能不考慮 super.onDraw() 了:你需要根據(jù)自己的需求,判斷出你繪制的內(nèi)容需要蓋住控件原有的內(nèi)容還是需要被控件原有的內(nèi)容蓋住隘世,從而確定你的繪制代碼是應(yīng)該寫在 super.onDraw() 的上面還是下面可柿。

1.1寫在 super.onDraw() 的下面

把繪制代碼寫在 super.onDraw() 的下面,由于繪制代碼會在原有內(nèi)容繪制結(jié)束之后才執(zhí)行丙者,所以繪制內(nèi)容就會蓋住控件原來的內(nèi)容复斥。

1.2 寫在 super.onDraw() 的上面

如果把繪制代碼寫在 super.onDraw() 的上面,由于繪制代碼會執(zhí)行在原有內(nèi)容的繪制之前械媒,所以繪制的內(nèi)容會被控件的原內(nèi)容蓋住目锭。

2 dispatchDraw():繪制子 View 的方法

除了onDraw() 這一個繪制方法,其實繪制方法不是只有一個的纷捞,而是有好幾個痢虹,其中 onDraw()只是負責自身主體內(nèi)容繪制的。而有的時候主儡,你想要的遮蓋關(guān)系無法通過 onDraw() 來實現(xiàn)奖唯,而是需要通過別的繪制方法。

例如缀辩,你繼承了一個 LinearLayout臭埋,重寫了它的 onDraw() 方法踪央,在 super.onDraw() 中插入了你自己的繪制代碼,使它能夠在內(nèi)部繪制一些斑點作為點綴:

public class SpottedLinearLayout extends LinearLayout {
    ...
    
    protected void onDraw(Canvas canvas) {
       super.onDraw(canvas);
    
       ... // 繪制斑點
    }
}
onDraw()

但是瓢阴,當你添加了子 View 之后畅蹂,你的斑點不見了:

<SpottedLinearLayout
    android:orientation="vertical"
    ... >

    <ImageView ... />

    <TextView ... />

</SpottedLinearLayout>

造成這種情況的原因是 Android 的繪制順序:在繪制過程中,每一個 ViewGroup 會先調(diào)用自己的 onDraw() 來繪制完自己的主體之后再去繪制它的子 View荣恐。對于上面這個例子來說液斜,就是你的 LinearLayout 會在繪制完斑點后再去繪制它的子 View。那么在子 View 繪制完成之后叠穆,先前繪制的斑點就被子 View 蓋住了少漆。
具體來講,這里說的「繪制子 View」是通過另一個繪制方法的調(diào)用來發(fā)生的,這個繪制方法叫做:dispatchDraw()抬旺。也就是說馁痴,在繪制過程中,每個 View 和 ViewGroup 都會先調(diào)用 onDraw() 方法來繪制主體检访,再調(diào)用 dispatchDraw() 方法來繪制子 View。

注:雖然 View 和 ViewGroup 都有 dispatchDraw() 方法仔掸,不過由于 View 是沒有子 View 的脆贵,所以一般來說 dispatchDraw() 這個方法只對 ViewGroup(以及它的子類)有意義。

怎樣才能讓 LinearLayout 的繪制內(nèi)容蓋住子 View 呢起暮?只要讓它的繪制代碼在子 View 的繪制之后再執(zhí)行就好了卖氨。

2.1 寫在 super.dispatchDraw() 的下面

只要重寫 dispatchDraw(),并在 super.dispatchDraw() 的下面寫上你的繪制代碼负懦,這段繪制代碼就會發(fā)生在子 View 的繪制之后筒捺,從而讓繪制內(nèi)容蓋住子 View 了。

public class SpottedLinearLayout extends LinearLayout {
    ...
    
    // 把 onDraw() 換成了 dispatchDraw()
    protected void dispatchDraw(Canvas canvas) {
       super.dispatchDraw(canvas);
    
       ... // 繪制斑點
    }

}

2.2 寫在 super.dispatchDraw() 的上面

同理密似,把繪制代碼寫在 super.dispatchDraw() 的上面焙矛,這段繪制就會在 onDraw() 之后、 super.dispatchDraw() 之前發(fā)生残腌,也就是繪制內(nèi)容會出現(xiàn)在主體內(nèi)容和子 View 之間村斟。效果和重寫 onDraw() 并把繪制代碼寫在 super.onDraw() 之后的做法是一樣的。

3 繪制過程簡述

繪制過程中最典型的兩個部分是上面講到的主體和子 View抛猫,但它們并不是繪制過程的全部蟆盹。除此之外,繪制過程還包含一些其他內(nèi)容的繪制闺金。具體來講逾滥,一個完整的繪制過程會依次繪制以下幾個內(nèi)容:
1.背景
2.主體(onDraw())
3.子View(dispatchDraw())
4.滑動邊緣漸變和滑動條
5.前景

一般來說,一個 View(或 ViewGroup)的繪制不會這幾項全都包含,但必然逃不出這幾項寨昙,并且一定會嚴格遵守這個順序讥巡。例如通常一個 LinearLayout 只有背景和子 View,那么它會先繪制背景再繪制子 View舔哪;一個 ImageView 有主體欢顷,有可能會再加上一層半透明的前景作為遮罩,那么它的前景也會在主體之后進行繪制捉蚤。需要注意抬驴,前景的支持是在 Android 6.0(也就是 API 23)才加入的;之前其實也有缆巧,不過只支持 FrameLayout布持,而直到 6.0 才把這個支持放進了 View 類里。

這其中的第 2陕悬、3 兩步题暖,前面已經(jīng)講過了;第 1 步——背景捉超,它的繪制發(fā)生在一個叫 drawBackground() 的方法里芙委,但這個方法是 private 的,不能重寫狂秦,你如果要設(shè)置背景,只能用自帶的 API 去設(shè)置(xml 布局文件的 android:background 屬性以及 Java 代碼的 View.setBackgroundXxx() 方法推捐,這個每個人都用得很 6 了)裂问,而不能自定義繪制;而第 4牛柒、5 兩步——滑動邊緣漸變和滑動條以及前景堪簿,這兩部分被合在一起放在了 onDrawForeground() 方法里,這個方法是可以重寫的皮壁。


繪制順序

滑動邊緣漸變和滑動條可以通過 xml 的 android:scrollbarXXX 系列屬性或 Java 代碼的 View.setXXXScrollbarXXX() 系列方法來設(shè)置椭更;前景可以通過 xml 的 android:foreground 屬性或 Java 代碼的 View.setForeground() 方法來設(shè)置。而重寫 onDrawForeground() 方法蛾魄,并在它的 super.onDrawForeground() 方法的上面或下面插入繪制代碼虑瀑,則可以控制繪制內(nèi)容和滑動邊緣漸變、滑動條以及前景的遮蓋關(guān)系滴须。

4 onDrawForeground()

這個方法是 API 23 才引入的舌狗,所以在重寫這個方法的時候要確認你的 minSdk 達到了 23,不然低版本的手機裝上你的軟件會沒有效果扔水。

在 onDrawForeground() 中痛侍,會依次繪制滑動邊緣漸變、滑動條和前景

4.1 寫在 super.onDrawForeground() 的下面

如果你把繪制代碼寫在了 super.onDrawForeground() 的下面魔市,繪制代碼會在滑動邊緣漸變主届、滑動條和前景之后被執(zhí)行赵哲,那么繪制內(nèi)容將會蓋住滑動邊緣漸變、滑動條和前景君丁。

public class AppImageView extends ImageView {
    ...
    
    public void onDrawForeground(Canvas canvas) {
       super.onDrawForeground(canvas);
    
       ... // 繪制「New」標簽
    }

}
<!-- 使用半透明的黑色作為前景枫夺,這是一種很常見的處理 -->
<AppImageView
    ...
    android:foreground="#88000000" />
寫在 super.onDrawForeground() 的下面

左上角的標簽并沒有被黑色遮罩蓋住,而是保持了原有的顏色谈截。

4.2 寫在 super.onDrawForeground() 的上面

如果把繪制代碼寫在了 super.onDrawForeground() 的上面筷屡,繪制內(nèi)容就會在 dispatchDraw() 和 super.onDrawForeground() 之前執(zhí)行,那么繪制內(nèi)容會蓋住子 View簸喂,但被滑動邊緣漸變毙死、滑動條以及前景蓋住:

public class AppImageView extends ImageView {
    ...
    
    public void onDrawForeground(Canvas canvas) {
       ... // 繪制「New」標簽
    
       super.onDrawForeground(canvas);
    }
}
寫在 super.onDrawForeground() 的上面

由于被半透明黑色遮罩蓋住喻鳄,左上角的標簽明顯變暗了扼倘。

5 draw() 總調(diào)度方法

除了 onDraw() dispatchDraw() 和 onDrawForeground() 之外,還有一個可以用來實現(xiàn)自定義繪制的方法: draw()除呵。

draw() 是繪制過程的總調(diào)度方法再菊。一個 View 的整個繪制過程都發(fā)生在 draw() 方法里。前面講到的背景颜曾、主體纠拔、子 View 、滑動相關(guān)以及前景的繪制泛豪,它們其實都是在 draw() 方法里的稠诲。

// View.java 的 draw() 方法的簡化版大致結(jié)構(gòu)(是大致結(jié)構(gòu),不是源碼)

public void draw(Canvas canvas) {
    ...
    
    drawBackground(Canvas); // 繪制背景(不能重寫)
    onDraw(Canvas); // 繪制主體
    dispatchDraw(Canvas); // 繪制子 View
    onDrawForeground(Canvas); // 繪制滑動相關(guān)和前景
    
    ...
}

從上面的代碼可以看出诡曙,onDraw() dispatchDraw() onDrawForeground() 這三個方法在 draw() 中被依次調(diào)用臀叙,因此它們的遮蓋關(guān)系也就像前面所說的——dispatchDraw() 繪制的內(nèi)容蓋住 onDraw() 繪制的內(nèi)容;onDrawForeground() 繪制的內(nèi)容蓋住 dispatchDraw() 繪制的內(nèi)容价卤。而在它們的外部劝萤,則是由 draw() 這個方法作為總的調(diào)度。所以慎璧,你也可以重寫 draw() 方法來做自定義的繪制床嫌。

整體調(diào)度

5.1 寫在 super.draw() 的下面

由于 draw() 是總調(diào)度方法,所以如果把繪制代碼寫在 super.draw() 的下面胸私,那么這段代碼會在其他所有繪制完成之后再執(zhí)行既鞠,也就是說,它的繪制內(nèi)容會蓋住其他的所有繪制內(nèi)容盖文。

5.2 寫在 super.draw() 的上面

同理嘱蛋,由于 draw() 是總調(diào)度方法,所以如果把繪制代碼寫在 super.draw() 的上面,那么這段代碼會在其他所有繪制之前被執(zhí)行洒敏,所以這部分繪制內(nèi)容會被其他所有的內(nèi)容蓋住龄恋,包括背景。是的凶伙,背景也會蓋住它郭毕。

注意

關(guān)于繪制方法,有兩點需要注意一下:

  1. 出于效率的考慮函荣,ViewGroup 默認會繞過 draw() 方法显押,換而直接執(zhí)行 dispatchDraw(),以此來簡化繪制流程傻挂。所以如果你自定義了某個 ViewGroup 的子類(比如 LinearLayout)并且需要在它的除 dispatchDraw() 以外的任何一個繪制方法內(nèi)繪制內(nèi)容乘碑,你可能會需要調(diào)用 View.setWillNotDraw(false) 這行代碼來切換到完整的繪制流程(是「可能」而不是「必須」的原因是,有些 ViewGroup 是已經(jīng)調(diào)用過 setWillNotDraw(false) 了的金拒,例如 ScrollView)兽肤。
  2. 有的時候,一段繪制代碼寫在不同的繪制方法中效果是一樣的绪抛,這時你可以選一個自己喜歡或者習(xí)慣的繪制方法來重寫资铡。但有一個例外:如果繪制代碼既可以寫在 onDraw() 里,也可以寫在其他繪制方法里幢码,那么優(yōu)先寫在 onDraw() 笤休,因為 Android 有相關(guān)的優(yōu)化,可以在不需要重繪的時候自動跳過 onDraw() 的重復(fù)執(zhí)行症副,以提升開發(fā)效率宛官。享受這種優(yōu)化的只有 onDraw() 一個方法。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瓦糕,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子腋么,更是在濱河造成了極大的恐慌咕娄,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件珊擂,死亡現(xiàn)場離奇詭異圣勒,居然都是意外死亡,警方通過查閱死者的電腦和手機摧扇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評論 3 395
  • 文/潘曉璐 我一進店門圣贸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人扛稽,你說我怎么就攤上這事吁峻。” “怎么了?”我有些...
    開封第一講書人閱讀 165,417評論 0 356
  • 文/不壞的土叔 我叫張陵用含,是天一觀的道長矮慕。 經(jīng)常有香客問我,道長啄骇,這世上最難降的妖魔是什么痴鳄? 我笑而不...
    開封第一講書人閱讀 58,868評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮缸夹,結(jié)果婚禮上痪寻,老公的妹妹穿的比我還像新娘。我一直安慰自己虽惭,他們只是感情好橡类,可當我...
    茶點故事閱讀 67,892評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著趟妥,像睡著了一般猫态。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上披摄,一...
    開封第一講書人閱讀 51,692評論 1 305
  • 那天亲雪,我揣著相機與錄音,去河邊找鬼疚膊。 笑死义辕,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的寓盗。 我是一名探鬼主播灌砖,決...
    沈念sama閱讀 40,416評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼傀蚌!你這毒婦竟也來了基显?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,326評論 0 276
  • 序言:老撾萬榮一對情侶失蹤善炫,失蹤者是張志新(化名)和其女友劉穎撩幽,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體箩艺,經(jīng)...
    沈念sama閱讀 45,782評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡窜醉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,957評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了艺谆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片榨惰。...
    茶點故事閱讀 40,102評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖静汤,靈堂內(nèi)的尸體忽然破棺而出琅催,到底是詐尸還是另有隱情居凶,我是刑警寧澤,帶...
    沈念sama閱讀 35,790評論 5 346
  • 正文 年R本政府宣布恢暖,位于F島的核電站排监,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏杰捂。R本人自食惡果不足惜舆床,卻給世界環(huán)境...
    茶點故事閱讀 41,442評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望嫁佳。 院中可真熱鬧挨队,春花似錦、人聲如沸蒿往。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瓤漏。三九已至腾夯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蔬充,已是汗流浹背蝶俱。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留饥漫,地道東北人榨呆。 一個月前我還...
    沈念sama閱讀 48,332評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像庸队,于是被迫代替她去往敵國和親积蜻。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,044評論 2 355

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