View框架浅析
參考文章如下,這幾篇文章很好,圖文并茂,我這里只是取了一些原文的概念放到這里方便復(fù)習(xí):
http://www.jianshu.com/p/a3014f8442b0
整體View結(jié)構(gòu)
控件主要分為兩類,一類是View,一類是ViewGroup
如下是View的一些原理
- 所有的View都是矩形的
- View是不能添加子View的,ViewGroup可以
- Activity之所以能加載并且控制View,是因?yàn)樗艘粋€(gè)Window,所有的圖形化界面都是由View顯示的而Service之所以稱之為沒有界面的activity是因?yàn)樗话蠾indow,不能夠加載View;
- 一個(gè)View有且只能有一個(gè)父View;
- 在Android中Window對(duì)象通常由PhoneWindow來實(shí)現(xiàn)的,PhoneWindow將一個(gè)DecorView設(shè)置為整個(gè)應(yīng)用窗口的根View,即DecorView為整個(gè)Window界面的最頂層View。
- DecorView是FrameLayout的子類,它繼承了FrameLayout,即頂層的FrameLayout的實(shí)現(xiàn)類是Decorview,它是在phoneWindow里面創(chuàng)建的;
- 頂層的FrameLayout的父view是Handler,Handler的作用除了線程之間的通訊以外,還可以跟WindowManagerService進(jìn)行通訊;windowManagerService是后臺(tái)的一個(gè)服務(wù),它控制并且管理者屏幕;
- 一個(gè)應(yīng)用可以有很多個(gè)window,其由windowManager來管理,而windowManager又由windowManagerService來管理;
Measure測(cè)量一個(gè)View的大小
- 1、MeasureSpe描述了父View對(duì)子View大小的期望。里面包含了測(cè)量模式和大小。
- 2、MeasureSpe類把測(cè)量模式和大小組合到一個(gè)32位的int型的數(shù)值中,其中高2位表示模式,低30位表示大小而在計(jì)算中使用位運(yùn)算的原因是為了提高并優(yōu)化效率。
- 3、Android中提供了三種測(cè)量模式,EXACTLY 精確模式,AT_MOST最大值模式,UNSPECIFIED不確定模式。默認(rèn)的是去定的模式。
- 4、系統(tǒng)最終會(huì)調(diào)用setMeasureDimension(int measuredWidth, int measuredHeight)方法將測(cè)量后的寬高設(shè)置進(jìn)去,從而完成測(cè)量工作。
- 5、當(dāng)我們需要自定義的時(shí)候,需要自定義的measureWidth()方法和measureHeight()方法對(duì)寬高進(jìn)行了重新定義。從MeasureSpec對(duì)象中獲取到測(cè)量模式和測(cè)量大小值,通過判斷測(cè)量模式,返回不同的測(cè)量值。
Layout擺放一個(gè)View的位置
- 1、首先是layout函數(shù), Layout方法中接受四個(gè)參數(shù),是由父View提供,指定了子View在父View中的左、上、右、下的位置。父View在指定子View的位置時(shí)通常會(huì)根據(jù)子View在measure中測(cè)量的大小來決定。
- 2、子View的位置通常還受有其他屬性左右,例如父View的orientation,gravity,自身的margin等等,特別是RelativeLayout,影響布局的因素非常多。
- 3、layout和onLayout之間有一個(gè)setFrame方法。setFrame方法是一個(gè)隱藏方法,所以作為應(yīng)用層程序員來說,無法重寫該方法。該方法體內(nèi)部通過比對(duì)本次的l、t、r、b四個(gè)值與上次是否相同來判斷自身的位置和大小是否發(fā)生了改變。如果發(fā)生了改變,將會(huì)調(diào)用invalidate請(qǐng)求重繪。
- 4、onLayout是ViewGroup用來決定子View擺放位置的,各種布局的差異都在該方法中得到了體現(xiàn)。
Draw畫出View的顯示內(nèi)容
View的繪制也是遵循一定的順序:
- 1、畫背景
- 2、畫邊緣
- 3、畫自身: ondraw方法
- 4、畫子View: dispatchDraw方法
- 5、畫滾動(dòng)條
draw()->onDraw()->dispatchDraw()
- 1、draw是由ViewRoot的performTraversals方法發(fā)起,它將調(diào)用DecorView的draw方法,并把成員變量canvas傳給給draw方法。而在后面draw遍歷中,傳遞的都是同一個(gè)canvas。所以android的繪制是同一個(gè)window中的所有View都繪制在同一個(gè)畫布上。
- 2、onDraw()方法的使用,因?yàn)槲覀兊哪康木褪亲远xView,所以當(dāng)我們測(cè)量好了一個(gè)View之后,我們就可以間的重寫onDraw()這個(gè)方法,并在Canvas對(duì)象上來繪制所需要的圖形。在onDraw()中就有一個(gè)參數(shù),該參數(shù)就是Canvas canvas對(duì)象,使用這個(gè)對(duì)象即可進(jìn)行繪圖操作。
- 3、之所以要傳入一個(gè)bitmap,是因?yàn)閭鬟M(jìn)來的bitmap與通過這個(gè)bitmap創(chuàng)建的Canvas畫布是緊緊聯(lián)系在一起的,這個(gè)過程稱之為裝載畫布。
- 4、dispatchDraw
先根據(jù)自身的padding剪裁畫布,所有的子View都將在畫布剪裁后的區(qū)域繪制。遍歷所有子View,調(diào)用子View的computeScroll對(duì)子View的滾動(dòng)值進(jìn)行計(jì)算。根據(jù)滾動(dòng)值和子View在父View中的坐標(biāo)進(jìn)行畫布原點(diǎn)坐標(biāo)的移動(dòng),根據(jù)子在父View中的坐標(biāo)計(jì)算出子View的視圖大小,然后對(duì)畫布進(jìn)行剪裁。
View框架的measure機(jī)制
1.mesure干了什么
Android中View有自使用的機(jī)制,把各種尺寸值,經(jīng)過計(jì)算,得到具體的像素值。measure過程會(huì)遍歷整棵View樹,然后依次測(cè)量每個(gè)View真實(shí)的尺寸。具體是每個(gè)ViewGroup會(huì)向它內(nèi)部的每個(gè)子View發(fā)送measure命令,然后由具體子View的onMeasure()來測(cè)量自己的尺寸。最后測(cè)量的結(jié)果保存在View的mMeasuredWidth和mMeasuredHeight中,保存的數(shù)據(jù)單位是像素。
2.如何合理的測(cè)量一顆View樹
一個(gè)View需要把它內(nèi)部的match_parent或者wrap_content轉(zhuǎn)換成具體的像素值。
在measure過程中,ViewGroup會(huì)根據(jù)自己當(dāng)前的狀況,結(jié)合子View的尺寸數(shù)據(jù),進(jìn)行一個(gè)綜合評(píng)定,然后把相關(guān)信息告訴子View,然后子View在onMeasure自己的時(shí)候,一邊需要考慮到自己的content大小,一邊還要考慮的父布局的限制信息,然后綜合評(píng)定,測(cè)量出一個(gè)最優(yōu)的結(jié)果。
3.ViewGroup是如何向子View傳遞限制信息
談到傳遞限制信息,那就是MeasureSpec類了,該類貫穿于整個(gè)measure過程,用來傳遞父布局對(duì)子View尺寸測(cè)量的約束信息。簡(jiǎn)單來說,該類就保存兩類數(shù)據(jù)。
1、子View當(dāng)前所在父布局的具體尺寸。
2、父布局對(duì)子View的限制類型。
還是包括那三種類型精確、最大和適應(yīng)
4.源代碼的分析
我們知道,整棵View樹的根節(jié)點(diǎn)是DecorView,它是一個(gè)FrameLayout,所以它是一個(gè)ViewGroup,所以整棵View樹的測(cè)量是從一個(gè)ViewGroup對(duì)象的measure方法開始的。
View的測(cè)量過程
measure->onMeasure->setMeasuredDimension
(1)measure
該方法會(huì)調(diào)用onMeasure()方法,所以只有onMeasure能被也必須要被override。public final void measure(int widthMeasureSpec, int heightMeasureSpec);
父布局會(huì)在自己的onMeasure方法中,調(diào)用child.measure ,這就把measure過程轉(zhuǎn)移到了子View中。
(2)onMeasure
具體測(cè)量過程,測(cè)量view和它的內(nèi)容,來決定測(cè)量的寬高(mMeasuredWidth mMeasuredHeight )。該方法中必須要調(diào)用setMeasuredDimension(int, int)來保存該view測(cè)量的寬高。
(3)setMeasuredDimension
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight);
當(dāng)View測(cè)量結(jié)束后,把測(cè)量結(jié)果保存起來,具體保存在mMeasuredWidth和mMeasuredHeight中。
ViewGroup的測(cè)量過程
measureChildren->measureChild->measureChildWithMargins->getChildMeasureSpec
(1)measureChildren
讓所有子view測(cè)量自己的尺寸,需要考慮當(dāng)前ViewGroup的MeasureSpec和Padding。跳過狀態(tài)為gone的子view
(2)measureChild
測(cè)量單個(gè)View,需要考慮當(dāng)前ViewGroup的MeasureSpec和Padding。
(3)measureChildWithMargins
測(cè)量單個(gè)View,需要考慮當(dāng)前ViewGroup的MeasureSpec和Padding、margins。
(4)getChildMeasureSpec
measureChildren過程中最困難的一部分,為child計(jì)算MeasureSpec。該方法為每個(gè)child的每個(gè)維度(寬、高)計(jì)算正確的MeasureSpec。目標(biāo)就是把當(dāng)前viewgroup的MeasureSpec和child的LayoutParams結(jié)合起來,生成最合理的結(jié)果。
getChildMeasureSpec的過程分析
根據(jù)當(dāng)前自身的狀況,以及特定子View的尺寸參數(shù),為特定子View計(jì)算一個(gè)合理的限制信息
- 首先判斷限定信息的模式
- 如果是精確的模式,如果子容器申請(qǐng)的是固定尺寸,就用這個(gè)固定尺寸,如果子容器希望和父容器一樣大,就使用父容器的尺寸,還有就殺包裹內(nèi)容啦。
- 最大尺寸模式
- 當(dāng)前容器尺寸不限定模式
自定義View控件
需要覆寫onMeasure來正確測(cè)量自己。最后都需要調(diào)用setMeasuredDimension來保存測(cè)量結(jié)果
一般來說,自定義View的measure過程偽代碼為:
- 首先根據(jù)measureSpc來獲得mode和size
- 根據(jù)不同的模式來設(shè)置不同的值,如果當(dāng)前是精確模式直接設(shè)置大小即可,如果是最大值模式,就設(shè)置為內(nèi)容尺寸和父布局尺寸的最小值。
如果是不限定模式,那么當(dāng)前值有多大就設(shè)置為多大。 - 然后通過setMeasureDimension(viewSize)來設(shè)置
自定義ViewGroup控件
不但需要覆寫onMeasure來正確測(cè)量自己,可能還要覆寫一系列measureChild方法,來正確的測(cè)量子view,比如ScrollView。或者干脆放棄父類實(shí)現(xiàn)的measureChild規(guī)則,自己重新實(shí)現(xiàn)一套測(cè)量子view的規(guī)則,比如RelativeLayout。最后都需要調(diào)用setMeasuredDimension來保存測(cè)量結(jié)果。
- ViewGroup開始測(cè)量自己的尺寸
- ViewGroup為每個(gè)Child計(jì)算限定信息
- 將上一步的限定信息傳遞給子View,然后子View需要measure自己的尺寸
- 子View測(cè)量完成后,ViewGroup就可以獲取每個(gè)子View測(cè)量后的尺寸
- ViewGroup會(huì)根據(jù)自己的狀況計(jì)算自己的尺寸
- ViewGroup保存自己的尺寸
View框架的layout機(jī)制
什么是layout過程
就是給View找到合適的位置
該位置是View相對(duì)于父布局坐標(biāo)系的相對(duì)位置,而不是以屏幕坐標(biāo)系為準(zhǔn)的絕對(duì)位置。這樣更容易保持樹型結(jié)構(gòu)的遞歸性和內(nèi)部自治性。而View的位置,可以無限大,超出當(dāng)前ViewGroup的可視范圍,這也是通過改變View位置而實(shí)現(xiàn)滑動(dòng)效果的原理。
layout過程干了什么
由于View是以樹結(jié)構(gòu)進(jìn)行存儲(chǔ),所以典型的數(shù)據(jù)操作就是遞歸操作,所以,View框架中,采用了內(nèi)部自治的layout過程。
每個(gè)葉子節(jié)點(diǎn)根據(jù)父節(jié)點(diǎn)傳遞過來的位置信息,設(shè)置自己的位置數(shù)據(jù),每個(gè)非葉子節(jié)點(diǎn),除了負(fù)責(zé)根據(jù)父節(jié)點(diǎn)傳遞過來的位置信息,設(shè)置自己的位置數(shù)據(jù)外(如果有父節(jié)點(diǎn)的話),還需要根據(jù)自己內(nèi)部的layout規(guī)則(比如垂直排布等),計(jì)算出每一個(gè)子節(jié)點(diǎn)的位置信息,然后向子節(jié)點(diǎn)傳遞layout過程。
對(duì)于ViewGroup,除了根據(jù)自己的parent傳遞的位置信息,來設(shè)置自己的位置之外,還需要根據(jù)自己的layout規(guī)則,為每一個(gè)子View計(jì)算出準(zhǔn)確的位置(相對(duì)于子View的父布局的位置)。
View對(duì)象的位置信息,在內(nèi)部是以4個(gè)成員變量的保存的,分別是mLeft、mRight、mTop、mBottom。他們的含義如圖所示。
源代碼
protected void onLayout(boolean changed, int left, int top, int right, int bottom);
ViewGroup中,只需要覆寫onLayout方法,來計(jì)算出每一個(gè)子View的位置,并且把layout流程傳遞給子View。
- 在onLayout中首先遍歷子View
- 然后在遍歷的for循環(huán)中計(jì)算每一個(gè)子View的位置信息
- 計(jì)算的規(guī)則包括當(dāng)前布局規(guī)則/子View 的測(cè)量尺寸/子View所在的位置索引
- 通過child.layout來設(shè)置上面的位置信息
結(jié)論
一般來說,自定義View,如果該View不包含子View,類似于TextView這種的,是不需要覆寫onLayout方法的。而含有子View的,比如LinearLayout這種,就需要根據(jù)自己的布局規(guī)則,來計(jì)算每一個(gè)子View的位置。
View框架的draw
什么是layout過程
View框架中,draw過程主要是繪制View的外觀。ViewGroup除了負(fù)責(zé)繪制自己之外,還需要負(fù)責(zé)繪制所有的子View。而不含子View的View對(duì)象,就負(fù)責(zé)繪制自己就可以了。
draw過程的主要流程
- 繪制 backgroud(drawBackground)
- 如果需要的話,保存canvas的layer,來準(zhǔn)備fading(不是必要的步驟)
- 繪制view的content(onDraw方法)
- 繪制children(dispatchDraw方法)
- 如果需要的話,繪制fading edges,然后還原layer(不是必要的步驟)
- 繪制裝飾器、比如scrollBar(onDrawForeground)
源代碼
我們知道,整棵View樹的根節(jié)點(diǎn)是DecorView,它是一個(gè)FrameLayout,所以它是一個(gè)ViewGroup,所以整棵View樹的測(cè)量是從一個(gè)ViewGroup對(duì)象的draw方法開始的。
下面是draw方法的流程
- 繪制background
- 繪制View的content
- 繪制children(dispatchView)
- 繪制裝飾器
總結(jié)
- 上一篇: asp.net配置文件connectio
- 下一篇: Wonderware-InTouch 历