1. 程式人生 > >Android開發之自定義控制元件(一)---onMeasure詳解

Android開發之自定義控制元件(一)---onMeasure詳解

         話說一個有十年的程式設計經驗的老漢,決定改行書法,在一個熱火炎炎的中午,老漢拿著毛筆,在一張白紙上寫了個“Hello World!”,從此開啟了他的書法旅程。那麼問題來了請問自定義一個控制元件需要怎樣的流程?我們經常說自定義控制元件,那麼究竟怎樣去自定義一個控制元件?可能大家都聽過自定義控制元件是android開發人員的一個檻,其實對於這個我們個人而言是贊同的,因為如果你掌握了自定義控制元件那麼你對android的瞭解肯定更深了一個檔次,為什麼這樣說呢?學過自定義控制元件你自然會知道。自定義控制元件相對來說還是比較複雜的,可能在閱讀第一遍你理解的不是特別好,但是不要灰心你就會發現很清晰,我相信認真讀完此部落格,你肯定會有收穫。

如有謬誤歡迎批評指標,如有疑問歡迎留言,謝謝

相關文章

通過本篇部落格你將學到以下知識點:

①自定義控制元件onMeasure的過程

②徹底理解MeasureSpec

③瞭解View的繪製流程

④對測量過程中需要的谷歌工程師給我們準備好的其它的一些方法的原始碼深入理解。

        為了響應文章的開頭,我們從一個“Hello World!”的小例子說起,這個例子我們自定義一個View讓它顯示“Hello World!”非常簡單,程式碼如下

package com.example.customviewpractice;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;

public class CustomView1 extends View {

	private Paint mPaint;
	private String str = "Hello World!";

	public CustomView1(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	private void init() {
		// 例項化一個畫筆工具
		mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
		// 設定字型大小
		mPaint.setTextSize(50);
		// 設定畫筆顏色
		mPaint.setColor(Color.RED);

	}

	// 重寫onMeasure方法
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}

	// 重寫onDraw方法
	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		/**
		 * getWidth() / 2 - mPaint.measureText(str) / 2讓文字在水平方向居中
		 */
		canvas.drawText(str, getWidth() / 2 - mPaint.measureText(str) / 2,
				getHeight()/2, mPaint);
	}

}

它的佈局檔案如下

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <com.example.customviewpractice.CustomView1
        android:id="@+id/cus_textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/darker_gray" />

</LinearLayout>
執行結果如下

         這樣一個大大的"Hello World!"呈現在我們面前,可能有的人會問到底怎樣去自定義一個控制元件呢?別急我們慢慢的,一點一點的去學習,首先你可以想象一下,假如我要求你去畫一個空心的圓,你會怎麼做,首先你要拿張白紙,然後你會問我圓的半徑多大?圓的位置在哪?圓的線條顏色是什麼?圓的線條粗細是多少?等我把這些問題都告訴你之後,你就會明白要求,並按照這個要求去畫一個圓。我們自定義控制元件呢,也是這樣需要下面三個步驟:

①重寫onMeasure(獲得半徑的大小)

②重寫onLayout(獲得圓的位置)

③重寫onDraw(用例項化的畫筆包括:顏色,粗細等去繪畫)

待這三個方法都重寫完後我們的自定義控制元件就完成了,為了講的能夠詳細我們這一篇專門來講解onMeasure以及和其相關的方法,首先我們需要明白的是Android給我提供了可以操縱控制元件測量的方法是onMeasure()方法,在上面的自定義控制元件中我們採用了其預設的實現

        @Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}
看到這,可能大部分人都要問,這裡的widthMeasureSpec和heightMeasureSpec是從何處來?要到哪裡去?其實這兩個引數是由View的父容器傳遞過來的測量要求,在上述自定義控制元件中也就是我們的LinearLayout,為什麼這麼說?這麼說是有依據的我們都知道在Activity中可以呼叫其setContentView方法為介面填充一個佈局
 protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
在setContentView方法中做了哪些事情呢?我們看看他的原始碼
public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
    }
我們看到它呼叫了getWindow方法,沒什麼可說的,跟著步驟去看getWindow方法的原始碼
public Window getWindow() {
        return mWindow;
    }

這裡返回一個Window例項,其本質是繼承Window的PhoneWindow,所以在Acitivity中的setContentView中getWindow.setContentView()getWindow.setContentView()其實就是PhoneWindow.setContentView()我們來Look Look它的程式碼

public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);
        final Callback cb = getCallback();
        if (cb != null) {
            cb.onContentChanged();
        }
    }
該方法首先會判斷是否是第一次呼叫setContentView方法,如果是第一次呼叫則呼叫installDecor()方法,否則將mContentParent中的所有View移除掉

然後呼叫LayoutInflater將我們的佈局檔案載入進來並新增到mContentParent檢視中。跟上節奏我們來看看installDecor()方法的原始碼

private void installDecor() {
        if (mDecor == null) {
        	//mDecor為空,則建立一個Decor物件
            mDecor = generateDecor();
            mDecor.setIsRootNamespace(true);
        }
        if (mContentParent == null) {
        	//generateLayout()方法會根據視窗的風格修飾,選擇對應的修飾佈局檔案  
            //並且將id為content(android:id="@+id/content")的FrameLayout賦值給mContentParent  
            mContentParent = generateLayout(mDecor);
                          。。。省略部分程式碼。。。
        }
    }

可以發現在這個方法中首先會去判斷mDecor是否為空如果為空會呼叫generateDecor方法,它幹了什麼呢?

protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }
可以看到它返回了一個DecorView,DecorView類是FrameLayout的子類,是一個內部類存在於PhoneWindow類中,這裡我們知道它是FrameLayout的子類就ok了。 installDecor方法中判斷了mDecor是否為空後,接著會在該方法中判斷mContentParent是否為空,如果為空就會呼叫generateLayout方法,我們來看看它做了什麼。。。
protected ViewGroup generateLayout(DecorView decor) {
		 	。。。省略部分程式碼。。。
	        View in = mLayoutInflater.inflate(layoutResource, null);
	        decor.addView(in, new ViewGroup.LayoutParams(FILL_PARENT, FILL_PARENT));
	        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
	        。。。省略部分程式碼。。。
	        return contentParent;
	    }

根據視窗的風格修飾型別為該視窗選擇不同的窗口布局檔案(根檢視),這些視窗修飾佈局檔案指定一個用來存放Activity自定義佈局檔案的ViewGroup檢視,一般為FrameLayout 其id 為: android:id="@android:id/content",並將其賦給mContentParent,到這裡mContentParent和mDecor均已生成,而我們xml佈局檔案中的佈局則會被新增至mContentParent。接著對上面的過程做一個簡單的總結如下圖


我們用張圖來說明下層次結構


所以說實際上我們在寫xml佈局檔案的時候我們的根佈局並不是我們能在xml檔案中能看到的最上面的那個,而是FrameLayout,我們再用谷歌給我提供的hierarchyviewer這個工具來看看我們最開始的那個小例子的佈局情況,看完你就明白了


看到了吧,在LinearLayout的上面是FrameLayout。到這可能有的人會說你這是寫的啥?跟自定義控制元件一點關係都沒有,其實只有瞭解了上面過程我們才能更好的去理解自定義控制元件
到這裡我們回到最初我們提出的問題widthMeasureSpec和heightMeasureSpec是從哪來?我們在上面提到是從其父View傳遞過來的,那麼它的父View的這兩個引數又是從哪來,這樣一步一步我們就需要知道View繪製的時候是從兒開始的,其實擔任此重任的是ViewRootImpl,繪製開始是從ViewRootImpl中的performTraversals()這個方法開始的,我們來看看原始碼,可能有的人會說又看原始碼,只有看原始碼才能學的更透徹,這裡我們只看主要的程式碼,理解其流程即可,其實performTraversals()方法的程式碼很多,我們省略後如下

private void performTraversals() {
		 
             int desiredWindowWidth;
	     int desiredWindowHeight;
	     int childWidthMeasureSpec;
	     int childHeightMeasureSpec;
	     
	     。。。省略部分程式碼。。。
		 
             DisplayMetrics packageMetrics =
	     mView.getContext().getResources().getDisplayMetrics();
	     desiredWindowWidth = packageMetrics.widthPixels;
	     desiredWindowHeight = packageMetrics.heightPixels;
	     
	     。。。省略部分程式碼。。。
	             
           childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
           childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);

	    。。。省略部分程式碼。。。
	            
	   host.measure(childWidthMeasureSpec, childHeightMeasureSpec);
	    
	     。。。省略部分程式碼。。。
	            
	    }
我們清楚的看到在此呼叫了getRootMeasureSpec方法後會得到childWidthMeasureSpec和childHeightMeasureSpec,得到的這個資料作為引數傳給host(這裡的host是View)measure方法。在呼叫getRootMeasureSpec時需要兩個引數desiredWindowWidth ,lp.width和desiredWindowHeight  , lp.height這裡我們看到desiredWindowWidth 和desiredWindowHeight就是我們視窗的大小而lp.width和lp.height均為MATCH_PARENT,其在mWindowAttributes(WindowManager.LayoutParams型別)將值賦予給lp時就已被確定。引數搞明白後我們來看看getRootMeasureSpec的原始碼,看看它都是幹了個啥。
    /**
     * Figures out the measure spec for the root view in a window based on it's
     * layout params.
     *
     * @param windowSize
     *            The available width or height of the window
     *
     * @param rootDimension
     *            The layout params for one dimension (width or height) of the
     *            window.
     *
     * @return The measure spec to use to measure the root view.
     */
    private int getRootMeasureSpec(int windowSize, int rootDimension) {
        int measureSpec;
        switch (rootDimension) {

        case ViewGroup.LayoutParams.FILL_PARENT:
            // Window can't resize. Force root view to be windowSize.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            // Window can resize. Set max size for root view.
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            // Window wants to be an exact size. Force root view to be that size.
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
上面三種情況的英文註釋很簡單自己翻譯下即可理解。總之這個方法執行後不管是哪一種情況我們的根檢視都是全屏的。在上面中大家看到MeasureSpec這個類有點陌生,MeasureSpec這個類的設計很精妙,對於學習自定義View也非常重要,理解它對於學習自定義控制元件非常有用接下來我們就花點篇幅來詳細的講解一下這個類,measurespec封裝了父類傳遞給子類的測量要求,每個measurespec代表寬度或者高度的要求以及大小,也就是說一個measurespec包含size和mode。它有三種mode(模式)
 ①UNSPECIFIED:父View沒有對子View施加任何約束。它可以是任何它想要的大小。 
 ②EXACTLY:父View已經確定了子View的確切尺寸。子View將被限制在給定的界限內,而忽略其本身的大小。 

 ③AT_MOST:子View的大小不能超過指定的大小

它有三個主要的方法:

①(imeasureSpec)它的作用就是根據規格提取出mode,這裡的mode是上面的三種模式之一

②(int measureSpec)它的作用就是根據規格提取出size,這裡的size就是我們所說的大小

③(int size, int mode)根據size和mode,建立一個測量要求。

說了這些可能大家仍然是一頭霧水接下來我們看看它的原始碼,MeasureSpec是View的內部類,它的原始碼如下

public static class MeasureSpec {
        private static final int MODE_SHIFT = 30;
        //轉化為二進位制就是11向左移30位,其結果為:11 0000...(11後跟30個0)
        private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
        /**
         * 下面就是MeasureSpec的三種模式
         */
        //0左移30位變為 :00  0000...(00後跟30個0)
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;
        //01左移30位變為:01 0000...(01後跟30個0)
        public static final int EXACTLY     = 1 << MODE_SHIFT;
        //10左移30位變為:10 0000...(10後跟30個0)
        public static final int AT_MOST     = 2 << MODE_SHIFT;

        //建立一個測量的規格其高位的前兩位代表mode,後面30為代表size,即measureSpec=size+mode;
        public static int makeMeasureSpec(int size, int mode) {
            return size + mode;
        }

        //與運算獲得mode,這裡為什麼可以得到mode?因為從measureSpec=size+mode,而MODE_MASK=11 0000...(11後跟30個0)
        //我們都知道   & 運算的規則是"遇0為0,遇1不變",而MODE_MASK的前兩位為11後面30為全為0,這樣進行運算後就可以得到measureSpec的前兩位,而剛好
        //這前兩位就代表了mode。
        public static int getMode(int measureSpec) {
            return (measureSpec & MODE_MASK);
        }
        //這裡的思想跟getMode方法是一樣的,首先對MODE_MASK進行取反,得到的結果為00 1111...(00後跟30個1)& 運算的規則是"遇0為0,遇1不變",而此時~MODE_MASK
        //的前兩位為0後面30為全為1,所以measureSpec&~MODE_MASK得到的結果去後面30位,這後面的30位就是我們的size
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }

        public static String toString(int measureSpec) {
        	。。。內容省略。。。
        }
    }

MeasureSpec這個類的設計是非常巧妙的,用int型別佔有32位,它將其高2位作為mode,後30為作為size這樣用32位就解決了size和mode的問題

看完的它的原始碼大家可能似懂非懂,那麼我們就舉個例子畫個圖,讓你徹底理解它的設計思想。

假如現在我們的mode是EXACTLY,而size=101(5)那麼size+mode的值為:


這時候通過size+mode構造除了MeasureSpec物件及測量要求,當需要獲得Mode的時候只需要用measureSpec與MODE_TASK相與即可如下圖


我們看到得到的值就是上面的mode,而如果想獲得size的話只需要只需要measureSpec與~MODE_TASK相與即可如下圖


我們看到得到值就是上面的size。關於這個設計思想大家好好的,慢慢的體會下。

好了到這裡我們應該對MeasureSpec有了一定的理解。這時返回去看看我們的getRootMeasureSpec方法,你是不是能看懂了?看懂後回到performTraversals方法,通過getRootMeasureSpec方法得到childWidthMeasureSpec和childHeightMeasureSpec後,我們看到在performTraversals方法中會呼叫host.measure(childWidthMeasureSpec,childHeightMeasureSpec),這樣childWidthMeasureSpec和childHeightMeasureSpec這兩個測量要求就一步一步的傳下去並由當前View與其父容器共同決定其測量大小,在這裡View與ViewGroup中的遞迴呼叫過程中有幾個重要的方法,而對於View是measure方法,接著我們看看host.measure也就是View的measure方法的原始碼吧

public class View implements ... {
		
	。。。省略了部分程式碼。。。
	
	 public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
			//判斷是否為強制佈局,即帶有“FORCE_LAYOUT”標記 以及 widthMeasureSpec或heightMeasureSpec發生了改變
	        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
	                widthMeasureSpec != mOldWidthMeasureSpec ||
	                heightMeasureSpec != mOldHeightMeasureSpec) {
	        	//清除MEASURED_DIMENSION_SET標記   ,該標記會在onMeasure()方法後被設定
	            mPrivateFlags &= ~MEASURED_DIMENSION_SET;

	            if (ViewDebug.TRACE_HIERARCHY) {
	                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
	            }
	            // 1、 測量該View本身的大小
	            onMeasure(widthMeasureSpec, heightMeasureSpec);

	            if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
	                throw new IllegalStateException("onMeasure() did not set the"
	                        + " measured dimension by calling"
	                        + " setMeasuredDimension()");
	            }
	          //下一步是layout了,新增LAYOUT_REQUIRED標記
	            mPrivateFlags |= LAYOUT_REQUIRED;
	        }
	        mOldWidthMeasureSpec = widthMeasureSpec;//儲存值
	        mOldHeightMeasureSpec = heightMeasureSpec;//儲存值
	    }	
	
	}
看到了吧,在measure方法中呼叫了onMeasure方法,你是不是應該笑30分鐘?終於見到我們的onMeasure方法了,這裡的onMeasure就是我們重寫的onMeasure,它接收兩個引數widthMeasureSpec和heightMeasureSpec這兩個引數由父View構建,表示父View對子View的測量要求。它有它的預設實現,即重寫後我們什麼都不做直接呼叫super.onMeasure方法它的預設實現如下
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }
在onMeasure方法中直接呼叫setMeasuredDimension方法,在這裡它會呼叫getSuggestedMinimumWidth方法得到的資料傳遞給getDefaultSize方法,首先來看看getSuggestedMinimunWidth,getDefaultSize以及setMeasuredDimension這三個方法的原始碼吧
protected int getSuggestedMinimumWidth() {
		//獲得android:minHeight這個屬性的值,一般不設定此屬性如果沒有設定的話mMinWidth=0
        int suggestedMinWidth = mMinWidth;
        if (mBGDrawable != null) {
        	//獲得背景的寬度
            final int bgMinWidth = mBGDrawable.getMinimumWidth();
            //從背景的寬度和minHeight屬性中選出一個最大的值作為返回值
            if (suggestedMinWidth < bgMinWidth) {
                suggestedMinWidth = bgMinWidth;
            }
        }
        return suggestedMinWidth;
    }
	//在這裡這裡size是getSuggestedMinimumWidth方法的返回值,這也是預設的大小
	//measureSpec是父View傳過來的measureSpec,測量要求
	public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        //獲得測量的模式
        int specMode = MeasureSpec.getMode(measureSpec);
        //獲得測量的大小
        int specSize =  MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        //模式為Unspecified及未指定大小
        case MeasureSpec.UNSPECIFIED:
        	//將上面的size作為結果返回
            result = size;
            break;
        case MeasureSpec.AT_MOST://模式為At_Most,此時使用預設的大小size
        case MeasureSpec.EXACTLY://模式為Exactly,此時返回測量值
            result = specSize;
            break;
        }
        return result;
    }
	//為View設定寬和高
	protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= MEASURED_DIMENSION_SET;
    }
這只是一個自定義View的預設實現,如果想按照我們的要求來進行繪製的話,重寫onMeasure需要新增我們自己的邏輯去實現,最終在onMeasure方法中會呼叫setMeasureDimenSion決定我們的View的大小,這也是我們重寫onMeasure方法的最終目的。

上面這些是對於一個View的測量,android中在進行測量時有兩種情況,一種是一個View如Button,ImaeView這中,不能包含子View的對於這種測量一下就ok了,另外一種就是ViewGroup像LinearLayout,FrameLayout這種可以包含子View的,對於這種我們就需要迴圈遍歷每一個子View併為其設定大小,在自定義的ViewGroup中重寫onMeasure如下的虛擬碼

//某個ViewGroup型別的檢視  
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
	  //必須呼叫super.ononMeasure()或者直接呼叫setMeasuredDimension()方法設定該View大小,否則會報異常。  
	  super.onMeasure(widthMeasureSpec , heightMeasureSpec)  
	     //setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
	     //        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
	       
	  //一、遍歷每個子View  
	  for(int i = 0 ; i < getChildCount() ; i++){  
	    View child = getChildAt(i);  
	    //呼叫子View的onMeasure,設定他們的大小,
	    child.onMeasure(childWidthMeasureSpec, childHeightMeasureSpec);  
	  }  
	  //二、直接呼叫ViewGroup中給我們提供好的measureChildren方法、
	  measureChildren(widthMeasureSpec, heightMeasureSpec);
	}
其實ViewGroup已經為我們提供了測量子View的方法,主要有measureChildren,measureChild和getMeasureSpec,下面我們來分別看看這三個方法都是幹了個啥?

measureChildren方法的原始碼如下

//widthMeasureSpec和heightMeasureSpec:父View傳過來的測量要求
	protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
        final int size = mChildrenCount;
        final View[] children = mChildren;
        //遍歷所有的View
        for (int i = 0; i < size; ++i) {
            final View child = children[i];
            //Gone掉的View排除
            if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
            }
        }
    }
可以看到在measureChildren方法中會遍歷所有的View然後對每一個View(不包括gone的View)呼叫measureChild方法,順其自然我們來看看measureChild方法的原始碼
protected void measureChild(View child, int parentWidthMeasureSpec,
            int parentHeightMeasureSpec) {
		// 獲取子元素的佈局引數
        final LayoutParams lp = child.getLayoutParams();
        //將父容器的測量規格以及上下和左右的邊距還有子元素本身的佈局引數傳入getChildMeasureSpec方法計算最終測量要求 
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight, lp.width);
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom, lp.height);
        // 將計算好的寬高詳細測量值傳入measure方法,完成最後的測量
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }
在measureChild方法中通過getChildMeasureSpec得到最終的測量要求,並將這個測量要求傳遞給childView的measure方法,就會按照View的那一套邏輯執行。在這裡看到呼叫了getChildMeasureSpec方法我們來看看這個方法的原始碼
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
		//獲取父View的測量模式
        int specMode = MeasureSpec.getMode(spec);
        //獲取父View的測量大小
        int specSize = MeasureSpec.getSize(spec);
       //父View計算出的子View的大小,子View不一定用這個值
        int size = Math.max(0, specSize - padding);
        //宣告變數用來儲存實際計算的到的子View的size和mode即大小和模式
        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
         // Parent has imposed an exact size on us
        //如果父容器的模式是Exactly即確定的大小
        case MeasureSpec.EXACTLY:
        	//子View的高度或寬度>0說明其實一個確切的值,因為match_parent和wrap_content的值是<0的
            if (childDimension >= 0) {
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
                //子View的高度或寬度為match_parent
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
            	// Child wants to be our size. So be it.
                resultSize = size;//將size即父View的大小減去邊距值所得到的值賦值給resultSize
                resultMode = MeasureSpec.EXACTLY;//指定子View的測量模式為EXACTLY
               //子View的高度或寬度為wrap_content
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            	// Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;//將size賦值給result
                resultMode = MeasureSpec.AT_MOST;//指定子View的測量模式為AT_MOST
            }
            break;
         // Parent has imposed a maximum size on us
        //如果父容器的測量模式是AT_MOST
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
            	// Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
            	// Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                // 因為父View的大小是受到限制值的限制,所以子View的大小也應該受到父容器的限制並且不能超過父View  
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            	 // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
         // Parent asked to see how big we want to be
        //如果父容器的測量模式是UNSPECIFIED即父容器的大小未受限制
        case MeasureSpec.UNSPECIFIED:
        	//如果自View的寬和高是一個精確的值
            if (childDimension >= 0) {
            	 // Child wants a specific size... let him have it
            	//子View的大小為精確值
                resultSize = childDimension;
                //測量的模式為EXACTLY
                resultMode = MeasureSpec.EXACTLY;
                //子View的寬或高為match_parent
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
            	// Child wants to be our size... find out how big it should
                // be
            	//resultSize=0;因為父View的大小是未定的,所以子View的大小也是未定的
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            	// Child wants to determine its own size.... find out how
                // big it should be
            	
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //根據resultSize和resultMode呼叫makeMeasureSpec方法得到測量要求,並將其作為返回值
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

我們經常說View的大小是由父View以及當前View共同決定的,這一點從上面這個方法也可以看出。但是這只是一個期望的大小,其大小的最終決定權由setMeasureDimenSion方法決定。

所以最終View的大小將受以下幾個方面的影響(以下三點摘自:http://blog.csdn.net/qinjuning/article/details/8074262此部落格,這是一個大神。。)

 1、父View的MeasureSpec屬性;

 2、子View的LayoutParams屬性;

 3、setMeasuredDimension()或者其它類似設定 mMeasuredWidth 和 mMeasuredHeight 值的方法。

關於View的測量過程就介紹完了,可能你一遍沒有讀懂,只要你認真的去看我相信你一定會有收穫,如果你一遍就讀懂了,千萬別告訴我,我會傷心的,哈哈,因為我花了一週的時間才對onMeasure有了點理解。

如果你覺得本篇部落格對你有幫助就留言頂一個唄。

如有謬誤歡迎批評指正,如有疑問歡迎留言。我將在第一時間改正或回答。。。






相關推薦

Android開發定義控制元件()---onMeasure

         話說一個有十年的程式設計經驗的老漢,決定改行書法,在一個熱火炎炎的中午,老漢拿著毛筆,在一張白紙上寫了個“Hello World!”,從此開啟了他的書法旅程。那麼問題來了請問自定義一個控制元件需要怎樣的流程?我們經常說自定義控制元件,那麼究竟怎樣去自定義一

Android 開發定義控制元件開發-01

最近一直在忙於公司的專案,因為要去現場測試正式使用,專案不大但是經手了三個人,到我這裡只能去填坑了,不說這個了,說一下今天得主題,自定義控制元件之基本圖形繪製。 我們平時畫圖需要兩種工具:紙和筆。在Android中 Paint 就是畫筆,而Canvas類就是紙,在這裡叫做畫布。 所以

Android 開發定義控制元件開發-02

1.畫筆的基本設定 : 1.setColor() 該函式的作用是設定畫筆顏色,完整的函式宣告如下: void setColor(int color) 我們知道,一種顏色是由紅、綠、藍三色合成出來的,所以引數 color 只能取8位的0xAARRGGBB樣式顏色值。 其中:

Android開發定義控制元件--ViewPager

package com.itheima18.viewpager; import java.util.ArrayList; import java.util.Timer; import java.util.concurrent.Executors; import java.util.concurrent.Sc

Android開發最新Recyclerview控制元件的使用

    本篇博文主要給大家分享關於RecyclerView控制元件的使用及通過繼承RecyclerView來實現滑動時載入圖片的優化方案,也同樣能解決防止圖片亂序的問題,之前有在網上有看到大神對Android中ListView非同步載入圖片亂序問題進行過分析,並深入剖析原理

Android軟體開發 定義控制元件

Android軟體開發之 自定義控制元件 雖然Android系統提供了各種各樣的控制元件供我們開發使用,但在實際的開發中,系統提供的控制元件有時候不能滿足我們的需求,這時我們就需要自定義一個控制元件。 下面的例子就來自定義一個簡單的Button: 首先是佈局,image_btn.xml: <?xml

Android定義控制元件系列:onMeasure()方法中如何測量一個控制元件尺寸()

轉載請註明出處:http://blog.csdn.net/cyp331203/article/details/45027641 今天的任務就是詳細研究一下protected void onMeasure(int widthMeasureSpec, int he

Android開發筆記定義控制元件(物流時間軸的實現)

最近修改專案遇到檢視物流這個需求,經過一個下午的時間的終於搞定,趁著這個時間點,趕快把這個功能抽取出來,方便大家以後開發的需要,幫助到更多的人 先看效果圖,如下 看完之後,分析可知道,主要是兩部分,一個頭部和一個body. 那我們最主要的工作就是bod

android開發定義TextView設定字間距以及通過TextView控制元件屬性設定行間距

眾所周知,我們的TextView控制元件是沒有設定字間距的屬性滴!為了現實這一夢想,我玩起來了自定義TextView,從而來設定字間距: 自定義TextView設定字間距 第一步:建立自定義TextView: package com.zanel

Android 定義控制元件 () ,柱狀圖 ,Canvas 繪製 柱狀圖 ,支援觸控操作

專案中,經常會用到統計圖表,個性化展示資料,增加趣味性,之前也用過百度Echarts來展示,效果很不錯,包括一些互動操作,不得不說,echarts幫我我們實現了絕大多數的需求,體積小不說,實現方式也很簡單,後來想了想,為什麼不用安卓Canvas繪製呢,畢竟是安卓開發攻城獅,下

Android UI高階定義控制元件

在android中有許多控制元件,比如editview、button等,但是,有時這些控制元件並不能滿足我們的需求,這時,我們就需要去自定義一個控制元件來滿足我們的需要,下面給出一個仿360加速球的demo. 1、首先,來看一下360加速球的效果 2、

iOS開發學習-定義控制元件賦值問題--在model的set方法中給控制元件賦值

在自定義控制元件的過程中,剛開始的時候碰到問題是如何給各控制元件動態賦值,最初的想法是把各控制元件屬性放在.h檔案中定義.然後在控制器內獲取資料一一賦值(可行),但是這樣就增加了控制器中的程式碼,比如給定一個場景: collectionViewCell中,有10個控制元件,需要顯示10個數據,這

讓你的app提升一個檔次-Android酷炫定義控制元件

這是我近期整理的比較酷炫並且我們會經常用到的custom view,也有一些不是custom view,但是也是android UI相關的,實現了酷炫UI效果的開源庫,合理利用這些開源庫,可以讓你的app提升一個檔次!總結的專案最後維護時間一般不會超過6個月,會持續更新。部落格可能不能實

一起Talk Android吧(第一百回:Android中使用定義控制元件

各位看官們,大家好,上一回中咱們說的是Android中使用自定義佈局的例子,這一回說的例子是Android中使用自定義控制元件。閒話休提,言歸正轉。讓我們一起Talk Android吧! 看官們,我們在上一回中通過自定義佈局巧妙地實現了分隔線,不過這個分隔線中看

android 8.0 定義控制元件onmesure獲取寬度為0

最近專案需要適配8.0版本,自定義控制元件出現了下面的問題 第一次顯示此彈窗字型出現了偏移,找到原因是textpaint在繪製文字的時候 canvas.drawText(itemText, x + (controlWidth / 2) -textRect.width

Android開發定義可清空內容的EditText

在開發過程中不可避免的總會遇到比如登入註冊、使用者資訊修改等,這時候又是不可避免的會用到EditText控制元件。這個控制元件的使用頻率雖然幾乎類似我們吃飯用“筷子”的頻率,but能不能用出花樣

Android開發定義圓角矩形圖片ImageView

android中的ImageView只能顯示矩形的圖片,這樣一來不能滿足我們其他的需求,比如要顯示圓角矩形的圖片,這個時候,我們就需要自定義ImageView了,其原理就是首先獲取到圖片的Bitmap,然後進行裁剪對應的圓角矩形的bitmap,然後在onDraw()進行繪製

Qt編寫定義控制元件開關按鈕

從2010年進入網際網路+智慧手機時代以來,各種各樣的APP大行其道,手機上面的APP有很多流行的元素,開關按鈕個人非常喜歡,手機QQ、360衛士、金山毒霸等,都有很多開關控制一些操作,在Qt widgets應用專案上,在專案中應用些類似的開關按鈕,估計也會為專案增添不少新鮮

Qt定義控制元件(開關按鈕)

簡述 接觸過iOS系統的童鞋們應該對開關按鈕很熟悉,在設定裡面經常遇到,切換時候的滑動效果比較帥氣。 通常說的開關按鈕,有兩個狀態:on、off。 下面,我們利用自定義控制元件來實現一個開關按鈕。 原理 重寫滑鼠按下事件(mousePres

android開發定義屬性、View和使用

“自定義”這三字聽起來就像是一個高階程式設計師所擁有的一樣!太不接地氣了!come on,baby,讓我們成為高階程式設計師吧!哈哈! 第一步:首先建立一個工程專案,在專案中的res/values/下建立atts.xml檔案,在該檔案中: <?xml version