自定義View
最基本的方法:onMeasure()袁铐、onLayout()晃跺、onDraw();
onMeasure()決定大小
MeasureSpec值的確定
MeasureSpec值到底是如何計(jì)算得來(lái)的呢?
- 對(duì)于應(yīng)用層 View 稻轨,其 MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 來(lái)共同決定
- 對(duì)于不同的父容器和view本身不同的LayoutParams兄纺,view就可以有多種MeasureSpec镐侯。
- 當(dāng)view采用固定寬高的時(shí)候呵燕,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精確模式并且其大小遵循Layoutparams中的大写溆铩叽躯;
- 當(dāng)view的寬高是match_parent時(shí),這個(gè)時(shí)候如果父容器的模式是精準(zhǔn)模式肌括,那么view也是精準(zhǔn)模式并且其大小是父容器的剩余空間点骑,如果父容器是最大模式,那么view也是最大模式并且其大小不會(huì)超過(guò)父容器的剩余空間谍夭;
- 當(dāng)view的寬高是wrap_content時(shí)黑滴,不管父容器的模式是精準(zhǔn)還是最大化,view的模式總是最大化并且大小不能超過(guò)父容器的剩余空間紧索。
- Unspecified模式袁辈,這個(gè)模式主要用于系統(tǒng)內(nèi)部多次measure的情況下,一般來(lái)說(shuō)齐板,我們不需要關(guān)注此模式(這里注意自定義View放到ScrollView的情況 需要處理)吵瞻。
子View的MeasureSpec值是根據(jù)子View的布局參數(shù)(LayoutParams)和父容器的MeasureSpec值計(jì)算得來(lái)的,具體計(jì)算邏輯封裝在getChildMeasureSpec()里
MeasureSpecs 的意義
通過(guò)將 SpecMode 和 SpecSize 打包成一個(gè) int 值可以避免過(guò)多的對(duì)象內(nèi)存分配甘磨,為了方便操作橡羞,其提供了打包 / 解包方法
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int wSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int wSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int hSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int hSpecSize = MeasureSpec.getSize(heightMeasureSpec);
int w = 0;
int h = 0;
switch (wSpecMode) {
//精確模式
case MeasureSpec.EXACTLY:
w = wSpecSize;
break;
//不超過(guò)父布局的限制之下,自己要多少就多少
case MeasureSpec.AT_MOST:
//這里個(gè)的使父布局的限制大小;根據(jù)需要不超過(guò)這個(gè)大小就行
w = wSpecSize;
break;
//沒(méi)有限制济舆,隨沒(méi)有限制卿泽,系統(tǒng)多測(cè)測(cè)量的一個(gè)狀態(tài),最后還是會(huì)走確定大小的模式
case MeasureSpec.UNSPECIFIED:
//父布局不限制自己,自己給自己一個(gè)值
w = 100;
break;
}
onLayout()布局子view签夭,自定義Layout才用這個(gè)(必須覆蓋)齐邦,自定義View不用這個(gè)
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) {
//l、t第租、r措拇、b、是布局的位置慎宾,r-l=寬丐吓,b-t=高
Log.i(TAG, "l=" + l + " t=" + t + " r=" + r + " b=" + b);
}
onDraw()自定義view繪制的邏輯寫在這個(gè)方法里,方法會(huì)被執(zhí)行多次趟据,創(chuàng)建實(shí)例不宜在此操作
定義畫筆Paint 可設(shè)置顏色券犁,粗細(xì),圓頭汹碱,方頭粘衬,抗鋸齒、實(shí)心咳促、空心之類的
參數(shù)canvas可以畫點(diǎn)稚新,線,路徑path等缀,幾何圖形等
canvas.save();保存當(dāng)前畫布枷莉,之后canvas的操作不影響save之前的操作,如保存之后平移畫布尺迂,之前畫好的不會(huì)被平移,
canvas.restore();回復(fù)之前的畫布冒掌,保存多少次就得回復(fù)多少次噪裕,恢復(fù)多了會(huì)報(bào)錯(cuò)
canvas.restoreToCount(2);恢復(fù)兩次保存的畫布
什么叫自定以View,通常自定義一個(gè)類繼承了視圖組件都可以叫自定義view股毫,如果繼承了View就叫自定義視圖膳音,繼承了ViewGroup就叫自定義視圖組。
繼承了View組件有四個(gè)構(gòu)造方法覆蓋
第一個(gè)://如果在Java中直接new的畫調(diào)用铃诬,只有一個(gè)上下文參數(shù)(Context context)
第二個(gè):在xml文件中配置則會(huì)調(diào)用第二個(gè),第二個(gè)參數(shù)接收自定義屬性(Context context,AttributeSet attrs)
第三個(gè):不會(huì)自動(dòng)調(diào)用祭陷,一般是在第二個(gè)構(gòu)造函數(shù)調(diào)用,如View有style屬性時(shí)(Context context趣席,AttributeSet attrs,int defStyleAttr)
第四個(gè):API21后才使用兵志,不會(huì)自動(dòng)調(diào)用,一般是在第二個(gè)構(gòu)造函數(shù)主動(dòng)調(diào)用宣肚,如View有style屬性時(shí)(Context context想罕,AttributeSet attrs,int defStyleAttr,int defStyleRes)
AttributeSet與自定義屬性
系統(tǒng)自帶的View可以在xml中配置屬性霉涨,對(duì)于寫的好的自定義View同樣可以在xml中配置屬性按价,為了使自定義的View的屬性可以在xml中配置惭适,需要以下4個(gè)步驟:
1.通過(guò)<declare-styleable>
為自定義View添加屬性
2.在xml中為相應(yīng)的屬性聲明屬性值
3.在運(yùn)行時(shí)(一般為構(gòu)造函數(shù))獲取屬性值
4.將獲取到的屬性值應(yīng)用到View
View視圖結(jié)構(gòu)
1.PhoneWindow是Android系統(tǒng)中最基本的窗口系統(tǒng),繼承自Windows類楼镐,負(fù)責(zé)管理界面顯示以及事件響應(yīng)癞志。它是Activity與View系統(tǒng)交互的接口
2.DecorView是PhoneWindow中的起始節(jié)點(diǎn)View,繼承于View類框产,作為整個(gè)視圖容器來(lái)使用凄杯。用于設(shè)置窗口屬性。它本質(zhì)上是一個(gè)FrameLayout
3.ViewRoot在Activtiy啟動(dòng)時(shí)創(chuàng)建茅信,負(fù)責(zé)管理盾舌、布局、渲染窗口UI等等
一定要記住:無(wú)論是measure過(guò)程蘸鲸、layout過(guò)程還是draw過(guò)程妖谴,永遠(yuǎn)都是從View樹(shù)的根節(jié)點(diǎn)開(kāi)始測(cè)量或計(jì)算(即從樹(shù)的頂端開(kāi)始),一層一層酌摇、一個(gè)分支一個(gè)分支地進(jìn)行(即樹(shù)形遞歸)膝舅,最終計(jì)算整個(gè)View樹(shù)中各個(gè)View,最終確定整個(gè)View樹(shù)的相關(guān)屬性
Android坐標(biāo)系
- 屏幕的左上角為坐標(biāo)原點(diǎn)
- 向右為x軸增大方向
- 向下為y軸增大方向
View位置(坐標(biāo))描述
View的位置由4個(gè)頂點(diǎn)決定的窑多,4個(gè)頂點(diǎn)的位置描述分別由4個(gè)值決定:
請(qǐng)記兹韵 :View的位置是相對(duì)于父控件而言的)
- getTop():子View上邊界到父view上邊界的距離
- getLeft():子View左邊界到父view左邊界的距離
- getBottom():子View下邊距到父View上邊界的距離
- getRight():子View右邊界到父view左邊界的距離
與MotionEvent中 get()和getRaw()的區(qū)別,
event.getX(),觸摸點(diǎn)到自身左邊的距離
event.getRawX();觸摸點(diǎn)到父容器的左邊的距離埂息,垂直方向亦是如此
View樹(shù)的繪制流程
View樹(shù)的繪制流程是誰(shuí)負(fù)責(zé)的技潘?
view樹(shù)的繪制流程是通過(guò)ViewRoot去負(fù)責(zé)繪制的,ViewRoot這個(gè)類的命名有點(diǎn)坑千康,最初看到這個(gè)名字享幽,翻譯過(guò)來(lái)是view的根節(jié)點(diǎn),但是事實(shí)完全不是這樣拾弃,ViewRoot其實(shí)不是View的根節(jié)點(diǎn)值桩,它連view節(jié)點(diǎn)都算不上,它的主要作用是View樹(shù)的管理者豪椿,負(fù)責(zé)將DecorView和PhoneWindow“組合”起來(lái)奔坟,而View樹(shù)的根節(jié)點(diǎn)嚴(yán)格意義上來(lái)說(shuō)只有DecorView;每個(gè)DecorView都有一個(gè)ViewRoot與之關(guān)聯(lián)搭盾,這種關(guān)聯(lián)關(guān)系是由WindowManager去進(jìn)行管理的咳秉;
LayoutParams
layoutParams翻譯過(guò)來(lái)就是布局參數(shù),子View通過(guò)LayoutParams告訴父容器(ViewGroup)應(yīng)該如何放置自己增蹭。從這個(gè)定義中也可以看出來(lái)LayoutParams與ViewGroup是息息相關(guān)的滴某,因此脫離ViewGroup談LayoutParams是沒(méi)有意義的车伞。
事實(shí)上食店,每個(gè)ViewGroup的子類都有自己對(duì)應(yīng)的LayoutParams類版确,典型的如LinearLayout.LayoutParams和FrameLayout.LayoutParams等梢灭,可以看出來(lái)LayoutParams都是對(duì)應(yīng)ViewGroup子類的內(nèi)部類
MarginLayoutParams
MarginLayoutParams是和外間距有關(guān)的。事實(shí)也確實(shí)如此幕侠,和LayoutParams相比帝美,MarginLayoutParams只是增加了對(duì)上下左右外間距的支持。實(shí)際上大部分LayoutParams的實(shí)現(xiàn)類都是繼承自MarginLayoutParams晤硕,因?yàn)榛舅械母溉萜鞫际侵С肿覸iew設(shè)置外間距的
- 屬性優(yōu)先級(jí)問(wèn)題
MarginLayoutParams主要就是增加了上下左右4種外間距悼潭。在構(gòu)造方法中,先是獲取了margin屬性舞箍;如果該值不合法舰褪,就獲取horizontalMargin;如果該值不合法疏橄,再去獲取leftMargin和rightMargin屬性(verticalMargin占拍、topMargin和bottomMargin同理)。我們可以據(jù)此總結(jié)出這幾種屬性的優(yōu)先級(jí)
margin > horizontalMargin和verticalMargin > leftMargin和RightMargin捎迫、topMargin和bottomMargin
屬性覆蓋問(wèn)題
優(yōu)先級(jí)更高的屬性會(huì)覆蓋掉優(yōu)先級(jí)較低的屬性晃酒。
LayoutParams常見(jiàn)的子類
在為View設(shè)置LayoutParams的時(shí)候需要根據(jù)它的父容器選擇對(duì)應(yīng)的LayoutParams,否則結(jié)果可能與預(yù)期不一致窄绒,這里簡(jiǎn)單羅列一些常見(jiàn)的LayoutParams子類:
- ViewGroup.MarginLayoutParams
- FrameLayout.LayoutParams
- LinearLayout.LayoutParams
- RelativeLayout.LayoutParams
- RecyclerView.LayoutParams
- GridLayoutManager.LayoutParams
- StaggeredGridLayoutManager.LayoutParams
- ViewPager.LayoutParams
- WindowManager.LayoutParams
為什么要自定義View贝次?
自定義View的基本方法
自定義View的最基本的三個(gè)方法分別是:onMeasure、onLayout()彰导、onDraw蛔翅,View在Activity中顯示出來(lái)要經(jīng)歷測(cè)量、布局和繪制三個(gè)步驟位谋,分別對(duì)應(yīng)三個(gè)動(dòng)作:measure搁宾、layout、draw倔幼。
1.測(cè)量:onMeasure決定View的大小爽待;
2.布局:onLayout決定View在ViewGroup中的位置损同;
3.onDraw決定繪制這個(gè)View
自定義控件分類
1.自定義View:只需要重寫onMeasure和onDraw
2.自定義ViewGroup:則只需要重寫onMeasure和onLayout
自定義View基礎(chǔ)
View的分類
視圖View主要分兩類
單一視圖:即一個(gè)View,如TextView鸟款,不包含子View
視圖組:即多個(gè)View組成的ViewGroup膏燃,如LinearLayout,包含子View
View類的簡(jiǎn)介
1.View類是Android中各種組件的基類何什,如View是ViewGroup基類
2.View表現(xiàn)為顯示在屏幕上的各種視圖组哩,Android中的UI組件都由View、ViewGroup組成
3.View的構(gòu)造函數(shù):共由4個(gè)
//如果View是在Java代碼里面new的,則調(diào)用第一個(gè)構(gòu)造函數(shù)
public CustomView(Context context){
super(context);
}
//如果View是在.xml里聲明的伶贰,則調(diào)用第二個(gè)構(gòu)造函數(shù)
// 自定義屬性是從AttributeSet參數(shù)傳進(jìn)來(lái)的
public CustomView(Context context,AttributeSet attrs){
super(context,attrs);
}
// 不會(huì)自動(dòng)調(diào)用
// 一般是在第二個(gè)構(gòu)造函數(shù)里主動(dòng)調(diào)用
// 如View有style屬性時(shí)
public CustomView(Context context,AttributeSet attrs, int defStyleAttr){
super(context, attrs, defStyleAttr);
}
//API21之后才使用
// 不會(huì)自動(dòng)調(diào)用
// 一般是在第二個(gè)構(gòu)造函數(shù)里主動(dòng)調(diào)用
// 如View有style屬性時(shí)
public CustomView(Context context,AttributeSet attrs, int defStyleAttr, int defStyleRes){
super(context, attrs, defStyleAttr, defStyleRes);
}
AttributeSet與自定義屬性
系統(tǒng)自帶的View可以在xml中配置屬性蛛砰,對(duì)于寫得好得自定義View同樣可以在xml中配置屬性,為了使自定義的View的屬性可以在xml中配置黍衙,需要以下4個(gè)步驟
1.通過(guò)<declare-styleable>為自定義View添加屬性
2.在xml中為相應(yīng)的屬性聲明屬性值
3.在運(yùn)行時(shí)(一般為構(gòu)造函數(shù))獲取屬性值
4.將獲取到的屬性值應(yīng)用到View
View視圖結(jié)構(gòu)
1.PhoneWindow是Android系統(tǒng)中最基本的窗口系統(tǒng)泥畅,繼承自Windows類,負(fù)責(zé)管理界面顯示以及事件響應(yīng)琅翻。它是Activity與View系統(tǒng)交互的接口
2.DecorView是PhoneWindow中的其實(shí)節(jié)點(diǎn)View位仁,繼承自View類,作為整個(gè)視圖容器來(lái)使用方椎。用于設(shè)置窗口屬性聂抢,它本質(zhì)上是一個(gè)FrameLayout
3.ViewRoot在Activity啟動(dòng)時(shí)創(chuàng)建,負(fù)責(zé)管理棠众、布局琳疏、渲染窗口UI等等
對(duì)于多View的視圖,結(jié)構(gòu)是樹(shù)形結(jié)構(gòu):最頂層是ViewGroup,ViewGroup下可能有多個(gè)ViewGroup或View摄欲,如下圖:
一定要記捉瘟痢:無(wú)論是measure過(guò)程、layout過(guò)程還是draw過(guò)程胸墙,永遠(yuǎn)都是從View樹(shù)的根節(jié)點(diǎn)開(kāi)始測(cè)量或計(jì)算(即從樹(shù)的頂端開(kāi)始)我注,一層一層、一個(gè)分支一個(gè)分支的進(jìn)行(樹(shù)形遞歸)最終計(jì)算整個(gè)View樹(shù)中各個(gè)View迟隅,最終確定整個(gè)View樹(shù)的相關(guān)屬性但骨。
Android坐標(biāo)系
Android的坐標(biāo)系定義為:
1.屏幕的左上角為坐標(biāo)原點(diǎn)
2.向右為x軸增大方向
3.向下為y軸增大方向
區(qū)別一般的數(shù)學(xué)坐標(biāo)系
View位置(坐標(biāo))描述
View的位置由4個(gè)頂點(diǎn)決定的,4個(gè)頂點(diǎn)的位置描述分別由4個(gè)值決定:
(view的位置是相對(duì)于父控件而言的)
1.Top:子View的上邊界到父View上邊界的距離
2.Left:子View的左邊界到父View左邊界的距離
3.Bottom:子View下邊界到父View上邊界的距離
4.Right:子View右邊界到父View左邊界的距離
位置獲取方式
View的位置是通過(guò)view.getxxx()函數(shù)進(jìn)行獲取的:(以Top為例)
//獲取Top位置
public final int getTop(){return mTop;}
// 其余如下:
getLeft(); //獲取子View左上角距父View左側(cè)的距離
getBottom(); //獲取子View右下角距父View頂部的距離
getRight(); //獲取子View右下角距父View左側(cè)的距離
與MotionEvent中g(shù)et()h和getRaw()的區(qū)別
//get() :觸摸點(diǎn)相對(duì)于其所在組件坐標(biāo)系的坐標(biāo)
event.getX();
event.getY();
//getRaw() :觸摸點(diǎn)相對(duì)于屏幕默認(rèn)坐標(biāo)系的坐標(biāo)
event.getRawX();
event.getRawY();
Android中顏色相關(guān)內(nèi)容
Android支持的顏色模式:
以ARGB8888為例介紹顏色定義:
View樹(shù)的繪制流程
View樹(shù)的繪制流程是誰(shuí)負(fù)責(zé)的智袭?
view樹(shù)的繪制流程是通過(guò)ViewRoot去負(fù)責(zé)繪制的奔缠,ViewRoot這個(gè)類的命名看起來(lái)不怎么合理,最初看到這個(gè)名字吼野,翻譯過(guò)來(lái)是view的根節(jié)點(diǎn)校哎,但是事實(shí)完全不是這樣,ViewRoot其實(shí)不是view的根節(jié)點(diǎn)瞳步,它連view節(jié)點(diǎn)都算不上闷哆,它主要作用是view樹(shù)的管理者,負(fù)責(zé)將DecorView和PhoneWindow"組合"起來(lái)单起,而View樹(shù)的根節(jié)點(diǎn)嚴(yán)格意義上來(lái)說(shuō)只有DecorView抱怔;每個(gè)DecorView都有一個(gè)ViewRoot與之關(guān)聯(lián),這種關(guān)聯(lián)關(guān)系是由WindowManager去進(jìn)行管理的嘀倒。
view的添加
View的繪制流程
measure
1.系統(tǒng)為什么要有measure過(guò)程屈留?
2.measure過(guò)程都干了點(diǎn)什么事局冰?
3.對(duì)于自適應(yīng)的尺寸機(jī)制,如何合理的測(cè)量一顆view樹(shù)灌危?
4.那么ViewGroup是如何向子View傳遞限制信息的康二?
5.ScrollView嵌套ListView問(wèn)題?
layout
1.系統(tǒng)為什么要有l(wèi)ayout過(guò)程乍狐?
2.layout過(guò)程干了啥赠摇?
draw
1.系統(tǒng)為什么要有draw過(guò)程?
2.draw過(guò)程都干了啥浅蚪?
LayoutParams
layoutParams翻譯過(guò)來(lái)就是布局參數(shù)藕帜,子view通過(guò)LayoutParams告訴父容器(ViewGroup)應(yīng)該如何放置自己。從這個(gè)定義中也可以看出來(lái)LayoutParams與ViewGroup是息息相關(guān)的惜傲,因此脫離ViewGroup談LayoutParams是沒(méi)有意義的
事實(shí)上洽故,每個(gè)ViewGroup的子類都有自己對(duì)應(yīng)的LayoutParams類,典型的如LinearLayout.LayoutParams和FrameLayout.LayoutParams等盗誊,可以看出來(lái)LayoutParams都是對(duì)應(yīng)ViewGroup子類的內(nèi)部類
MarginLayoutParams
MarginLayoutParams是和外間距有關(guān)系的时甚,和LayoutParams相比,MarginLayoutParams只是對(duì)外增加了對(duì)上下左右外間距的支持哈踱。實(shí)際上大部分LayoutParams的實(shí)現(xiàn)類都是繼承自MarginLayoutParams荒适,因?yàn)榛舅械母溉萜鞫际侵С肿覸eiw設(shè)置外間距的
1.屬性優(yōu)先級(jí)問(wèn)題
MarginLayoutParams主要就是增加了上下左右4種外間距。在構(gòu)造方法中开镣,先是獲取了margin屬性刀诬;如果該值不合法,就獲取horizontalMargin邪财;如果該值不合法陕壹,再去獲取leftMargin和rightMargin屬性(verticalMargin、topMargin和bottomMargin同理)树埠。我們可以據(jù)此總結(jié)出這幾種屬性的優(yōu)先級(jí)
margin > horizontalMargin和verticalMargin > leftMargin和RightMargin糠馆、topMargin和bottomMargin
2.屬性覆蓋問(wèn)題
優(yōu)先級(jí)更高的屬性會(huì)覆蓋掉優(yōu)先級(jí)較低的屬性。此外怎憋,還要注意下這幾種屬性上的注釋
Call {@link ViewGroup#setLayoutParams(LayoutParams)} after reassigning a new value
LayoutParams與View如何建立聯(lián)系
1.在xml種定義View
2.在Java代碼中直接生成View對(duì)應(yīng)的實(shí)例對(duì)象
addView
**/\**
**
\* 重載方法1:添加一個(gè)子View
\* 如果這個(gè)子View還沒(méi)有LayoutParams又碌,就為子View設(shè)置當(dāng)前ViewGroup默認(rèn)的LayoutParams
*/
public void addView(View child) {
addView(child, -1);
}
/**
\* 重載方法2:在指定位置添加一個(gè)子View
\* 如果這個(gè)子View還沒(méi)有LayoutParams,就為子View設(shè)置當(dāng)前ViewGroup默認(rèn)的LayoutParams
\* @param index View將在ViewGroup中被添加的位置(-1代表添加到末尾)
*/
public void addView(View child, int index) {
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
LayoutParams params = child.getLayoutParams();
if (params == null) {
params = generateDefaultLayoutParams();// 生成當(dāng)前ViewGroup默認(rèn)的LayoutParams
if (params == null) {
throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
}
}
addView(child, index, params);
}
/**
\* 重載方法3:添加一個(gè)子View
\* 使用當(dāng)前ViewGroup默認(rèn)的LayoutParams绊袋,并以傳入?yún)?shù)作為L(zhǎng)ayoutParams的width和height
*/
public void addView(View child, int width, int height) {
final LayoutParams params = generateDefaultLayoutParams(); // 生成當(dāng)前ViewGroup默認(rèn)的LayoutParams
params.width = width;
params.height = height;
addView(child, -1, params);
}
/**
\* 重載方法4:添加一個(gè)子View赠橙,并使用傳入的LayoutParams
*/
@Override
public void addView(View child, LayoutParams params) {
addView(child, -1, params);
}
/**
\* 重載方法4:在指定位置添加一個(gè)子View,并使用傳入的LayoutParams
*/
public void addView(View child, int index, LayoutParams params) {
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
private void addViewInner(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
.....
if (mTransition != null) {
mTransition.addChild(this, child);
}
if (!checkLayoutParams(params)) { // ① 檢查傳入的LayoutParams是否合法
params = generateLayoutParams(params); // 如果傳入的LayoutParams不合法愤炸,將進(jìn)行轉(zhuǎn)化操作
}
if (preventRequestLayout) { // ② 是否需要阻止重新執(zhí)行布局流程
child.mLayoutParams = params; // 這不會(huì)引起子View重新布局(onMeasure->onLayout->onDraw)
} else {
child.setLayoutParams(params); // 這會(huì)引起子View重新布局(onMeasure->onLayout->onDraw)
}
if (index < 0) {
index = mChildrenCount;
}
addInArray(child, index);
// tell our children
if (preventRequestLayout) {
child.assignParent(this);
} else {
child.mParent = this;
}
.....
}
自定義LayoutParams
1.創(chuàng)建自定義屬性
<resources>
<declare-styleable name="xxxViewGroup_Layout">
<!-- 自定義的屬性 -->
<attr name="layout_simple_attr" format="integer"/>
<!-- 使用系統(tǒng)預(yù)置的屬性 -->
<attr name="android:layout_gravity"/>
</declare-styleable>
</resources>
2.繼承MarginLayout
public static class LayoutParams extends ViewGroup.MarginLayoutParams {
public int simpleAttr;
public int gravity;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
// 解析布局屬性
TypedArray typedArray = c.obtainStyledAttributes(attrs, R.styleable.SimpleViewGroup_Layout);
simpleAttr = typedArray.getInteger(R.styleable.SimpleViewGroup_Layout_layout_simple_attr, 0);
gravity=typedArray.getInteger(R.styleable.SimpleViewGroup_Layout_android_layout_gravity, -1);
typedArray.recycle();//釋放資源
}
public LayoutParams(int width, int height) {
super(width, height);
}
public LayoutParams(MarginLayoutParams source) {
super(source);
}
public LayoutParams(ViewGroup.LayoutParams source) {
super(source);
}
}
3.重寫ViewGroup中幾個(gè)與LayoutParams相關(guān)的方法
// 檢查L(zhǎng)ayoutParams是否合法
@Override
protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
return p instanceof SimpleViewGroup.LayoutParams;
}
// 生成默認(rèn)的LayoutParams
@Override
protected ViewGroup.LayoutParams generateDefaultLayoutParams() {
return new SimpleViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
// 對(duì)傳入的LayoutParams進(jìn)行轉(zhuǎn)化
@Override
protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
return new SimpleViewGroup.LayoutParams(p);
}
// 對(duì)傳入的LayoutParams進(jìn)行轉(zhuǎn)化
@Override
public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {
return new SimpleViewGroup.LayoutParams(getContext(), attrs);
}
LayoutParams常見(jiàn)的子類
在為View設(shè)置layoutParams的時(shí)候要根據(jù)他的父容器選擇對(duì)應(yīng)的LayoutParams,否則結(jié)果可能與預(yù)期不一致掉奄,這里簡(jiǎn)單羅列一些常見(jiàn)的LayoutParams子類:
ViewGroup.MarginLayoutParams
FrameLayout.LayoutParams
LinearLayout.LayoutParams
RelativeLayout.LayoutParams
RecyclerView.LayoutParams
GridLayoutManager.LayoutParams
StaggeredGridLayoutManager.LayoutParams
ViewPager.LayoutParams
WindowManager.LayoutParams
MeasureSpec
定義
測(cè)量規(guī)格规个,封裝了父容器對(duì)View的布局上的限制凤薛,內(nèi)部提供了寬高的信息(SpecMode、SpecSize)诞仓,SpecSize是指在某種SpecMode下的參考尺寸缤苫,其中SpecMode有如下三種:
1.UNSPECIFIED:父控件不對(duì)你有任何限制,你想要多大給你多大墅拭,這種情況一般用于系統(tǒng)內(nèi)部活玲,表示一種測(cè)量狀態(tài)。(這個(gè)模式主要用于系統(tǒng)內(nèi)部多次Measure的情形谍婉,并不是想要多大就有多大)
2.EXACTLY:父控件已經(jīng)知道你所需要的精確大小舒憾,你的最終大小就應(yīng)該是這么大
3.:AT_MOST:你的大小不能大于父控件給你指定的size,但具體是多少穗熬,得看你自己的實(shí)現(xiàn)
MeasureSpecs的意義
通過(guò)將SpecMode和SpecSize打包成一個(gè)int值可以避免過(guò)多的內(nèi)存分配镀迂,為了方便操作,其提供了打包/解包的方法
MeaureSpec值的確定
MeasureSpec值到底是如何計(jì)算得來(lái)的呢唤蔗?
子View的MeasureSpec值是根據(jù)子View的布局參數(shù)(LayoutParams)和父容器的MeasureSpec值計(jì)算得來(lái)的探遵,具體計(jì)算邏輯封裝在getChildMeasureSpec()里
/**
*
\* 目標(biāo)是將父控件的測(cè)量規(guī)格和child view的布局參數(shù)LayoutParams相結(jié)合,得到一個(gè)
\* 最可能符合條件的child view的測(cè)量規(guī)格妓柜。
\* @param spec 父控件的測(cè)量規(guī)格
\* @param padding 父控件里已經(jīng)占用的大小
\* @param childDimension child view布局LayoutParams里的尺寸
\* @return child view 的測(cè)量規(guī)格
*/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec); //父控件的測(cè)量模式
int specSize = MeasureSpec.getSize(spec); //父控件的測(cè)量大小
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// 當(dāng)父控件的測(cè)量模式 是 精確模式箱季,也就是有精確的尺寸了
case MeasureSpec.EXACTLY:
//如果child的布局參數(shù)有固定值,比如"layout_width" = "100dp"
//那么顯然child的測(cè)量規(guī)格也可以確定下來(lái)了棍掐,測(cè)量大小就是100dp藏雏,測(cè)量模式也是EXACTLY
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
}
//如果child的布局參數(shù)是"match_parent",也就是想要占滿父控件
//而此時(shí)父控件是精確模式塌衰,也就是能確定自己的尺寸了诉稍,那child也能確定自己大小了
else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
}
//如果child的布局參數(shù)是"wrap_content",也就是想要根據(jù)自己的邏輯決定自己大小最疆,
//比如TextView根據(jù)設(shè)置的字符串大小來(lái)決定自己的大小
//那就自己決定唄杯巨,不過(guò)你的大小肯定不能大于父控件的大小嘛
//所以測(cè)量模式就是AT_MOST,測(cè)量大小就是父控件的size
else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 當(dāng)父控件的測(cè)量模式 是 最大模式努酸,也就是說(shuō)父控件自己還不知道自己的尺寸服爷,但是大小不能超過(guò)size
case MeasureSpec.AT_MOST:
//同樣的,既然child能確定自己大小获诈,盡管父控件自己還不知道自己大小仍源,也優(yōu)先滿足孩子的需求
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
}
//child想要和父控件一樣大,但父控件自己也不確定自己大小舔涎,所以child也無(wú)法確定自己大小
//但同樣的笼踩,child的尺寸上限也是父控件的尺寸上限size
else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
//child想要根據(jù)自己邏輯決定大小,那就自己決定唄
else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
針對(duì)上表亡嫌,這里在做一下說(shuō)明
1.對(duì)于應(yīng)用層View嚎于,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams來(lái)共同決定
2.對(duì)于不同的父容器和View本身不同的LayoutParams掘而,view就可以由多種MeasureSpec
(1)當(dāng)view采用固定寬高時(shí),不管父容器的MeasureSpec是什么于购,view的MeasureSpec都是精確模式并且其大小遵循LayoutParams中的大小
(2)當(dāng)view的寬高是match_parent時(shí)袍睡,這時(shí)候如果父容器的模式是精確模式,那么view也是精確模式并且其大小是父容器的剩余空間肋僧,如果父容器是最大模式斑胜,那么view也是最大模式并且其大小不會(huì)超過(guò)父容器的剩余空間
(3)當(dāng)view的寬高是wrap_content時(shí),不管父容器的模式是精準(zhǔn)還是最大化嫌吠,view的模式總是最大化并且大小不能超過(guò)父容器的剩余空間
(4)Unspecified模式止潘,這個(gè)模式主要用于系統(tǒng)內(nèi)部多次measure的情況,一般來(lái)說(shuō)居兆,我們不需要關(guān)注此模式(這里注意自定義View放到ScrollView的情況需要處理)
自定義View的開(kāi)發(fā)
函數(shù)主要作用是請(qǐng)求View樹(shù)進(jìn)行重繪覆山,該函數(shù)可以由應(yīng)用程序調(diào)用,或者由系統(tǒng)函數(shù)間接調(diào)用泥栖,例如setEnable(),setSelected(),setVisiblity()都會(huì)間接調(diào)用到invalidate()來(lái)請(qǐng)求View樹(shù)重繪簇宽,更新View樹(shù)的顯示。
Paint常用方法
Paint主要用于設(shè)置繪制風(fēng)格:包括畫筆的顏色吧享、粗細(xì)魏割、填充風(fēng)格及文字的特征
Canvas繪制
1.繪制幾何圖像
canvas.drawArc (扇形)
canvas.drawCircle(圓)
canvas.drawOval(橢圓)
canvas.drawLine(線)
canvas.drawPoint(點(diǎn))
canvas.drawRect(矩形)
canvas.drawRoundRect(圓角矩形)
canvas.drawVertices(頂點(diǎn))
cnavas.drawPath(路徑)
2.繪制圖片
canvas.drawBitmap (位圖)
canvas.drawPicture (圖片)
3.文本
canvas.drawText
Canvas的變換操作
Canvas還提供了一系列位置轉(zhuǎn)換的方法:rorate、scale钢颂、translate钞它、skew(扭曲)等。
Canvas的保存和回滾
Canvas還提供了保存和回滾屬性的方法(save和restore)殊鞭,比如你可以先保存目前畫布的位置(save),然后旋轉(zhuǎn)90度遭垛,向下移動(dòng)100像素后畫一些圖形,畫完后調(diào)用restore方法回到剛才保存的位置
屏幕顯示與Canvas的關(guān)系
Canvas是一個(gè)很虛幻的概念操灿,相當(dāng)于一個(gè)透明圖層每次Canvas畫圖時(shí)(即調(diào)用Draw系列函數(shù))锯仪,都會(huì)產(chǎn)生一個(gè)透明圖層,然后在這個(gè)圖層上畫圖趾盐,畫完之后覆蓋在屏幕上顯示庶喜。
文字繪制
基準(zhǔn)點(diǎn)是baseline
Ascent是baseline之上至字符最高處的距離
Descent是baseline之下至字符最低處的距離
Leading文檔說(shuō)的很含糊,其實(shí)是上一行字符的descent到下一行的ascent之間的距離
Top指的是指的是最高字符到baseline的值救鲤,即ascent的最大值
bottom指的是最下字符到baseline的值久窟,即descent的最大值
圓形頭像繪制
AvoidXfermode 指定了一個(gè)顏色和容差,強(qiáng)制Paint避免在它上面繪圖(或者只在它上面繪圖)本缠。
PixelXorXfermode 當(dāng)覆蓋已有的顏色時(shí)斥扛,應(yīng)用一個(gè)簡(jiǎn)單的像素XOR操作。
PorterDuffXfermode 這是一個(gè)非常強(qiáng)大的轉(zhuǎn)換模式丹锹,使用它犹赖,可以使用圖像合成的16條Porter-Duff規(guī)則的任意一條來(lái)控制Paint如何與已有的Canvas圖像進(jìn)行交互
矩陣的set方法
Matrix.reset()
Matrix.setValues(float[] values)
Matrix.setTranslate(float dx, float dy)
Matrix.setRotate(float degrees)
Matrix.setRotate(float degrees, float px, float py)
Matrix.setScale(float sx, float sy)
Matrix.setScale(float sx, float sy, float px, float py)
Matrix.setSkew(float kx, float ky)
Matrix.setSkew(float kx, float ky, float px, float py)
set方法一旦調(diào)用即會(huì)清空之前matrix中的所有變換
矩陣的post方法
Matrix.postConcat(Matrix other) // M' = other * M
Matrix.postTranslate(float dx, float dy) // M' = T(dx, dy) * M
Matrix.postRotate(float degrees) // M' = R(degrees) * M
Matrix.postRotate(float degrees, float px, float py) // M' = R(degrees, px, py) * M
Matrix.postScale(float sx, float sy) // M' = S(sx, sy) * M
Matrix.postScale(float sx, float sy, float px, float py) // M' = S(sx, sy, px, py) * M
Matrix.postSkew(float kx, float ky) // M' = K(kx, ky) * M
Matrix.postSkew(float kx, float ky, float px, float py) // M' = K(kx, ky, px, py) * M
post方法表示矩陣后乘
例如:變換矩陣為A队他,原始矩陣為B,post方法的含義即是B*A
矩陣的pre方法
Matrix.preConcat(Matrix other) // M' = M * other
Matrix.preTranslate(float dx, float dy) // M' = M * T(dx, dy)
Matrix.preRotate(float degrees) // M' = M * R(degrees)
Matrix.preRotate(float degrees, float px, float py) // M * M' = R(degrees, px, py)
Matrix.preScale(float sx, float sy) // M' = M * S(sx, sy)
Matrix.preScale(float sx, float sy, float px, float py) // M' = M * S(sx, sy, px, py)
Matrix.preSkew(float kx, float ky) // M' = M * K(kx, ky)
Matrix.preSkew(float kx, float ky, float px, float py) // M' = M * K(kx, ky, px, py)
pre方法表示矩陣前乘峻村,
例如:變換矩陣為A,原始矩陣為B锡凝,pre方法的含義即是A*B
自定義Drawable繪制
draw(Canvas canvas)
setBounds(int left, int top, int right, int bottom)
invalidateSelf()
自 定義View中畫Drawable時(shí)粘昨,要先給它設(shè)置位置
mDrawable.setBounds(100, 100, 100 + mAnimationDrawable.getIntrinsicWidth(),
100 +mAnimationDrawable.getIntrinsicHeight());
這樣他才能正確的畫在canvas的對(duì)應(yīng)位置。