當前位置:編程學習大全網 - 編程語言 - Android UI繪制之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開發藝術探索》

  • 上一篇:為什麽風險投資會誕生在矽谷?
  • 下一篇: 巴倫讀書會
  • copyright 2024編程學習大全網