第4章 View的工作原理

一、ViewRoot 和 DecorView

  • ViewRoot對應於ViewRootImpl 類,它是連接WIndowManager 和 DecorView的紐帶,View的三大流程均是通過VIewRoot來完成的,在ActivityThread中,當Activity對象被創建後,會將DecorView 添加到Window中,同時會創建VIewRootImpl對象,並將ViewRootImpl對象和DecorView建立關聯

  • View 的繪製流程是從ViewRoot的 performTraversals 方法開始的,performTraversals 依次調用 performMeasure、performLayout 和 performDraw,這三個方法分別完成頂級View的measure、layout 和 draw

  • performMeasure -> measure -> onMeasure,對子元素進行measure(layout 和 draw類似)

  • measure過程決定了View的寬/高,getMeasuredwidth/getMeasuredHeight,除特殊情況外它都等於View的最終寬/高;Layout過程決定View的四個頂點座標和實際寬/高,getTop/getBottom/getLeft/getRight/getWidth/getHeight;Draw決定View的顯示

  • DecorView作爲頂級View,包含上下兩個部分,上面是標題欄,下面是內容欄,Activity中 setContentView 所設置的佈局是被加到內容欄;View層的事件都先經過DecorView 然後傳遞給我們的View

二、MeasureSpec

MeasureSpec代表一個32位的int值,高 2 位代表SpecMode 測量模式,低30位代表 SpecSize 規格大小

1. SpecMode有三類

  • UNSPECIFIED:父容器不對View有任何限制,要多大有多大,一般用於系統內部

  • EXACTLY:父容器已經檢測說View所需要的的精確大小,View的最終大小就是SpecSize指定的值,對應於 match_parent 和 具體數值兩種模式

  • AT_MOST:父容器指定了一個可用大小即SpecSize,View的大小不能大於這個值,對應於 wrap_content

2. MeasureSpec 和 LayoutParams

對於頂級View(DecorView),其MeasureSpec由窗口的尺寸和其自身的LayoutParams來共同確定;

對於普通View,其MeasureSpec由父容器的MeasureSpec和自身的LayoutParams來共同確定

3. View 的measure

View的measure過程由ViewGrou傳遞而來,ViewGroup 的 measureChildWithMargins

protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

4. 注意

  • 當View採用固定寬/高的時候,不管父容器的 MeasureSpec是什麼,View的MeasureSpec 都是精確模式且遵循Layoutparams中的大小
  • 當View的寬/高是 match_parent 時,如果父容器是最大模式,那麼子View也是最大模式,且不超過父容器的剩餘空間
  • 當View的寬/高是 wrap_content 時,不管父容器的模式是精準還是最大化,View的模式總是最大化,且不超過父容器的剩餘空間

三、View的工作流程

1. measure 過程

1)View 的 measure過程

View 的measure過程由其measure方法來完成,measure方法是一個final類型的方法,子類不能重寫,measure方法中會去調用 onMeasure->setMeasuredDimension方法會設置View 寬/高的測量值

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
2)ViewGroup 的 measure過程

ViewGroup 除了完成自己的measure,還會遍歷去調用所有子元素的measure,各個子元素再遞歸去執行這個過程。ViewGroup 是一個抽象類,沒有重寫 onMeasure,其測量過程的onMeasure 方法需要各個子類去具體實現,但提供了 measureChildren方法,ViewGroup 在measure時,會對每一個子元素進行measure

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
3)異步獲取View寬高

如果想在onCreate等方法裏獲取View的寬高,有4種方式:

  • Activity/View#onWindowFocusChanged

onWindowFocusChanged 表示View已經初始化完畢,寬高已經準備好,在Activity的窗口得到和失去焦點時均會被調用

  • view.post(runnable)
  • ViewTreeObserver#onGlobalLayoutListener
  • view.measure 手動measure

2. Layout 過程

ViewGroup的位置被確定後,onLayout中會遍歷所有的子元素並調用其 layout方法,在layout方法中 onLayout方法又會被調用;layout方法確定View 本身的位置,onLayout方法確定所有子元素的位置

View 和 ViewGroup 均沒有真正實現 onLayout

3. Draw 過程

  • 繪製背景 background.draw(canvas)
  • 繪製自己 (onDraw)
  • 繪製 children (dispatchDraw)
  • 繪製裝飾(onDrawScrollBars)

四、自定義View

1. 分類

1)繼承View重寫onDraw方法
2)繼承ViewGroup 派生特殊的 Layout
3)繼承特定的View
4)繼承特定的ViewGroup

2. 一些注意

1)讓View支持 wrap_content
2)如果有必要,讓View支持 padding
3)儘量不要再View中使用 Handler
4)View中如果有線程或者動畫,需要及時停止 View#onDetachedFromWindow
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章