Android View measure (一) 流程分析

來源:互聯網
上載者:User

標籤:


 本篇類比三個角色:Android 架構師-小福、Android  控制項開發工程師-小黑、 Android 開發工程師-小白,下面按照三個角色不同角度分析measure過程。


小福負責分享:

  •     measure的本質 - ok
  •     measure代碼流程 - 分析FrameLayout.onMeasure
  •     onMeasure方法與MeasureSpec - ok
  •     提出問題




Android 架構師-小福的分享
一、Measure本質
小福:我今天分享是的measure架構設計相關的,先問一個問題,measure的本質是什嗎?
小黑:這個我知道,是Android系統建立UI介面的measure、layout、draw三步驟的第一步,主要用於測量視圖大小,更詳細點說是把“相對值”(WRAP_CONTENT, FILL_PARENT, MATCH_PARENT)轉換為具體指的過程。


小福:小黑說的對,再問一個問題,視圖大小指的是什嗎?
小白:視圖大小是在視圖在螢幕上顯示的大小,也就是開發的時候通過layout_width與layout_heigh設定的?
小福:小白說的只是其中一個作為開發人員的角度。Android系統設計中Canvas是無窮大的,假如一個螢幕的大小是320 * 480 ,但是layout_width="480px" , layout_heigh="800px",很明顯視圖的寬高大於實際螢幕大小。          問題來了,視圖的大小到底是螢幕上顯示的大小,還是視圖的實際大小(即使是超過了螢幕大小)?
小黑:具體視圖顯示大小是由開發人員設定,之後由我控制項開發工程師在onMeasure中決定,如果向小福說的尺寸,即使超過螢幕我可以決定是width= 320, heigh = 480 還是widt= 480, heigh = 800 ,決定權在我這裡,一會在我分享的時候會寫一個Demo來示範。          (視圖根據繪製大小不同分類:內容型視圖、圖形型視圖)


小白:Canvas是什嗎?小福:這個在之後分享draw過程的時候在詳細討論,可以笼統的理解為畫畫時使用的畫布。

二、Measure代碼流程
小福:先從源碼看下measure執行流程,看看這些過程中都做了些什麼。以下都是android.view.ViewRootImpl.java類中的源碼
public final class ViewRootImpl extends Handler implements ViewParent,        View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {    // 1 所有子視圖的requestLayout方法,最總都會觸發根視圖此方法    public void requestLayout() {        checkThread();        // 需要重新布局        mLayoutRequested = true;        scheduleTraversals();    }    // 調度遍曆    public void scheduleTraversals() {        if (!mTraversalScheduled) {            mTraversalScheduled = true;            .....            // 當前類繼承自Handler,發出一個空訊息,目的是加入Message隊列            sendEmptyMessage(DO_TRAVERSAL);        }    }    @Override    public void handleMessage(Message msg) {        switch (msg.what) {        ...        case DO_TRAVERSAL:            ...            // 處理DO_TRAVERSAL訊息            performTraversals();            ...            break;.....        }    }    // 執行遍曆    private void performTraversals() {        final View host = mView;        int desiredWindowWidth;        int desiredWindowHeight;        int childWidthMeasureSpec;        int childHeightMeasureSpec;        ......        if (mLayoutRequested && !mStopped) {            ......            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);            ......            // host是一個View對象            host.measure(childWidthMeasureSpec, childHeightMeasureSpec);            ......        }        ......    }}

注意:以上代碼中getRootMeasureSpec方法可以或者跟視圖中childWidthMeasureSpec與childHeightMeasureSpec,感興趣的可以自己看下desiredWindowWidth變數的賦值其擷取的是視窗的寬高。
上面的代碼一共分為5個步驟1 requestLayout() -> 2 scheduleTraversals() -> 3 handleMessage() -> 4 performTraversals() -> 5 host.measure(childWidthMeasureSpec, childHeightMeasureSpec);

  1. 介面中所有視圖執行requestLayout,重新布局請求會逐步向上傳遞,最終傳執行當前ViewRootImpl的requestLaout()
  2. 步驟1中會執行scheduleTraversals,其中發送一個空的訊息,把重新布局的請求通過Handler發送到主線程的MeassQueue等待執行(具體可以學習Handler)。
  3. 因為當前ViewRootImpl是繼承自Handler,所以直接尋找覆寫的handleMessage方法,因為傳遞的訊息是DO_TRAVERSAL,分支調用performTraversals
  4. performTraversals方法中調用host.measure(childWidthMeasureSpec, childHeightMeasureSpec); 
  5. 因為host是View對象所以接下來需要查看View.measure方法,才能進一步分析measure流程


    接著上面的measure流程的第五步走下去,以下是android.view.View.java檔案中的源碼:

public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Callback,        AccessibilityEventSource {    // 方法是final類型,說明不能被覆寫或者重載    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {        // 如果有重新請求標誌,或者寬高發生改變        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||                widthMeasureSpec != mOldWidthMeasureSpec ||                heightMeasureSpec != mOldHeightMeasureSpec) {            ......            // 真正執行測量視圖大小操作            // measure ourselves, this should set the measured dimension flag back            onMeasure(widthMeasureSpec, heightMeasureSpec);            ......            // 添加重新請求子視圖布局標誌            mPrivateFlags |= LAYOUT_REQUIRED;        }        ......    }    /**     * Call this when something has changed which has invalidated the     * layout of this view. This will schedule a layout pass of the view     * tree.     */    public void requestLayout() {        if (ViewDebug.TRACE_HIERARCHY) {            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);        }        // 添加重新請求布局標誌        mPrivateFlags |= FORCE_LAYOUT;        mPrivateFlags |= INVALIDATED;        if (mParent != null) {            if (mLayoutParams != null) {                mLayoutParams.resolveWithDirection(getResolvedLayoutDirection());            }            if (!mParent.isLayoutRequested()) {                mParent.requestLayout();            }        }    }}

上面代碼的measure流程可以分為4個步驟
1 measure與requestLayout -> 2 onMeasure

  1. measure方法是final類型,說明此方法不能被修改。其中判斷條件(mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT的值是在requestLayout 進行賦值的。只要測量的寬高等發生改變都會觸發第二步。
  2. 執行當前的onMeasure方法,通過Hierarchy Viewer等工具可以獲知根視圖是FrameLayout類型(這裡就不從源碼驗證了)

    緊接著看下android.widget.FrameLayout類的onMeasure總都做了什嗎?



三、onMeasure方法與MeasureSpec    上面顯示的代碼中參數int widthMeasureSpec, int heightMeasureSpec都是通過MeasureSpec類進行統一處理。     MeasureSpec是一個android.view.View的內部類,封裝了從父類傳送到子類的布局要求資訊。每個MeasureSpec對象描述了空間的高度或寬度。 MeasureSpec由size和mode組成。
1. MeasureSpec的方法介紹:
類名.方法名 解釋
MeasureSpec.getMode(int measureSpec) 根據提供的測量值(格式)提模數式(上述三個模式之一)
MeasureSpec.getSize(int measureSpec) 根據提供的測量值(格式)提取大小值
MeasureSpec.makeMeasureSpec(int size,int mode) 根據提供的大小值和模式建立一個測量值(格式)


2. MeasureSpec有三種mode,分別說明並描述模式與layout參數值的對應關係
模式 翻譯 模式與Layout參數對應關係  模式描述
UNSPECIFIED 無限制   parent view不約束child view的大小
AT_MOST 最多的 wrap_content child view可以在parent view範圍內取值
EXACTLY 準確的 fill_parent(例如50dip) parent view為child view指定固定大小

3. MeasureSpec通過位運行從int類型的值中擷取mode與sieze更詳細的分析請查看《android中onMeasure初看,深入理解布局之一!》
四、提出問題為什麼布局需要父視圖與子視圖共同決定? 為什麼不直接設定寬和高?如果不這樣就像HTML那樣指定固定大小,這樣會造成一個過大把另外一個擠出去。因為HTML是具有整個頁面的控制權。而Android是拆分開的,這樣做保證了最末端的(介面開發)影響其他層級的布局,需要按照(控制項開發)的規則來。view.setWidth, view.setHeight ? 不是
需要通過measureChilde(view, width, height), 或者childView.measure();
還有哪些?View.getWidth(), View.getHight()
View.getMeasureWidth(), View.getMeasureHiegh()
 一個未添加到視圖中的? 但是有時getMesureHeight 依然是返回0。為什嗎?


Android View measure (一) 流程分析

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.