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