Android自定義View(一)--基礎(chǔ)知識(shí)

一:自定義View繪制流程函數(shù)調(diào)用鏈


二.幾個(gè)重要的函數(shù)

1.構(gòu)造函數(shù)

構(gòu)造函數(shù)是View的入口灌侣,可以用于初始化一些的內(nèi)容辈赋,和獲取自定義屬性

View的構(gòu)造函數(shù)有四種重載分別如下:

public void SloopView(Context context) {}

public void SloopView(Context context,AttributeSet attrs) {}

public void SloopView(Context context,AttributeSet attrs,int defStyleAttr) {}

public void SloopView(Context context,AttributeSet attrs,int defStyleAttr,int defStyleRes) {}

可以看出赦抖,關(guān)于View構(gòu)造函數(shù)的參數(shù)有多有少惧所,先排除幾個(gè)不常用的崖咨,留下常用的再研究锻拘。

有四個(gè)參數(shù)的構(gòu)造函數(shù)在API21的時(shí)候才添加上,暫不考慮击蹲。

有三個(gè)參數(shù)的構(gòu)造函數(shù)中第三個(gè)參數(shù)是默認(rèn)的Style署拟,這里的默認(rèn)的Style是指它在當(dāng)前Application或Activity所用的Theme中的默認(rèn)Style,且只有在明確調(diào)用的時(shí)候才會(huì)生效歌豺,以系統(tǒng)中的ImageButton為例說(shuō)明:

public ImageButton(Context context,AttributeSet attrs) {//調(diào)用了三個(gè)參數(shù)的構(gòu)造函數(shù)推穷,明確指定第三個(gè)參數(shù)this(context, attrs,com.android.internal.R.attr.imageButtonStyle);? ? }

publicImageButton(Context context,AttributeSet attrs, int defStyleAttr) {//此處調(diào)了四個(gè)參數(shù)的構(gòu)造函數(shù),無(wú)視即可this(context, attrs, defStyleAttr,0);? ? }

注意:即使你在View中使用了Style這個(gè)屬性也不會(huì)調(diào)用三個(gè)參數(shù)的構(gòu)造函數(shù)类咧,所調(diào)用的依舊是兩個(gè)參數(shù)的構(gòu)造函數(shù)馒铃。

由于三個(gè)參數(shù)的構(gòu)造函數(shù)第三個(gè)參數(shù)一般不用,暫不考慮痕惋,第三個(gè)參數(shù)的具體用法會(huì)在以后用到的時(shí)候詳細(xì)介紹区宇。

排除了兩個(gè)之后,只剩下一個(gè)參數(shù)和兩個(gè)參數(shù)的構(gòu)造函數(shù)值戳,他們的詳情如下:

//一般在直接New一個(gè)View的時(shí)候調(diào)用议谷。

public void SloopView(Context context) {}

//一般在layout文件中使用的時(shí)候會(huì)調(diào)用,關(guān)于它的所有屬性(包括自定義屬性)都會(huì)包含在attrs中傳遞進(jìn)來(lái)堕虹。

public void SloopView(Context context,AttributeSet attrs) {}

以下方法調(diào)用的是一個(gè)參數(shù)的構(gòu)造函數(shù):

//在Avtivity中SloopViewview=newSloopView(this);

以下方法調(diào)用的是兩個(gè)參數(shù)的構(gòu)造函數(shù):

//在layout文件中 - 格式為: <包名.View名 />

關(guān)于構(gòu)造函數(shù)先講這么多卧晓,關(guān)于如何自定義屬性和使用attrs中的內(nèi)容芬首,在后面會(huì)詳細(xì)講解,目前只需要知道這兩個(gè)構(gòu)造函數(shù)在何時(shí)調(diào)用即可逼裆。

2.onAttachedToWindow():

運(yùn)行在onResume()之后郁稍;此函數(shù)會(huì)調(diào)用Activity的onResume()生命周期,所以在onResume之后可以設(shè)置窗體尺寸胜宇。

3.Measure


具體分析

measure 過(guò)程由measure(int, int)方法發(fā)起艺晴,從上到下有序的測(cè)量 View,在 measure 過(guò)程的最后掸屡,每個(gè)視圖存儲(chǔ)了自己的尺寸大小和測(cè)量規(guī)格封寞。 layout 過(guò)程由layout(int, int, int, int)方法發(fā)起,也是自上而下進(jìn)行遍歷仅财。在該過(guò)程中狈究,每個(gè)父視圖會(huì)根據(jù) measure 過(guò)程得到的尺寸來(lái)擺放自己的子視圖。

measure 過(guò)程會(huì)為一個(gè) View 及所有子節(jié)點(diǎn)的 mMeasuredWidth 和 mMeasuredHeight 變量賦值盏求,該值可以通過(guò)getMeasuredWidth()和getMeasuredHeight()方法獲得抖锥。而且這兩個(gè)值必須在父視圖約束范圍之內(nèi),這樣才可以保證所有的父視圖都接收所有子視圖的測(cè)量碎罚。如果子視圖對(duì)于 Measure 得到的大小不滿意的時(shí)候磅废,父視圖會(huì)介入并設(shè)置測(cè)量規(guī)則進(jìn)行第二次 measure。比如荆烈,父視圖可以先根據(jù)未給定的 dimension 去測(cè)量每一個(gè)子視圖拯勉,如果最終子視圖的未約束尺寸太大或者太小的時(shí)候,父視圖就會(huì)使用一個(gè)確切的大小再次對(duì)子視圖進(jìn)行 measure憔购。

measure 過(guò)程傳遞尺寸的兩個(gè)類

ViewGroup.LayoutParams (View 自身的布局參數(shù))

MeasureSpecs 類(父視圖對(duì)子視圖的測(cè)量要求)

ViewGroup.LayoutParams

這個(gè)類我們很常見(jiàn)宫峦,就是用來(lái)指定視圖的高度和寬度等參數(shù)。對(duì)于每個(gè)視圖的 height 和 width玫鸟,你有以下選擇:

具體值

MATCH_PARENT 表示子視圖希望和父視圖一樣大(不包含 padding 值)

WRAP_CONTENT 表示視圖為正好能包裹其內(nèi)容大小(包含 padding 值)

ViewGroup 的子類有其對(duì)應(yīng)的 ViewGroup.LayoutParams 的子類导绷。比如 RelativeLayout 擁有的 ViewGroup.LayoutParams 的子類 RelativeLayoutParams。

有時(shí)我們需要使用 view.getLayoutParams() 方法獲取一個(gè)視圖 LayoutParams屎飘,然后進(jìn)行強(qiáng)轉(zhuǎn)妥曲,但由于不知道其具體類型,可能會(huì)導(dǎo)致強(qiáng)轉(zhuǎn)錯(cuò)誤钦购。其實(shí)該方法得到的就是其所在父視圖類型的 LayoutParams檐盟,比如 View 的父控件為 RelativeLayout,那么得到的 LayoutParams 類型就為 RelativeLayoutParams肮雨。

measure 核心方法

measure(int widthMeasureSpec, int heightMeasureSpec)

該方法定義在View.java類中遵堵,為 final 類型,不可被復(fù)寫,但 measure 調(diào)用鏈最終會(huì)回調(diào) View/ViewGroup 對(duì)象的onMeasure()方法陌宿,因此自定義視圖時(shí)锡足,只需要復(fù)寫onMeasure()方法即可。

4.onMeasure(int widthMeasureSpec, int heightMeasureSpec)

該方法就是我們自定義視圖中實(shí)現(xiàn)測(cè)量邏輯的方法壳坪,該方法的參數(shù)是父視圖對(duì)子視圖的 width 和 height 的測(cè)量要求舶得。在我們自身的自定義視圖中,要做的就是根據(jù)該 widthMeasureSpec 和 heightMeasureSpec 計(jì)算視圖的 width 和 height爽蝴,不同的模式處理方式不同沐批。

Q: 為什么要測(cè)量View大小蝎亚?

A: View的大小不僅由自身所決定九孩,同時(shí)也會(huì)受到父控件的影響,為了我們的控件能更好的適應(yīng)各種情況发框,一般會(huì)自己進(jìn)行測(cè)量躺彬。

測(cè)量View大小使用的是onMeasure函數(shù),我們可以從onMeasure的兩個(gè)參數(shù)中取出寬高的相關(guān)數(shù)據(jù):

@OverrideprotectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec{

int widthsize=MeasureSpec.getSize(widthMeasureSpec);//取出寬度的確切數(shù)值

int widthmode=MeasureSpec.getMode(widthMeasureSpec);//取出寬度的測(cè)量模式

int heightsize=MeasureSpec.getSize(heightMeasureSpec);//取出高度的確切數(shù)值

int heightmode=MeasureSpec.getMode(heightMeasureSpec);//取出高度的測(cè)量模式

}

setMeasuredDimension()

測(cè)量階段終極方法梅惯,在onMeasure(int widthMeasureSpec, int heightMeasureSpec)方法中調(diào)用宪拥,將計(jì)算得到的尺寸,傳遞給該方法铣减,測(cè)量階段即結(jié)束她君。該方法也是必須要調(diào)用的方法,否則會(huì)報(bào)異常葫哗。在我們?cè)谧远x視圖的時(shí)候缔刹,不需要關(guān)心系統(tǒng)復(fù)雜的 Measure 過(guò)程的,只需調(diào)用setMeasuredDimension()設(shè)置根據(jù) MeasureSpec 計(jì)算得到的尺寸即可魄梯,你可以參考ViewPagerIndicator的 onMeasure 方法桨螺。

View.MeasureSpec中:

模式二進(jìn)制數(shù)值描述

UNSPECIFIED00默認(rèn)值宾符,父控件沒(méi)有給子view任何限制酿秸,子View可以設(shè)置為任意大小。

EXACTLY01表示父控件已經(jīng)確切的指定了子View的大小魏烫。

AT_MOST10表示子View具體大小沒(méi)有尺寸限制辣苏,但是存在上限,上限一般為父View大小哄褒。

源碼解析:ANDROID自定義視圖——onMeasure稀蟋,MeasureSpec源碼 流程 思路詳解

5.確定View大小(onSizeChanged)

這個(gè)函數(shù)在視圖大小發(fā)生改變時(shí)調(diào)用。

Q: 在測(cè)量完View并使用setMeasuredDimension函數(shù)之后View的大小基本上已經(jīng)確定了呐赡,那么為什么還要再次確定View的大小呢退客?

A: 這是因?yàn)閂iew的大小不僅由View本身控制,而且受父控件的影響,所以我們?cè)诖_定View大小的時(shí)候最好使用系統(tǒng)提供的onSizeChanged回調(diào)函數(shù)萌狂。

onSizeChanged如下:

@OverrideprotectedvoidonSizeChanged(intw,inth,intoldw,intoldh) {super.onSizeChanged(w, h, oldw, oldh);? ? }

可以看出档玻,它又四個(gè)參數(shù),分別為 寬度茫藏,高度误趴,上一次寬度,上一次高度务傲。

這個(gè)函數(shù)比較簡(jiǎn)單凉当,我們只需關(guān)注 寬度(w), 高度(h) 即可,這兩個(gè)參數(shù)就是View最終的大小售葡。


6.確定子View布局位置(onLayout)

首先要明確的是看杭,子視圖的具體位置都是相對(duì)于父視圖而言的。View 的 onLayout 方法為空實(shí)現(xiàn)挟伙,而 ViewGroup 的 onLayout 為 abstract 的泊窘,因此,如果自定義的 View 要繼承 ViewGroup 時(shí)像寒,必須實(shí)現(xiàn) onLayout 函數(shù)烘豹。

在 layout 過(guò)程中,子視圖會(huì)調(diào)用getMeasuredWidth()和getMeasuredHeight()方法獲取到 measure 過(guò)程得到的 mMeasuredWidth 和 mMeasuredHeight诺祸,作為自己的 width 和 height携悯。然后調(diào)用每一個(gè)子視圖的layout(l, t, r, b)函數(shù),來(lái)確定每個(gè)子視圖在父視圖中的位置筷笨。

確定布局的函數(shù)是onLayout憔鬼,它用于確定子View的位置,在自定義ViewGroup中會(huì)用到胃夏,他調(diào)用的是子View的layout函數(shù)轴或。

在自定義ViewGroup中,onLayout一般是循環(huán)取出子View仰禀,然后經(jīng)過(guò)計(jì)算得出各個(gè)子View位置的坐標(biāo)值照雁,然后用以下函數(shù)設(shè)置子View位置。

child.layout(l, t, r, b);

四個(gè)參數(shù)分別為:

名稱說(shuō)明對(duì)應(yīng)的函數(shù)

lView左側(cè)距父View左側(cè)的距離getLeft();

tView頂部距父View頂部的距離getTop();

rView右側(cè)距父View左側(cè)的距離getRight();

bView底部距父View頂部的距離getBottom();

具體可以參考坐標(biāo)系這篇文章答恶。

來(lái)自GcsSloop

7.draw繪制流程相關(guān)概念及核心方法

View.draw(Canvas canvas): 由于 ViewGroup 并沒(méi)有復(fù)寫此方法饺蚊,因此,所有的視圖最終都是調(diào)用 View 的 draw 方法進(jìn)行繪制的悬嗓。在自定義的視圖中污呼,也不應(yīng)該復(fù)寫該方法,而是復(fù)寫onDraw(Canvas)方法進(jìn)行繪制包竹,如果自定義的視圖確實(shí)要復(fù)寫該方法燕酷,那么請(qǐng)先調(diào)用super.draw(canvas)完成系統(tǒng)的繪制籍凝,然后再進(jìn)行自定義的繪制。

View.onDraw():

View 的onDraw(Canvas)默認(rèn)是空實(shí)現(xiàn)苗缩,自定義繪制過(guò)程需要復(fù)寫的方法静浴,繪制自身的內(nèi)容。

drawChild(canvas, this, drawingTime)

直接調(diào)用了 View 的child.draw(canvas, this,drawingTime)方法挤渐,文檔中也說(shuō)明了苹享,除了被ViewGroup.drawChild()方法外,你不應(yīng)該在其它任何地方去復(fù)寫或調(diào)用該方法浴麻,它屬于 ViewGroup得问。而View.draw(Canvas)方法是我們自定義控件中可以復(fù)寫的方法,具體可以參考上述對(duì)view.draw(Canvas)的說(shuō)明软免。從參數(shù)中可以看到宫纬,child.draw(canvas, this, drawingTime)肯定是處理了和父視圖相關(guān)的邏輯,但 View 的最終繪制膏萧,還是View.draw(Canvas)方法漓骚。

invalidate()

請(qǐng)求重繪 View 樹,即 draw 過(guò)程榛泛,假如視圖發(fā)生大小沒(méi)有變化就不會(huì)調(diào)用layout()過(guò)程蝌蹂,并且只繪制那些調(diào)用了invalidate()方法的 View。

requestLayout()

當(dāng)布局變化的時(shí)候曹锨,比如方向變化孤个,尺寸的變化,會(huì)調(diào)用該方法沛简,在自定義的視圖中齐鲤,如果某些情況下希望重新測(cè)量尺寸大小,應(yīng)該手動(dòng)去調(diào)用該方法椒楣,它會(huì)觸發(fā)measure()和layout()過(guò)程给郊,但不會(huì)進(jìn)行 draw。

dispatchDraw() 發(fā)起對(duì)子視圖的繪制捧灰。View 中默認(rèn)是空實(shí)現(xiàn)淆九,ViewGroup 復(fù)寫了dispatchDraw()來(lái)對(duì)其子視圖進(jìn)行繪制。該方法我們不用去管凤壁,自定義的 ViewGroup 不應(yīng)該對(duì)dispatchDraw()進(jìn)行復(fù)寫吩屹。

Android的view組件顯示主要經(jīng)過(guò)mesure, layout和draw這三個(gè)過(guò)程。在mesure階段里調(diào)用mesure(int widthSpec, int heightSpec)方法拧抖,這個(gè)方法是final不能被重寫,在這個(gè)過(guò)程里會(huì)調(diào)用onMesure(int widthSpec, int heightSpec)方法免绿。當(dāng)組件設(shè)置好大小后唧席,調(diào)用final layout(int l, int t, int r, int b)方法進(jìn)行布局,在這個(gè)過(guò)程里會(huì)調(diào)用onLayout(boolean changed, int l, int t, int r, int b)方法,所以處理組件的布局通常要重寫onMesure和onLayout這兩個(gè)方法淌哟。

View組件的繪制會(huì)調(diào)用draw(Canvas canvas)方法迹卢,這個(gè)方法在源代碼里看不到在哪里調(diào)用...draw過(guò)程中主要是先畫Drawable背景,對(duì)drawable調(diào)用setBounds()然后是draw(Canvas c)方法.有點(diǎn)注意的是背景drawable的實(shí)際大小會(huì)影響view組件的大小徒仓,drawable的實(shí)際大小通過(guò)getIntrinsicWidth()和getIntrinsicHeight()獲取腐碱,當(dāng)背景比較大時(shí)view組件大小等于背景drawable的大小,不過(guò)俺沒(méi)有在源代碼里找到布局時(shí)調(diào)用過(guò)getIntrinsicWidth()和getIntrinsicHeight()方法...

畫完背景后掉弛,draw過(guò)程會(huì)調(diào)用onDraw(Canvas canvas)方法症见,然后就是dispatchDraw(Canvas canvas)方法, dispatchDraw()主要是分發(fā)給子組件進(jìn)行繪制,我們通常定制組件的時(shí)候重寫的是onDraw()方法殃饿。值得注意的是ViewGroup容器組件的繪制谋作,當(dāng)它沒(méi)有背景時(shí)直接調(diào)用的是dispatchDraw()方法, 而繞過(guò)了draw()方法督暂,當(dāng)它有背景的時(shí)候就調(diào)用draw()方法乔遮,而draw()方法里包含了dispatchDraw()方法的調(diào)用。因此要在ViewGroup上繪制東西的時(shí)候往往重寫的是dispatchDraw()方法而不是onDraw()方法寞钥,或者自定制一個(gè)Drawable奈惑,重寫它的draw(Canvas c)和getIntrinsicWidth(),

getIntrinsicHeight()方法吭净,然后設(shè)為背景。


參考文檔:

公共技術(shù)點(diǎn)之 View 繪制流程

自定義View分類與流程

自定義View總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肴甸,一起剝皮案震驚了整個(gè)濱河市攒钳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌雷滋,老刑警劉巖不撑,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異晤斩,居然都是意外死亡焕檬,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門澳泵,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)实愚,“玉大人,你說(shuō)我怎么就攤上這事兔辅±扒茫” “怎么了?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵维苔,是天一觀的道長(zhǎng)碰辅。 經(jīng)常有香客問(wèn)我,道長(zhǎng)介时,這世上最難降的妖魔是什么没宾? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任凌彬,我火速辦了婚禮,結(jié)果婚禮上循衰,老公的妹妹穿的比我還像新娘铲敛。我一直安慰自己,他們只是感情好会钝,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布伐蒋。 她就那樣靜靜地躺著,像睡著了一般迁酸。 火紅的嫁衣襯著肌膚如雪先鱼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天胁出,我揣著相機(jī)與錄音型型,去河邊找鬼。 笑死全蝶,一個(gè)胖子當(dāng)著我的面吹牛闹蒜,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抑淫,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼绷落,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了始苇?” 一聲冷哼從身側(cè)響起砌烁,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎催式,沒(méi)想到半個(gè)月后函喉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡荣月,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年管呵,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哺窄。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡捐下,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出萌业,到底是詐尸還是另有隱情坷襟,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布生年,位于F島的核電站婴程,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏晶框。R本人自食惡果不足惜排抬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一懂从、第九天 我趴在偏房一處隱蔽的房頂上張望授段。 院中可真熱鬧蹲蒲,春花似錦、人聲如沸侵贵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)窍育。三九已至卡睦,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間漱抓,已是汗流浹背表锻。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留乞娄,地道東北人瞬逊。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像仪或,于是被迫代替她去往敵國(guó)和親确镊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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