- 測量/布局/繪制順序
- 如何引起View的測量/布局/繪制?
- PerformTraversales()
- ViewRoot
- View工作基本流程
- MeasureSpec
- SpecMode
- MeasureSpec和LayoutParams
- RootMeasureSpec
- MeasureSpec
測量/布局/繪制順序
View什么時候測量/布局/繪制?
Invalidate,requestLayout,requestFocus最終都會調用到ViewRoot中的schedulTraversale(),該函數發起一個異步消息,消息處理中調用performTraversals()方法對整個View進行遍歷。
- Invalidate
請求重繪view樹,假如視圖大小沒有變化就不會調用layout(),只繪制那些需要重繪的視圖,誰請求就重繪誰(ViewGroup調用就重繪整個ViewGroup) - requestLayout
只對view樹重新layout,會導致調用measure和layout過程,不會調用draw()過程 - requestFocus
請求view樹的draw過程,但只繪制需要重繪的視圖 - setVisibility()
當View可視狀態在INVISIBLE轉換VISIBLE時,會間接調用invalidate()方法, 當View的可視狀態在INVISIBLE/ VISIBLE 轉換為GONE狀態時,會間接調用requestLayout() 和invalidate方法。同時,由於整個個View樹大小發生了變化,會請求measure()過程以及draw()過程,同樣地,只繪制需要“重新繪制”的視圖
ViewRoot
一個Window中View根節點DecorView, 它的mParent稱為ViewRoot, 對應ViewRootImpl類, 它不是View的子類, 而是個ViewParent. ViewRootImpl是連接Window和DecorView的紐帶, View的焦點, 按鍵, 布局, 渲染等流程都是從ViewRoot中開始的.
View繪制流程從requestLayout觸發, View系統中所有會改變布局的方法都會觸發requestLayout, 如TextView改變文字, ViewGroup添加View等.
View.java
public void requestLayout() { mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); } }
View的requestLayout最終調用到ViewRootImpl
ViewRootImpl.java
public void requestLayout() { checkThread(); mLayoutRequested = true; scheduleTraversals(); }
從scheduleTraversals名字來看, requestLayout只是觸發一個異步的任務. 事實上, View真正的繪制流程是從ViewRootImpl的performTraversals方法開始, 里面會經過measure, layout和draw三個過程. 其中measure用來測量View的寬高, layout用來確定View的位置, 而draw負責渲染View到屏幕上. 大致流程如下:
performTraversals會依次調用performMeasure, performlayout和performDraw方法. 父容器measure方法會調用onMeasure, onMeasure方法會對所有子元素進行measure過程, 以此遍歷完整個View樹. layout和draw流程類似.
measure過程計算View的寬高, 先看measure方法
public final void measure(int widthMeasureSpec, int heightMeasureSpec)
參數measureSpec是父容器傳的對View的尺寸規格限制. 根節點DecorView的measureSpec在ViewRoot中計算出.
MeasureSpec
MeasureSpec字面上可以理解為尺寸規格, 測量過程中父容器會根據自己的MeasureSpec和子View的LayoutParams轉換成對應的MeasureSpec, 子View根據這個MeasureSpec來計算出寬高, 因此我理解MeasureSpec是父容器對子元素的尺寸限制, 這樣對下面的源碼就好理解
View.java
- 使int 類型的高兩位表示模式的實際值,其余30位代表長或寬的實際值—-可以是WRAP_CONTENT、MATCH_PARENT或具體大小exactly size。
- 通過掩碼MODE_MASK進行與運算 “&”,取得模式(mode)以及長或寬(value)的實際值。
- 最高兩位表示模式,后30位表示組件大小的值
最高兩位是00的時候表示”未指定模式”。即MeasureSpec.UNSPECIFIED
最高兩位是01的時候表示”’精確模式”。即MeasureSpec.EXACTLY
最高兩位是11的時候表示”最大模式”。即MeasureSpec.AT_MOST
SpecMode
SpecMode有三種
- UNSPECIFIED: 父容器不對View做任何限制. 一般在系統內部使用, 用於如ScrollView中, 或者需要多次測量來決定最終值的ViewGroup
- EXACTLY: 父容器已經知道View精確大小是SpecSize, 或者限制View大小就是SpecSize
- AT_MOST: 父容器指定View最大size是SpecSize, 一般在LayoutParams中是wrap_content或match_parent時使用.
MeasureSpec和LayoutParams
上面提到子View的MeasureSpec是根據LayoutParams和父容器的MeasureSpec轉換來的, 雖然我們可以自己寫轉換算法, 但是系統里面已經提供了完善的算法. 除了DecorView的MeasureSpec是ViewRootImpl構造出來的, 其他View的轉換方法都一樣.
ViewGroup.java
上述方法就是對子元素進行measure的, 在measure之前通過getChildMeasureSpec方法得到子元素的MeasureSpec.
ViewGroup.java
上面的方法就是根據父容器的MeasureSpec結合View的LayoutParams轉換子元素的MeasureSpec
方法中三個參數意義
- spec: 父容器的MeasureSpec(這個未必是父容器的measure方法傳入的MeasureSpec, 也可以根據情況構造一個)
- padding: 父容器中已經被占用的空間, 如FrameLayout的padding值, LinearLayout前面View占據的空間等
- childDimension: 子元素期望的size(或wrap_content/match_parent)
RootMeasureSpec
根節點View的MeasureSpec在performTraversals中得出. 先計算出窗口的最大可能尺寸desiredWindowWidth/desiredWindowHeight, 然后調用measureHierarchy方法來進入measure流程.:
ViewRootImpl.java
再看getRootMeasureSpec方法實現:
ViewRootImpl.java
DecorView的MeasureSpec根據窗口的LayoutParams按照如下規則生成
- match_parent 明確大小就是窗口大小
- wrap_content 最大不超過窗口大小
- 固定大小 明確大小就是LayoutParams指定的值
至此,view的工作原理到此結束了,接下來就是view的布局流程。見下篇
第一時間獲得博客更新提醒,以及更多Android干貨,源碼分析,歡迎關注我的微信公眾號,掃一掃下方二維碼,即可關注。