ViewRoot
ViewRoot對應於ViewRootImpl類,它是連接WindowManager和DecorView的紐帶,也可以說是Window和View的橋樑,他主要功能有:
- 完成View的繪製過程,包括measure、layout、draw過程
- 向DecorView分發Event事件,包括MotionEvent,KeyEvent等
當建立好了decorView與ViewRootImpl的關聯後,ViewRootImpl類的requestLayout()方法會被調用,該方法爲View繪製的起點方法。
ViewRoot與DectorView的關聯過程
我們從Activity的setContentView方法開始分析。該方法會創建DecorView,並將xml佈局文件添加到DecorView的content區域,最後調用handleResumeActivity將decorView加入到PhoneWindow中。
ActivityThread調用handleResumeActivity方法將頂層的DecorView添加到PhoneWindow窗口
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) { if (r.window == null && !a.mFinished && willBeVisible) { //獲得當前Activity的PhoneWindow對象 r.window = r.activity.getWindow(); //獲得當前phoneWindow內部類DecorView對象 View decor = r.window.getDecorView(); //設置窗口頂層視圖DecorView可見度 decor.setVisibility(View.INVISIBLE); //當前Activity的WindowManagerImpl對象 ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { //標記根佈局DecorView已經添加到窗口 a.mWindowAdded = true; //將根佈局DecorView添加到當前Activity的窗口上面 wm.addView(decor, l); } }
WindowManagerGloba.addView()方法將DectorView與ViewRootlmpl進行關聯
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { ............ ViewRootImpl root; View panelParentView = null; ............ //獲得ViewRootImpl對象root root = new ViewRootImpl(view.getContext(), display); ........... // do this last because it fires off messages to start doing things try { //將傳進來的參數DecorView設置到root中 root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { } }
接下來調用ViewRootImpl類中的setView()方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { //將頂層視圖DecorView賦值給全局的mView mView = view; ............. //標記已添加DecorView mAdded = true; ............. //請求佈局,View繪製的起點方法 requestLayout(); }
}
可以看出經過以上代碼調用,DectorView與ViewRootImpl建立了關聯,接下來就通過requestLayout()方法正試開始View的繪製過程了。
View的繪製過程
requestLayout最終會調用performTraversals方法來完成View的繪製。
整個View的繪製流程是在ViewRootImpl類的performTraversals()方法開始的,performTraversals方法會經過measure、layout和draw三個過程。所以View的繪製是ViewRootImpl完成的,另外當手動調用invalidate(主線程),postInvalidate(子線程)也會最終調用performTraversals來重新繪製View。
private void performTraversals() {
......
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
......
mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
......
mView.draw(canvas);
......
}
Measure
measure過程如果是ViewGroup,需要遍歷測量其孩子後再測量自己。如果是View則只負責測量自己就行。
- MeasureSpec
MeasureSpec是一個32位整數,由SpecMode和SpecSize兩部分組成,其中高2位爲SpecMode,低30位爲SpecSize。SpecMode爲測量模式,SpecSize爲相應測量模式下的測量尺寸。 - SpecMode的取值可爲以下三種
EXACTLY: 父容器測量出子View的精確大小SpecSize,如match_parent,具體dp值
AT_MOST: 父容器指定子View的大小不得超過SpecSize,如wrap_content
UNSPECIFIED: 父容器不對子View尺寸作限制,通常用於系統內部
在onCreate()中獲取View的高寬時不能保證View已測量完畢,因此需要調用view.post(runnable)將測量view的代碼投遞到消息隊列尾部,這樣能保證View初始化好之後纔回調runnable
Layout
Layout的作用是ViewGroup用來確定子元素位置,當ViewGroup的位置被確定後,它在onLayout中會遍歷所有子元素並調用其layout方法,在layout方法中onLayout方法又會被調用。layout方法中通過setFrame來確定View本身的位置,而onLayout方法則會確定所有子元素的位置。
Draw
它的作用是將View繪製到屏幕上,繪製過程有以下幾步:
1.繪製背景background
2.繪製自己
2.繪製children(ViewGroup)
3.繪製裝飾,滾動條等
onDraw(Canvas canvas)最終是調用canvas.translate() native層的方法進行繪製的
常見自定義View類型
1.繼承View重寫onDraw方法
2.繼承ViewGroup派生自定義Layout
3.繼承特定View (Button,TextView)
4.繼承特定ViewGroup(LinearLayout等)
自定義View注意事項
1.需要在onMeasure中實現支持wrap_content(指定一個默認大小)
爲什麼你的自定義View wrap_content不起作用?
https://blog.csdn.net/carson_ho/article/details/62037760
2.需自己實現padding效果
- 如果是View則在ondraw方法裏處理padding
- 如果是ViewGroup需要在onMeasure和onLayout中考慮padding和margin的影響
3.不要使用Handler,view本身提供了post方法
4.如果有線程或動畫,需要在onDetachedFromWindow中停止
5.如果有滑動衝突,需要處理衝突