《Android開發(fā)藝術探索》之學習筆記(四)View的工作原理

View的工作原理

初識ViewRoot和DecorView

1、ViewRoot對應于ViewRootImpl類,它是連接WindowManager和DecorView的紐帶,View的三大流程(measure、layout、draw)均是通過ViewRoot來完成的。在ActivityThread中,當Activity對象被創(chuàng)建完畢后,會將DecorView添加到Window中,同時會創(chuàng)建ViewRootImpl對象,并將ViewRootImpl對象和DecorView建立連接。

2、View的繪制流程是從ViewRoot的performTraversals方法開始的,它經過measure、layout、draw三個過程才能最終將一個View繪制出來,其中measure用來測量View的寬和高,layout用來確定View在父容器的放置位置,而draw則負責將View繪制在屏幕上。

3、performTraversals會依次調用performMeasure、performLayout、performDraw三個方法,這三個方法分別完成頂級View的measure、layout和draw這三大流程,其中performMeasure會調用measure方法,在measure方法中又會調用onMeasure方法,在onMeasure方法中對所有的子元素進行measure過程,這個時候measure流程就會從父容器傳遞到子元素中了,這樣就完成了一次measure過程。接著子元素就會重復父容器的measure過程,如此反復就完成了整個View樹的遍歷。

4、measure過程中決定了View的寬/高,Measure完成以后,可以通過getMeasureWidth和getMeasureHeight方法來獲取到View測量后的寬/高,在幾乎所有的情況下它都等同于View最終的寬高。layout決定了View的四個頂點的坐標和View的實際的寬高,通過getWidth和getHeight方法可以獲得最終的寬高。draw過程決定了View的顯示。

5、DecorView其實是一個FrameLayout,其中包含了一個豎直方向的LinearLayout,上面是標題欄,下面是內容欄(id為android.R.id.content)。View層的事件都先經過DecorView,然后才傳給我們的View。

理解MeasureSpec

1、MeasureSpec通過將SpecMode和SpecSize打包成一個int值來避免過多的內存分配,為了方便操作,其提供了打包和解包方法。SpecMode和SpecSize也是一個int值,一組SpecMode和SpecSize可以打包為一個MeasureSpec,而一個MeasureSpec可以通過解包的形式來得出其原始的SpecMode和SpecSize。

SpecMode有三類,每一類都表示特殊的含義:

  1. UNSPECIFIED 父容器不對View有任何的限制,要多大給多大,這種情況下一般用于系統內部,表示一種測量的狀態(tài)。
  2. EXACTLY 父容器已經檢測出View所需要的精確大小,這個時候View的最終大小就是SpecSize所指定的值,它對應于LayoutParams中的match_parent和具體的數值這兩種模式
  3. AT_MOST 父容器指定了一個可用大小即SpecSize,View的大小不能大于這個值,具體是什么值要看不同View的具體實現。它對應于LayoutParams中的wrap_content

2、MeasureSpec和LayoutParams的對應關系

在View測量的時候系統會將LayoutParams在父容器的約束下轉換成對應的MeasureSpec,然后再根據這個MeasureSpec來確定View測量后的寬高。

MeasureSpec不是唯一由LayoutParams決定的,LayoutParams需要和父容器一起才能決定View的MeasureSpec,從而進一步確定View的寬高。對于DecorView,它的MeasureSpec由窗口的尺寸和其自身的LayoutParams來決定;對于普通View,它的MeasureSpec由父容器的MeasureSpec和自身的LayoutParams來共同決定

3、當view采用固定寬高時,不管父容器的MeasureSpec是什么,view的MeasureSpec都是精確模式,并且大小是LayoutParams中的大小。
當view的寬高是match_parent時,如果父容器的模式是精確模式,那么view也是精確模式,并且大小是父容器的剩余空間;如果父容器是最大模式,那么view也是最大模式,并且大小是不會超過父容器的剩余空間。
當view的寬高是wrap_content時,不管父容器的模式是精確模式還是最大模式,view的模式總是最大模式,并且大小不超過父容器的剩余空間。

View的工作流程

1、View的measure過程和Activity的生命周期方法不是同步執(zhí)行的,因此無法保證Activity執(zhí)行了onCreate、onStart、onResume時某個View已經測量完畢了。如果View還沒有測量完畢,那么獲得的寬和高都是0。下面是四種解決該問題的方法:

  • Activity/View#onWindowsChanged方法

onWindowFocusChanged方法表示View已經初始化完畢了,寬高已經準備好了,這個時候去獲取是沒問題的。這個方法會被調用多次,當Activity繼續(xù)執(zhí)行或者暫停執(zhí)行的時候,這個方法都會被調用。

  • View.post(runnable)

通過post將一個Runnable投遞到消息隊列的尾部,然后等待Looper調用此runnable的時候,View也已經初始化好了。

  • ViewTreeObsever

使用ViewTreeObserver的眾多回調方法可以完成這個功能,比如使用onGlobalLayoutListener接口,當View樹的狀態(tài)發(fā)生改變或者View樹內部的View的可見性發(fā)生改變時,onGlobalLayout方法將被回調。伴隨著View樹的變化,這個方法也會被多次調用。

  • view.measure(int widthMeasureSpec, int heightMeasureSpec)

通過手動對View進行measure來得到View的寬高,這個要根據View的LayoutParams來處理:

match_parent:無法measure出具體的寬高

wrap_content:如下measure,設置最大值

int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST);

int heightMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST);

view.measure(widthMeasureSpec, heightMeasureSpec);

精確值:例如100px


int widthMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);

int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);

view.measure(widthMeasureSpec, heightMeasureSpec);

2、在View的默認實現中,View的測量寬高和最終寬高時相等的,只不過測量寬高形成于measure過程,而最終寬高形成于layout過程。

3、draw過程大概有下面幾步:

  1. 繪制背景:background.draw(canvas);
  2. 繪制自己:onDraw();
  3. 繪制children:dispatchDraw;
  4. 繪制裝飾:onDrawScrollBars

自定義View

自定義View分為以下4類:

  1. 繼承view重寫onDraw方法
  2. 繼承ViewGroup派生特殊的Layout
  3. 繼承特定的View(比如TextView)
  4. 繼承特殊的ViewGroup(比如LinearLayout)

自定義View須知:

  1. 讓View支持wrap_content
  2. 如果有必要,讓你的View支持padding
  3. 盡量不要在View中使用Handler,沒必要
  4. View中如果有線程或者動畫,需要及時停止,參考View#onDetachedFromWindow
  5. View帶有滑動嵌套情形時,需要處理好滑動沖突
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容