【原創】Android View框架總結(三)View工作原理


  • 測量/布局/繪制順序
  • 如何引起View的測量/布局/繪制?
  • PerformTraversales()
  • ViewRoot
  • View工作基本流程 
    • MeasureSpec 
      • SpecMode
      • MeasureSpec和LayoutParams
      • RootMeasureSpec

測量/布局/繪制順序

這里寫圖片描述

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干貨,源碼分析,歡迎關注我的微信公眾號,掃一掃下方二維碼,即可關注。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM