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