androidview測量
⑴ Android 自定義View
1.直接在XML文件中定義的 ==》布局文件。
2.在XML文件中通過style這個屬性定義的 ==》在布局中使用自定義屬性樣式。
3.通過defStyleAttr定義的 ==》在View的構造方法中使用自定義屬性樣式。
4.通過defStyleRes定義的 ==》在View的構造方法中使用自定義樣式。
5.直接在當然工程的theme主題下定義的 ==》AndroidManifest.xml中設置。
1、onMeasure 測量自身,自定義View時重寫,定義控制項的寬高,常在自定義的View中使用
2、Measure 測量自身,方法不可重寫,內部調用onMeasure方法,常在自定義的ViewGroup中使用
3、measureChild 測量某個子View,內部調用Measure方法,常在自定義的ViewGroup中使用
4、measureChildren 測量所有子View,內部調用measureChild方法,常在自定義的ViewGroup中使用
在自定義View的開發中,我們重寫測量方法,方法里的傳參(widthMeasureSpec,heightMeasureSpec)都是由父類提供的,在自定義ViewGroup的開發中,我們可以根據當前布局的測量參數,為布局內的子控制項創建新的測量參數,來控制子View在布局的顯示大小
1、layout:指定View新的顯示位置,用法:view.layout(left,top,right,bottom);
2、onLayout:設置View的顯示位置,用法:重寫該方法,定義View的顯示規則
3、requestLayout:強制View重新布局,用法:view.requestLayout();
onFinishInflate -> onAttachedToWindow -> onMeasure -> onSizeChanged -> onLayout -> onDraw -> onDetachedFromWindow
Android的事件分發可以理解為向下分發,向上回傳,類似V字型,V字的左邊是事件進行向下分發,如果途中沒有進行事件的分發攔截,則事件傳遞到最底層的View,即是最接近屏幕的View。V字的右邊是事件的回傳,如果中途沒有進行事件的消費,則事件傳遞到最頂層的View,直至消失。
⑵ android view測量機制有哪幾種
Android自上而下對所有View進行量算,這樣Android就知道了每個View想要的尺寸大小,即寬高信息
在完成了對所有View的量算工作後,Android會自上而下對所有View進行布局,Android就知道了每個View在其父控制項中的位置,即View到其父控制項四邊的left、right、top、bottom
在完成了對所有View的布局工作後,Android會自上而下對所有View進行繪圖,這樣Android就將所有的View渲染到屏幕上了
以下是涉及到的相關類的源碼:
⑶ Android自定義View
View的構造函數:共有4個
系統自帶的View可以在xml中配置屬性,對於寫的好的自定義View同樣可以在xml中配置屬性,為了使自定義的View的屬性可以在xml中配置,需要以下4個步驟:
一定要記住:無論是measure過程、layout過程還是draw過程,永遠都是從View樹的根節點開始測量或計算(即從樹的頂端開始),一層一層、一個分支一個分支地進行(即樹形遞歸),最終計算整個View樹中各個View,最終確定整個View樹的相關屬性。
Android的坐標系定義為:
View的位置由4個頂點決定的 4個頂點的位置描述分別由4個值決定:
View的位置是通過view.getxxx()函數進行獲取:(以Top為例)
與MotionEvent中 get()和getRaw()的區別
MarginLayoutParams是和外間距有關的。事實也確實如此,和LayoutParams相比,MarginLayoutParams只是增加了對上下左右外間距的支持。實際上大部分LayoutParams的實現類都是繼承自MarginLayoutParams,因為基本所有的父容器都是支持子View設置外間距的。
1. 創建自定義屬性
2. 繼承MarginLayout
3. 重寫ViewGroup中幾個與LayoutParams相關的方法
在為View設置LayoutParams的時候需要根據它的父容器選擇對應的LayoutParams,否則結果可能與預期不一致,這里簡單羅列一些常見的LayoutParams子類:
測量規格,封裝了父容器對 view 的布局上的限制,內部提供了寬高的信息( SpecMode 、 SpecSize ),SpecSize是指在某種SpecMode下的參考尺寸,其中SpecMode 有如下三種:
針對上表,這里再做一下具體的說明
一般getIntrinsicWidth/Height能獲得內部寬/高 圖片Drawable其內部寬高就是圖
片的寬高 顏色Drawable沒有內部寬高的概念 內部寬高不等同於它的大小,一般
Drawable沒有大小概念(作為View背景時,會被拉伸至View的大小)
⑷ Android - View 繪制流程
我們知道,在 Android 中,View 繪制主要包含 3 大流程:
Android 中,主要有兩種視圖: View 和 ViewGroup ,其中:
雖然 ViewGroup 繼承於 View ,但是在 View 繪制三大流程中,某些流程需要區分 View 和 ViewGroup ,它們之間的操作並不完全相同,比如:
對 View 進行測量,主要包含兩個步驟:
對於第一個步驟,即求取 View 的 MeasureSpec ,首先我們來看下 MeasureSpec 的源碼定義:
MeasureSpec 是 View 的一個公有靜態內部類,它是一個 32 位的 int 值,高 2 位表示 SpecMode(測量模式),低 30 位表示 SpecSize(測量尺寸/測量大小)。
MeasureSpec 將兩個數據打包到一個 int 值上,可以減少對象內存分配,並且其提供了相應的工具方法可以很方便地讓我們從一個 int 值中抽取出 View 的 SpecMode 和 SpecSize。
一個 MeasureSpec 表達的是:該 View 在該種測量模式(SpecMode)下對應的測量尺寸(SpecSize)。其中,SpecMode 有三種類型:
對 View 進行測量,最關鍵的一步就是計算得到 View 的 MeasureSpec ,子View 在創建時,可以指定不同的 LayoutParams (布局參數), LayoutParams 的源碼主要內容如下所示:
其中:
LayoutParams 會受到父容器的 MeasureSpec 的影響,測量過程會依據兩者之間的相互約束最終生成子View 的 MeasureSpec ,完成 View 的測量規格。
簡而言之,View 的 MeasureSpec 受自身的 LayoutParams 和父容器的 MeasureSpec 共同決定( DecorView 的 MeasureSpec 是由自身的 LayoutParams 和屏幕尺寸共同決定,參考後文)。也因此,如果要求取子View 的 MeasureSpec ,那麼首先就需要知道父容器的 MeasureSpec ,層層逆推而上,即最終就是需要知道頂層View(即 DecorView )的 MeasureSpec ,這樣才能一層層傳遞下來,這整個過程需要結合 Activity 的啟動過程進行分析。
我們知道,在 Android 中, Activity 是作為視圖組件存在,主要就是在手機上顯示視圖界面,可以供用戶操作, Activity 就是 Andorid 中與用戶直接交互最多的系統組件。
Activity 的基本視圖層次結構如下所示:
Activity 中,實際承載視圖的組件是 Window (更具體來說為 PhoneWindow ),頂層View 是 DecorView ,它是一個 FrameLayout , DecorView 內部是一個 LinearLayout ,該 LinearLayout 由兩部分組成(不同 Android 版本或主題稍有差異): TitleView 和 ContentView ,其中, TitleView 就是標題欄,也就是我們常說的 TitleBar 或 ActionBar , ContentView 就是內容欄,它也是一個 FrameLayout ,主要用於承載我們的自定義根布局,即當我們調用 setContentView(...) 時,其實就是把我們自定義的布局設置到該 ContentView 中。
當 Activity 啟動完成後,最終就會渲染出上述層次結構的視圖。
因此,如果我們要求取得到子View 的 MeasureSpec ,那麼第一步就是求取得到頂層View(即 DecorView )的 MeasureSpec 。大致過程如下所示:
經過上述步驟求取得到 View 的 MeasureSpec 後,接下來就可以真正對 View 進行測量,求取 View 的最終測量寬/高:
Android 內部對視圖進行測量的過程是由 View#measure(int, int) 方法負責的,但是對於 View 和 ViewGroup ,其具體測量過程有所差異。
因此,對於測量過程,我們分別對 View 和 ViewGroup 進行分析:
綜上,無論是對 View 的測量還是 ViewGroup 的測量,都是由 View#measure(int widthMeasureSpec, int heightMeasureSpec) 方法負責,然後真正執行 View 測量的是 View 的 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法。
具體來說, View 直接在 onMeasure(...) 中測量並設置自己的最終測量寬/高。在默認測量情況下, View 的測量寬/高由其父容器的 MeasureSpec 和自身的 LayoutParams 共同決定,當 View 自身的測量模式為 LayoutParams.UNSPECIFIED 時,其測量寬/高為 android:minWidth / android:minHeight 和其背景寬/高之間的較大值,其餘情況皆為自身 MeasureSpec 指定的測量尺寸。
而對於 ViewGroup 來說,由於布局特性的豐富性,只能自己手動覆寫 onMeasure(...) 方法,實現自定義測量過程,但是總的思想都是先測量 子View 大小,最終才能確定自己的測量大小。
當確定了 View 的測量大小後,接下來就可以來確定 View 的布局位置了,也即將 View 放置到屏幕具體哪個位置。
View 的布局過程由 View#layout(...) 負責,其源碼如下:
View#layout(...) 主要就做了兩件事:
ViewGroup 的布局流程由 ViewGroup#layout(...) 負責,其源碼如下:
可以看到, ViewGroup#layout(...) 最終也是通過 View#layout(...) 完成自身的布局過程,一個注意的點是, ViewGroup#layout(...) 是一個 final 方法,因此子類無法覆寫該方法,主要是 ViewGroup#layout(...) 方法內部對子視圖動畫效果進行了相關設置。
由於 ViewGroup#layout(...) 內部最終調用的還是 View#layout(...) ,因此, ViewGroup#onLayout(...) 就會得到回調,用於處理 子View 的布局放置,其源碼如下:
由於不同的 ViewGroup ,其布局特性不同,因此 ViewGroup#onLayout(...) 是一個抽象方法,交由 ViewGroup 子類依據自己的布局特性,擺放其 子View 的位置。
當 View 的測量大小,布局位置都確定後,就可以最終將該 View 繪制到屏幕上了。
View 的繪制過程由 View#draw(...) 方法負責,其源碼如下:
其實注釋已經寫的很清楚了, View#draw(...) 主要做了以下 6 件事:
我們知道,在 Activity 啟動過程中,會調用到 ActivityThread.handleResumeActivity(...) ,該方法就是 View 視圖繪制的起始之處:
可以看到, ActivityThread.handleResumeActivity(...) 主要就是獲取到當前 Activity 綁定的 ViewManager ,最後調用 ViewManager.addView(...) 方法將 DecorView 設置到 PhoneWindow 上,也即設置到當前 Activity 上。 ViewManager 是一個介面, WindowManager 繼承 ViewManager ,而 WindowManagerImpl 實現了介面 WindowManager ,此處的 ViewManager.addView(...) 實際上調用的是 WindowManagerImpl.addView(...) ,源碼如下所示:
WindowManagerImpl.addView(...) 內部轉發到 WindowManagerGlobal.addView(...) :
在 WindowManagerGlobal.addView(...) 內部,會創建一個 ViewRootImpl 實例,然後調用 ViewRootImpl.setView(...) 將 ViewRootImpl 與 DecorView 關聯到一起:
ViewRootImpl.setView(...) 內部首先關聯了傳遞過來的 DecorView (通過屬性 mView 指向 DecorView 即可建立關聯),然後最終調用 requestLayout() ,而 requestLayout() 內部又會調用方法 scheleTraversals() :
ViewRootImpl.scheleTraversals() 內部主要做了兩件事:
Choreographer.postCallback(...) 會申請一次 VSYNC 中斷信號,當 VSYNC 信號到達時,便會回調 Choreographer.doFrame(...) 方法,內部會觸發已經添加的回調任務, Choreographer 的回調任務有以下四種類型:
因此, ViewRootImpl.scheleTraversals(...) 內部通過 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null) 發送的非同步視圖渲染消息就會得到回調,即回調 mTra
⑸ android基礎-viewgroup的測量,布局,繪制
相關文章
android基礎-view的測量,布局,繪制
viewgroup的作用主要用於管理子view,而在測量的時候可以分兩種情況
關於viewgroup遍歷子view去測量的方法,android中已經幫我們封裝了兩個常用方法:
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec)
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)
從方法名和方法裡面不難看出這兩個方法的區別,就是後者把子view的padding和margin也考慮了進去,不過他們最終調用的都是子view的 view.measure(int wSpec,int hSpec) 方法該方法回觸發子view的 onMeasure 方法
最後在測量子view之後,就要對自身大小做決定了,同樣是根據不同的測量模式來確定最終的大小,並且最後需要調用
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight)
該方法來設置viewgroup的寬高
viewgroup的測量栗子如下:
在自定義viewgroup的時候,我們必須重寫如下方法:
該方法主要就是通知子view去設置他們的布局位置,之前 android基礎-view的測量,布局,繪制 的篇章也已經詳細說明了view.layout方法的過程
viewgroup通知情況下不需要繪制,因為他本身就沒有需要繪制的東西,如果不是指定了viewgroup的背景色,那麼viewgroup的onDraw方法都不會被調用。但是,viewgroup會使用dispatchDraw()方法來繪制其子view,其過程同樣是通過變遍歷所有的子view,並調用子view的繪制方法來完成繪制工作
注意對於viewgroup而言onDraw()先於dispatchDraw()執行,用於本身控制項的繪制,dispatchDraw()用於子控制項的繪制,所以如果想對於viewgroup中繪制完子view之後在對其修改,我們可以在dispatchDraw調用surper方法之前做自己想要的繪制效果,這樣避免了被子view的覆蓋
viewgroup的測量,布局,繪制,其實都只是用來管理和通知子view去具體實現,可能最主要就是onLayout方法去定義子view的顯示位置,其他的核心都是在view中做處理的,所以先理解清楚view的顯示過程,那麼再來理解viewgroup的顯示過程,就會容易理解許多
《Android群英傳》¬
⑹ Android寶典|View必考知識點總結
我們知道,Activity 是在 ActivityThread 的 performLaunchActivity 中進行創建的,在創建完成之後就會調用其 attach 方法,它是先於 onCreate、onStart、onResume 等生命周期函數的,因此將 attach 方法作為這篇文章主線的開頭:
attach() 方法就是 new 一個 PhoneWindow 並且關聯 WindowManager。
接下來就到了 onCreate 方法:
這一步就是把我們的布局文件解析成 View 塞到 DecorView 的一個 id 為 R.id.content 的 ContentView 中,DecorView 本身是一個 FrameLayout,它還承載了 StatusBar、NavigationBar 。
然後在 handleResumeActivity 中,通過 WindowManager 的 addView 方法把 DecorView 添加進去,實際實現是 WindowManagerImpl 的 addView 方法,它裡面再通過 WindowManagerGlobal 的實例去 addView 的,在它裡面就會 new 一個 ViewRootImpl,也就是說最後是把 DecorView 傳給了 ViewRootImpl 的 setView 方法。ViewRootImpl 是 DecorView 的管理者,它負責 View 樹的測量、布局、繪制,以及通過 Choreographer 來控制 View 的刷新。
WMS 是所有 Window 窗口的管理員,負責 Window 的添加和刪除、Surface 的管理和事件派發等等,因此每一個 Activity 中的 PhoneWindow 對象如果需要顯示等操作,就必須要與 WMS 交互才能進行。
在 ViewRootImpl 的 setView 方法中,會調用 requestLayout,並且通過 WindowSession 的 addToDisplay 與 WMS 進行交互。WMS 會為每一個 Window 關聯一個 WindowStatus。
SurfaceFlinger 主要是進行 Layer 的合成和渲染。
在 WindowStatus 中,會創建 SurfaceSession,SurfaceSession 會在 Native 層構造一個 SurfaceComposerClient 對象,它是應用程序與 SurfaceFlinger 溝通的橋梁。
經過步驟四和步驟五之後,ViewRootImpl 與 WMS、SurfaceFlinger 都已經建立起連接,但此時 View 還沒顯示出來,我們知道,所有的 UI 最終都要通過 Surface 來顯示,那麼 Surface 是什麼時候創建的呢?
這就要回到前面所說的 ViewRootImpl 的 requestLayout 方法了,首先會 checkThread 檢查是否是主線程,然後調用 scheleTraversals 方法,scheleTraversals 方法會先設置同步屏障,然後通過 Choreographer 類在下一幀到來時去執行 doTraversal 方法。簡單來說,Choreographer 內部會接受來自 SurfaceFlinger 發出的 Vsync 垂直同步信號,這個信號周期一般是 16ms 左右。doTraversal 方法首先會先移除同步屏障,然後 performTraversals 真正進行 View 的繪制流程,即調用 performMeasure、performLayout、performDraw。不過在它們之前,會先調用 relayoutWindow 通過 WindowSession 與 WMS 進行交互,即把 Java 層創建的 Surface 與 Native 層的 Surface 關聯起來。
接下來就是正式繪制 View 了,從 performTraversals 開始,Measure、Layout、Draw 三步走。
第一步是獲取 DecorView 的寬高的 MeasureSpec 然後執行 performMeasure 流程。MeasureSpec 簡單來說就是一個 int 值,高 2 位表示測量模式,低 30 位用來表示大小。策略模式有三種,EXACTLY、AT_MOST、UNSPECIFIED。EXACTLY 對應為 match_parent 和具體數值的情況,表示父容器已經確定 View 的大小;AT_MOST 對應 wrap_content,表示父容器規定 View 最大隻能是 SpecSize;UNSPECIFIED 表示不限定測量模式,父容器不對 View 做任何限制,這種適用於系統內部。接著說,performMeasure 中會去調用 DecorView 的 measure 方法,這個是 View 裡面的方法並且是 final 的,它裡面會把參數透傳給 onMeasure 方法,這個方法是可以重寫的,也就是我們可以干預 View 的測量過程。在 onMeasure 中,會通過 getDefaultSize 獲取到寬高的默認值,然後調用 setMeasureDimension 將獲取的值進行設置。在 getDefaultSize 中,無論是 EXACTLY 還是 AT_MOST,都會返回 MeasureSpec 中的大小,這個 SpecSize 就是測量後的最終結果。至於 UNSPECIFIED 的情況,則會返回一個建議的最小值,這個值和子元素設置的最小值以及它的背景大小有關。從這個默認實現來看,如果我們自定義一個 View 不重寫它的 onMeasure 方法,那麼 warp_content 和 match_parent 一樣。所以 DecorView 重寫了 onMeasure 函數,它本身是一個 FrameLayout,所以最後也會調用到 FrameLayout 的 onMeasure 函數,作為一個 ViewGroup,都會遍歷子 View 並調用子 View 的 measure 方法。這樣便實現了層層遞歸調用到了每個子 View 的 onMeasure 方法進行測量。
第二步是執行 performLayout 的流程,也就是調用到 DecorView 的 layout 方法,也就是 View 裡面的方法,如果 View 大小發生變化,則會回調 onSizeChanged 方法,如果 View 狀態發生變化,則會回調 onLayout 方法,這個方法在 View 中是空實現,因此需要看 DecorView 的父容器 FrameLayout 的 onLayout 方法,這個方法就是遍歷子 View 調用其 layout 方法進行布局,子 View 的 layout 方法被調用的時候,它的 onLayout 方法又會被調用,這樣就布局完了所有的 View。
第三步就是 performDraw 方法了,裡面會調用 drawSoftware 方法,這個方法需要先通過 mSurface lockCanvas 獲取一個 Canvas 對象,作為參數傳給 DecorView 的 draw 方法。這個方法調用的是 View 的 draw 方法,先繪制 View 背景,然後繪制 View 的內容,如果有子 View 則會調用子 View 的 draw 方法,層層遞歸調用,最終完成繪制。
完成這三步之後,會在 ActivityThread 的 handleResumeActivity 最後調用 Activity 的 makeVisible,這個方法就是將 DecorView 設置為可見狀態。
https://juejin.im/post/5c67c1e16fb9a04a05403549
https://juejin.im/post/5bf16ff5f265da6141712acc
⑺ Android View何時能拿到寬高的值
一個View或ViewGroup中什麼什麼時候能拿到寬高的值?
width 表示 View 在屏幕上可顯示的區域大小;
measuredWidth 表示 View 的實際大小,包括超出屏幕范圍外的尺寸;
甚至有這樣的公式總結到:
getMeasuredWidth() = visible width + invisible width
getMeasuredWidth() 在執行setMeasuredDimension(一般在onMeasure方法中執行)後才有值;
getWidth()在onLayout方法執行後才有值。
Constructor : 構造方法,View初始化的時候調用,在這里是無法獲取其子控制項的引用的.更加無法獲取寬高了.
onFinishInflate : 當布局初始化完畢後回調,在這里可以獲取所有直接子View的引用,但是無法獲取寬高.
onMeasure : 當測量控制項寬高時回調,當調用了requestLayout()也會回調onMeasure.在這里一定可以通過getMeasuredHeight()和getMeasuredWidth()來獲取控制項的高和寬,但不一定可以通過getHeight()和getWidth()來獲取控制項寬高,因為getHeight()和getWidth()必須要等onLayout方法回調之後才能確定.
onSizeChanged : 當控制項的寬高發生變化時回調,和onMeasure一樣,一定可以通過getMeasuredHeight()和getMeasuredWidth()來獲取控制項的高和寬,因為它是在onMeasure方法執行之後和onLayout方法之前回調的.
onLayout : 當確定控制項的位置時回調,當調用了requestLayout()也會回調onLayout.在這里一定可以通過getHeight()和getWidth()獲取控制項的寬高,同時由於onMeasure方法比onLayout方法先執行,所以在這里也可以通過getMeasuredHeight()和getMeasuredWidth()來獲取控制項的高和寬.
addOnGlobalLayoutListener : 當View的位置確定完後會回調改監聽方法,它是緊接著onLayout方法執行而執行的,只要onLayout方法調用了,那麼addOnGlobalLayoutListener的監聽器就會監聽到.在這里getMeasuredHeight()和getMeasuredWidth()和getHeight()和getWidth()都可以獲取到寬高.
onWindowFocusChanged : 當View的焦點發送改變時回調,在這里getMeasuredHeight()和getMeasuredWidth()和getHeight()和getWidth()都可以獲取到寬高.Activity也可以通過重寫該方法來判斷當前的焦點是否發送改變了;需要注意的是這里View獲取焦點和失去焦點都會回調.
(部分內容參考於網路,如有不妥,請聯系刪除~)
⑻ Android UI繪制之View繪制的工作原理
這是AndroidUI繪制流程分析的第二篇文章,主要分析界面中View是如何繪制到界面上的具體過程。
ViewRoot 對應於 ViewRootImpl 類,它是連接 WindowManager 和 DecorView 的紐帶,View的三大流程均是通過 ViewRoot 來完成的。在 ActivityThread 中,當 Activity 對象被創建完畢後,會將 DecorView 添加到 Window 中,同時會創建 ViewRootImpl 對象,並將 ViewRootImpl 對象和 DecorView 建立關聯。
measure 過程決定了 View 的寬/高, Measure 完成以後,可以通過 getMeasuredWidth 和 getMeasuredHeight 方法來獲取 View 測量後的寬/高,在幾乎所有的情況下,它等同於View的最終的寬/高,但是特殊情況除外。 Layout 過程決定了 View 的四個頂點的坐標和實際的寬/高,完成以後,可以通過 getTop、getBottom、getLeft 和 getRight 來拿到View的四個頂點的位置,可以通過 getWidth 和 getHeight 方法拿到View的最終寬/高。 Draw 過程決定了 View 的顯示,只有 draw 方法完成後 View 的內容才能呈現在屏幕上。
DecorView 作為頂級 View ,一般情況下,它內部會包含一個豎直方向的 LinearLayout ,在這個 LinearLayout 裡面有上下兩個部分,上面是標題欄,下面是內容欄。在Activity中,我們通過 setContentView 所設置的布局文件其實就是被加到內容欄中的,而內容欄id為 content 。可以通過下面方法得到 content:ViewGroup content = findViewById(R.android.id.content) 。通過 content.getChildAt(0) 可以得到設置的 view 。 DecorView 其實是一個 FrameLayout , View 層的事件都先經過 DecorView ,然後才傳遞給我們的 View 。
MeasureSpec 代表一個32位的int值,高2位代表 SpecMode ,低30位代表 SpecSize , SpecMode 是指測量模式,而 SpecSize 是指在某種測量模式下的規格大小。
SpecMode 有三類,如下所示:
UNSPECIFIED
EXACTLY
AT_MOST
LayoutParams需要和父容器一起才能決定View的MeasureSpec,從而進一步決定View的寬/高。
對於頂級View,即DecorView和普通View來說,MeasureSpec的轉換過程略有不同。對於DecorView,其MeasureSpec由窗口的尺寸和其自身的LayoutParams共同確定;
對於普通View,其MeasureSpec由父容器的MeasureSpec和自身的Layoutparams共同決定;
MeasureSpec一旦確定,onMeasure就可以確定View的測量寬/高。
小結一下
當子 View 的寬高採用 wrap_content 時,不管父容器的模式是精確模式還是最大模式,子 View 的模式總是最大模式+父容器的剩餘空間。
View 的工作流程主要是指 measure 、 layout 、 draw 三大流程,即測量、布局、繪制。其中 measure 確定 View 的測量寬/高, layout 確定 view 的最終寬/高和四個頂點的位置,而 draw 則將 View 繪制在屏幕上。
measure 過程要分情況,如果只是一個原始的 view ,則通過 measure 方法就完成了其測量過程,如果是一個 ViewGroup ,除了完成自己的測量過程外,還會遍歷調用所有子元素的 measure 方法,各個子元素再遞歸去執行這個流程。
如果是一個原始的 View,那麼通過 measure 方法就完成了測量過程,在 measure 方法中會去調用 View 的 onMeasure 方法,View 類裡面定義了 onMeasure 方法的默認實現:
先看一下 getSuggestedMinimumWidth 和 getSuggestedMinimumHeight 方法的源碼:
可以看到, getMinimumWidth 方法獲取的是 Drawable 的原始寬度。如果存在原始寬度(即滿足 intrinsicWidth > 0),那麼直接返回原始寬度即可;如果不存在原始寬度(即不滿足 intrinsicWidth > 0),那麼就返回 0。
接著看最重要的 getDefaultSize 方法:
如果 specMode 為 MeasureSpec.UNSPECIFIED 即未指定模式,那麼返回由方法參數傳遞過來的尺寸作為 View 的測量寬度和高度;
如果 specMode 不是 MeasureSpec.UNSPECIFIED 即是最大模式或者精確模式,那麼返回從 measureSpec 中取出的 specSize 作為 View 測量後的寬度和高度。
看一下剛才的表格:
當 specMode 為 EXACTLY 或者 AT_MOST 時,View 的布局參數為 wrap_content 或者 match_parent 時,給 View 的 specSize 都是 parentSize 。這會比建議的最小寬高要大。這是不符合我們的預期的。因為我們給 View 設置 wrap_content 是希望View的大小剛好可以包裹它的內容。
因此:
如果是一個 ViewGroup,除了完成自己的 measure 過程以外,還會遍歷去調用所有子元素的 measure 方法,各個子元素再遞歸去執行 measure 過程。
ViewGroup 並沒有重寫 View 的 onMeasure 方法,但是它提供了 measureChildren、measureChild、measureChildWithMargins 這幾個方法專門用於測量子元素。
如果是 View 的話,那麼在它的 layout 方法中就確定了自身的位置(具體來說是通過 setFrame 方法來設定 View 的四個頂點的位置,即初始化 mLeft , mRight , mTop , mBottom 這四個值), layout 過程就結束了。
如果是 ViewGroup 的話,那麼在它的 layout 方法中只是確定了 ViewGroup 自身的位置,要確定子元素的位置,就需要重寫 onLayout 方法;在 onLayout 方法中,會調用子元素的 layout 方法,子元素在它的 layout 方法中確定自己的位置,這樣一層一層地傳遞下去完成整個 View 樹的 layout 過程。
layout 方法的作用是確定 View 本身的位置,即設定 View 的四個頂點的位置,這樣就確定了 View 在父容器中的位置;
onLayout 方法的作用是父容器確定子元素的位置,這個方法在 View 中是空實現,因為 View 沒有子元素了,在 ViewGroup 中則進行抽象化,它的子類必須實現這個方法。
1.繪制背景( background.draw(canvas); );
2.繪制自己( onDraw );
3.繪制 children( dispatchDraw(canvas) );
4.繪制裝飾( onDrawScrollBars )。
dispatchDraw 方法的調用是在 onDraw 方法之後,也就是說,總是先繪制自己再繪制子 View 。
對於 View 類來說, dispatchDraw 方法是空實現的,對於 ViewGroup 類來說, dispatchDraw 方法是有具體實現的。
通過 dispatchDraw 來傳遞的。 dispatchDraw 會遍歷調用子元素的 draw 方法,如此 draw 事件就一層一層傳遞了下去。dispatchDraw 在 View 類中是空實現的,在 ViewGroup 類中是真正實現的。
如果一個 View 不需要繪制任何內容,那麼就設置這個標記為 true,系統會進行進一步的優化。
當創建的自定義控制項繼承於 ViewGroup 並且不具備繪制功能時,就可以開啟這個標記,便於系統進行後續的優化;當明確知道一個 ViewGroup 需要通過 onDraw 繪制內容時,需要關閉這個標記。
參考:《Android開發藝術探索》