在Android的知識體系中,View扮演着很重要的角色,簡單來了解,View是Android在視覺上的呈現。在界面上Android提供了一套GUI庫,裡面有很多控件,但是很多時候我們并不滿足于系統提供的控件,因為這樣就意味着這應用界面的同類比較嚴重,如何做出與衆不同的效果呢,就是自定義View。
首先,要先了解下View的一些基本概念,這樣才能更好了解View的measure、layout和draw過程。
ViewRoot對應于ViewRootImpl類,它是連接配接WindowManager和DecorView的紐帶,View的三大流程是通過VeiwRoot來完成的。在ActivityThread中,當Activity對象被建立完畢後,會将DecorVeiw添加到Window中,同時會建立ViewRootImpl對象,并将ViewRootImpl對象和DecorView建立關聯。
View的繪制流程是從ViewRoot的performTraversals方法開始的,它經過measure、layout和draw三個過程才能最終将一個View繪制出來。如圖:

從中,我們可以看到,performTraversals會依次調用performMeasure、performLayout、performDraw三個方法,這個三個方法分别完成頂級View的measure、layout、draw這三大方法,在onMeasure方法中則會對所有的子元素進行measure過程,這個時候measure流程就從父容器傳遞到子元素中了,這樣就完成一次measure過程,接着子元素會重複父容器的過程,如此反複就完成了整個View樹的周遊。同理,其他兩個步驟也是類似的過程。
measure過程決定了View的寬和高,Measure完成以後,可以通過getMeasureWidth和getMeasureHeight方法來擷取到View的測量後的寬和高。
确切來說,MeasureSpec在很大程度上決定了一個View的尺寸規格,之是以說是很大程度上是因為這個過程還是受父容器的影響,因為父容器影響View的MeasureSpec的建立過程。在測量過程中,系統會将View的LayoutParams根據父容器所施加的規則轉換成對應的MeasureSpec,然後再根據這個measureSpec來測量出View的寬和高。
MeasureSpec代表一個32位int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指測量模式,而SpecSize是指在某種測量模式下的規格大小。MeasureSpec通過将SpecMode和SpecSize打包成一個int值來避免過多的對象記憶體配置設定,為了友善操作,其提供了打包和解包的方法。
SpecMode有三類,每一類都表示了特殊的含義。
UNSPECIFIED。父容器不對View有任何限制,要多大就給多大,這種情況一般用于系統内部,表示一種測量的狀态。
EXACTLY。父容器已經檢測出View所需要的精确大小,這個時候View的最終大小就是SpecSize所指定的值。它對應于LayoutParams中的match_parent和具體的數值這兩種模式。
AT_MOST。父容器指定了一個可用大小即SpecSize,View的大小不能大于這個值,具體是什麼值要看不同的View的具體實作。它對應于LayoutParams中的wrap_content。
簡單來說,當View采用固定寬/高的時候,不管父容器的MeasureSpec是什麼,View的MeasureSpec都是EXACTLY模式并且大小遵循LayoutParams中的大小,當View的寬/高是match_parent時,如果父容器的模式是EXACTLY,那麼View也是精确模式并且其大小是父容器的剩餘空間,如果父容器是最大模式,那麼View也是最大模式并且其大小不會超過父容器的剩餘空間,當View的寬/高是wrap_content時,不管父容器的模式是精确還是最大化,View的模式總是最大化并且大小不能超過父容器的剩餘空間。
View的工作流程主要是指measure、layout、draw這三大流程,即測量、布局、繪制,其中measure确定View的測量寬/高,layout确定View的最終寬/高和四個頂點的位置,而draw則将View繪制到螢幕上。
measure過程
measure過程要分情況來看,如果是一個原始的View,那麼通過measure方法就完成了其測量過程,如果是一個ViewGroup,除了完成自己的測量過程外,還會周遊去調用所有子元素的measure方法,各個子元素再遞歸去執行這個流程。
1,View的measure過程
measure方法是一個final類型的方法,這意味着子類不能重寫此方法,在View的measure方法中會去調用View的onMeasure方法,是以隻需看onMeasure方法即可。
上面的代碼很簡潔,但是簡潔并不代表簡單,setMeasuredDimension方法會設定View的寬/高的測量值,是以我們隻需要看getDefaultSize這個方法。
簡單了解,其實getDefaultSize方法傳回的大小就是MeasureSpec中的specSize,而這個specSize就是View測量後的大小,但View的最終大小是在layout階段确定的,是以這裡必須要加以區分,但是幾乎所有情況下的View的測量大小和最終大小是相等的。
同時,直接繼承View的自定義控件需要重寫onMeasure方法并設定wrap_content時的自身大小,否則在布局中使用wrap_content就相當于使用match_parent。為什麼呢,如果View在布局中使用wrap_content,那麼它的specMode是AT_MOST模式,在這種模式下,它的寬/高等于specSize,也就是說,這種情況下的View的specSize是parentSize,而parentSize是父容器中目前目前剩餘使用的大小,也就是父容器目前剩餘的空間大小。
那麼該如何該解決這個問題,很簡單,代碼如下:
給wrap_content設定一個預設值,比如都是寬/高都是200px。
2,ViewGroup的measure過程
對于ViewGroup來說,除了完成自己的measure過程以外,還會周遊去調用所有子元素的measure方法,各個子元素再去遞歸執行這個過程。和View不同的是,ViewGroup是一個抽象類,是以它沒有重寫View的onMeasure方法,但是它提供了一個叫measureChildren的方法。
從上述代碼來看,ViewGroup在measure時,會對每一個子元素進行measure。具體measureChild這個方法的實作也很好了解,如下所示:
很明顯,measureChild的思想就是取出子元素的LayoutParams,然後再通過getChildMeasureSpec來建立子元素的MeasureSpec,接着将MeasureSpec直接傳遞給View的measure方法進行測量。
Layout過程
Layout的作用是ViewGroup用來确定子元素的位置,當ViewGroup的位置被确定後,它在onLayout中會周遊所有的子元素并調用其layout方法,在layout方法中onLayout方法又會被調用。先看下View中的layout方法
layout方法的大緻流程:首先會通過setFrame方法來設定View的四個頂點的位置,即初始化mLeft、mRight、mTop和mBottom這四個值,View的四個頂點一旦确定,那麼View在父容器中的位置也就确定了,接着就會調用onLayout方法,這個方法用途是父容器确定子元素的位置,和onMeasure方法類似,onLayout的具體實作同樣和具體的布局有關,是以View和ViewGroup均沒有真正實作onLayout方法。
draw過程
draw過程就比較簡單了,它的作用是将View繪制到螢幕上面,View的繪制過程循序以下幾步:
繪制背景background.draw(canvas) 繪制自己(onDraw) 繪制children(dispatchDraw) 繪制裝飾(onDrawScrollBars)
這一點看代碼,就能看出來。
View的繪制過程的傳遞是通過dispatchDraw來實作的,dispatchDraw會周遊調用所有子元素的draw方法,如此draw事件就一層層地傳遞下去。
自定義View是一個綜合的技術體系,它涉及View的層次結構、事件分發機制和View的工作原理等技術細節。
自定義View的分類
繼承View重寫onDraw方法。這種方法主要用于實作一些不規則的效果,即這種效果不友善通過布局的組合方式來達到,往往需要靜态或者動态地顯示一些不規則的圖形。這種方式需要重寫onDraw方法,同時需要自己支援wrap_content,并且padding也需要自己處理。 繼承ViewGroup派生特殊的Layout。這種方法主要用于實作自定義的布局,即除了LinearLayout、RelativeLayout、FrameLayout這幾種系統的布局之外,我們需要重新定義一種新的布局。 繼承特定的View(比如TextView)。這種方法比較常見,一般是用于擴充某種已有的View的功能,比如TextView。 繼承特定的ViewGroup(比如LinearLayout)。這種效果看起來很像幾種View組合在一起的時候,可以采用這種方法實作。
自定義View須知
一些具體的注意事項。
讓View支援wrap_content 如果有必要,讓你的View支援padding 盡量不要在View中使用Handler,沒必要 View中如果有線程或者動畫,需要及時停止,參考View#onDetachedFromWindow View帶有滑動嵌套情形時,需要處理好滑動沖突
源于對掌握的Android開發基礎點進行整理,羅列下已經總結的文章,從中可以看到技術積累的過程。
1,Android系統簡介
2,ProGuard代碼混淆
3,講講Handler+Looper+MessageQueue關系
4,Android圖檔加載庫了解
5,談談Android運作時權限了解
6,EventBus初了解
7,Android 常見工具類
8,對于Fragment的一些了解
9,Android 四大元件之 " Activity "
10,Android 四大元件之" Service "
11,Android 四大元件之“ BroadcastReceiver "
12,Android 四大元件之" ContentProvider "
13,講講 Android 事件攔截機制
14,Android 動畫的了解
15,Android 生命周期和啟動模式
16,Android IPC 機制
17,View 的事件體系
18,View 的工作原理
19,了解 Window 和 WindowManager
20,Activity 啟動過程分析
21,Service 啟動過程分析
22,Android 性能優化
23,Android 消息機制
24,Android Bitmap相關
25,Android 線程和線程池
26,Android 中的 Drawable 和動畫
27,RecylerView 中的裝飾者模式
28,Android 觸摸事件機制
29,Android 事件機制應用
30,Cordova 架構的一些了解
31,有關 Android 插件化思考
32,開發人員必備技能——單元測試