Android View的繪製流程

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.如果有滑動衝突,需要處理衝突

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章