話說一個有十年的編程經驗的老漢,決定改行書法,在一個熱火炎炎的中午,老漢拿著毛筆,在一張白紙上寫了個“Hello World!”,從此開啟了他的書法旅程。那么問題來了請問自定義一個控件需要怎樣的流程?我們經常說自定義控件,那么究竟怎樣去自定義一個控件?可能大家都聽過自定義控件是android開發人員的一個檻,其實對于這個我們個人而言是贊同的,因為如果你掌握了自定義控件那么你對android的了解肯定更深了一個檔次,為什么這樣說呢?學過自定義控件你自然會知道。自定義控件相對來說還是比較復雜的,可能在閱讀第一遍你理解的不是特別好,但是不要灰心你就會發現很清晰,我相信認真讀完此博客,你肯定會有收獲。如有謬誤歡迎批評指針,如有疑問歡迎留言,謝謝
相關文章[](http://blog.csdn.net/dmk877/article/details/49632959)
# [Android開發之自定義控件(二)---onLayout詳解](http://blog.csdn.net/dmk877/article/details/49632959)
[](http://blog.csdn.net/dmk877/article/details/49632959)
#
通過本篇博客你將學到以下知識點:
①自定義控件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。接著對上面的過程做一個簡單的總結如下圖

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

注:此圖引自[http://blog.csdn.net/qinjuning/article/details/7226787](http://blog.csdn.net/qinjuning/article/details/7226787)這位大神的博客。
所以說實際上我們在寫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的大小不能超過指定的大小
它有三個主要的方法:
①[getMode](#)(imeasureSpec)它的作用就是根據規格提取出mode,這里的mode是上面的三種模式之一
②[getSize](#)(int measureSpec)它的作用就是根據規格提取出size,這里的size就是我們所說的大小
③[makeMeasureSpec](#)(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有了點理解。
如果你覺得本篇博客對你有幫助就留言頂一個唄。
轉載注明出處:[http://blog.csdn.net/dmk877/article/details/49558367](http://blog.csdn.net/dmk877/article/details/49558367)
如有謬誤歡迎批評指正,如有疑問歡迎留言。我將在第一時間改正或回答。。。