Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android View measure (一) 流程分析

Android View measure (一) 流程分析

編輯:關於Android編程


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


小福負責分享

measure的本質 measure代碼流程 onMeasure方法與MeasureSpec 提出問題




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代碼流程


小福:先從源碼看下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);

界面中所有視圖執行requestLayout,重新布局請求會逐步向上傳遞,最終傳執行當前ViewRootImpl的requestLaout()步驟1中會執行scheduleTraversals,其中發送一個空的消息,把重新布局的請求通過Handler發送到主線程的MeassQueue等待執行(具體可以學習Handler)。因為當前ViewRootImpl是繼承自Handler,所以直接查找覆寫的handleMessage方法,因為傳遞的消息是DO_TRAVERSAL,分支調用performTraversalsperformTraversals方法中調用host.measure(childWidthMeasureSpec, childHeightMeasureSpec); 因為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

measure方法是final類型,說明此方法不能被修改。其中判斷條件(mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT的值是在requestLayout 進行賦值的。只要測量的寬高等發生改變都會觸發第二步。執行當前的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。為什麼?


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved