本文轉自:http://blog.csdn.net/u012604322/article/details/17093421java
能夠說重載onMeasure(),onLayout(),onDraw()三個函數構建了自定義View的外觀形象。再加上onTouchEvent()等重載視圖的行爲,能夠構建任何咱們須要的可感知到的自定義View。android
本節咱們探索自定義View中onMeasure()起到了什麼樣的做用,題外要插的一句是,Activity框架,View框架中大量的on函數基本上都應用到了Template模式,掌握這一模式對於理解這些框架大有裨益。程序員
咱們知道,無論是自定義View仍是系統提供的TextView這些,它們都必須放置在LinearLayout等一些ViewGroup中,所以理論上咱們能夠很好的理解onMeasure(),onLayout(),onDraw()這三個函數:1.View自己大小多少,這由onMeasure()決定;2.View在ViewGroup中的位置如何,這由onLayout()決定;3.繪製View,onDraw()定義瞭如何繪製這個View。app
首先咱們看看TextView.java中的onMeasure()源碼:框架
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int width; int height; ... if (widthMode == MeasureSpec.EXACTLY) { // Parent has told us how big to be. So be it. width = widthSize; } else { if (mLayout != null && mEllipsize == null) { des = desired(mLayout); } ... setMeasuredDimension(width, height);
首先咱們要理解的是widthMeasureSpec, heightMeasureSpec這兩個參數是從哪裏來的?onMeasure()函數由包含這個View的具體的ViewGroup調用,所以值也是從這個ViewGroup中傳入的。這裏我直接給出答案:子類View的這兩個參數,由ViewGroup中的layout_width,layout_height和padding以及View自身的layout_margin共同決定。權值weight也是尤爲須要考慮的因素,有它的存在狀況可能會稍微複雜點。ide
瞭解了這兩個參數的來源,還要知道這兩個值的做用。咱們只取heightMeasureSpec做說明。這個值由高32位和低16位組成,高32位保存的值叫specMode,能夠經過如代碼中所示的MeasureSpec.getMode()獲取;低16位爲specSize,一樣能夠由MeasureSpec.getSize()獲取。那麼specMode和specSize的做用有是什麼呢?要想知道這一點,咱們須要知道代碼中的最後一行,全部的View的onMeasure()的最後一行都會調用setMeasureDimension()函數的做用——這個函數調用中傳進去的值是View最終的視圖大小。也就是說onMeasure()中以前所做的全部工做都是爲了最後這一句話服務的。函數
咱們知道在ViewGroup中,給View分配的空間大小並非肯定的,有可能隨着具體的變化而變化,而這個變化的條件就是傳到specMode中決定的,specMode一共有三種可能:oop
MeasureSpec.EXACTLY:父視圖但願子視圖的大小應該是specSize中指定的。佈局
MeasureSpec.AT_MOST:子視圖的大小最可能是specSize中指定的值,也就是說不建議子視圖的大小超過specSize中給定的值。this
MeasureSpec.UNSPECIFIED:咱們能夠隨意指定視圖的大小。
由TextView中源碼也能夠知道這個值的設計意義是爲了根據ViewGroup中具體可以提供的空間大小來指定子View的視圖大小。
經過以上這些分析,能夠知道視圖最終的大小由父視圖,子視圖以及程序員根據須要決定,良好的設計通常會根據子視圖的measureSpec設置合適的佈局大小。
講到上述這些內容,可能已經瞭解瞭如何去使用onMeasure來設置咱們的視圖的大小,但還有一個疑惑的地方,EXACTLY,AT_MOST,UNSPECIFIED和layout_是如何對應的呢?什麼狀況下對應什麼值呢?
咱們經過以下例子,稍做了解:
<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:gravity="center" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" > <LinearLayout android:layout_width="200dp" android:layout_height="wrap_content" android:paddingTop="20dp" android:layout_marginTop="30dp" android:background="@android:color/darker_gray" > <com.sean.myview.MyView android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingTop="10dp" android:layout_marginTop="15dp" android:background="@android:color/holo_red_light" /> </LinearLayout> </LinearLayout>
效果圖以下:
package com.sean.myview; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.View; public class MyView extends View { public MyView(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub Log.d("MyView","------", new Throwable()); int speSize = MeasureSpec.getSize(heightMeasureSpec); int speMode = MeasureSpec.getMode(heightMeasureSpec); Log.d("MyView", "---speSize = " + speSize + ""); Log.d("MyView", "---speMode = " + speMode + ""); if(speMode == MeasureSpec.AT_MOST){ Log.d("MyView", "---AT_MOST---"); } if(speMode == MeasureSpec.EXACTLY){ Log.d("MyView", "---EXACTLY---"); } if(speMode == MeasureSpec.UNSPECIFIED){ Log.d("MyView", "---UNSPECIFIED---"); } setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), speSize); } }
當前狀況下打印出的log以下:
widthMeasureSpecD/MyView ( 3506): java.lang.Throwable D/MyView ( 3506): at com.sean.myview.MyView.onMeasure(MyView.java:18) D/MyView ( 3506): at android.view.View.measure(View.java:15775) D/MyView ( 3506): at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4942) D/MyView ( 3506): at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1411) D/MyView ( 3506): at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1059) D/MyView ( 3506): at android.widget.LinearLayout.onMeasure(LinearLayout.java:590) D/MyView ( 3506): at android.view.View.measure(View.java:15775) D/MyView ( 3506): at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4942) D/MyView ( 3506): at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1411) D/MyView ( 3506): at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1059) D/MyView ( 3506): at android.widget.LinearLayout.onMeasure(LinearLayout.java:590) D/MyView ( 3506): at android.view.View.measure(View.java:15775) D/MyView ( 3506): at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4942) D/MyView ( 3506): at android.widget.FrameLayout.onMeasure(FrameLayout.java:310) D/MyView ( 3506): at android.view.View.measure(View.java:15775) D/MyView ( 3506): at android.widget.LinearLayout.measureVertical(LinearLayout.java:850) D/MyView ( 3506): at android.widget.LinearLayout.onMeasure(LinearLayout.java:588) D/MyView ( 3506): at android.view.View.measure(View.java:15775) D/MyView ( 3506): at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:4942) D/MyView ( 3506): at android.widget.FrameLayout.onMeasure(FrameLayout.java:310) D/MyView ( 3506): at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2193) D/MyView ( 3506): at android.view.View.measure(View.java:15775) D/MyView ( 3506): at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2212) D/MyView ( 3506): at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1291) D/MyView ( 3506): at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1486) D/MyView ( 3506): at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1181) D/MyView ( 3506): at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:4942) D/MyView ( 3506): at android.view.Choreographer$CallbackRecord.run(Choreographer.java:776) D/MyView ( 3506): at android.view.Choreographer.doCallbacks(Choreographer.java:579) D/MyView ( 3506): at android.view.Choreographer.doFrame(Choreographer.java:548) D/MyView ( 3506): at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:762) D/MyView ( 3506): at android.os.Handler.handleCallback(Handler.java:800) D/MyView ( 3506): at android.os.Handler.dispatchMessage(Handler.java:100) D/MyView ( 3506): at android.os.Looper.loop(Looper.java:194) D/MyView ( 3506): at android.app.ActivityThread.main(ActivityThread.java:5391) D/MyView ( 3506): at java.lang.reflect.Method.invokeNative(Native Method) D/MyView ( 3506): at java.lang.reflect.Method.invoke(Method.java:525) D/MyView ( 3506): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:833) D/MyView ( 3506): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:600) D/MyView ( 3506): at dalvik.system.NativeStart.main(Native Method) D/MyView ( 3506): ---speSize = 940 D/MyView ( 3506): ---speMode = -2147483648 D/MyView ( 3506): ---AT_MOST---
查看onMeasure()的調用堆棧,而後查看源碼,能夠知道heightMeasureSpec和widthMeasureSpec的值在ViewRootImpl.java中初始化,而初始化又參考了view和LinearLayout中的屬性參數的設置最終獲得了heightMeasureSpec和widthMeasureSpec的值。
而specMode怎麼對應呢?我先給出設置對應值的地方,
如下代碼皆出自ViewRootImpl.java
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);//這裏是賦值的代碼
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_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; }
從這裏咱們基本上能夠看出了MATCH_PARENT對應於EXACTLY,WRAP_CONTENT對應於AT_MOST,其餘狀況也對應於EXACTLY,它和MATCH_PARENT的區別在於size值不同。如今咱們須要知道這個rootDimension即lp.height對應於什麼。
private void performTraversals() { // cache mView since it is used so much below... ... WindowManager.LayoutParams lp = mWindowAttributes;
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; mViewLayoutDirectionInitial = mView.getRawLayoutDirection(); mFallbackEventHandler.setView(view); mWindowAttributes.copyFrom(attrs);
這個View就是ViewGroup中的各個子類視圖,這裏咱們用實驗說明,更改MyView中的android:layout_height的值爲"match_parent"。log中輸出變爲了
D/MyView ( 4249): ---speSize = 940
D/MyView ( 4249): ---speMode = 1073741824
D/MyView ( 4249): ---EXACTLY---
而更改LinearLayout中的這個值對這裏的specMode是沒有影響的。
下面咱們再來經過實驗來驗證specSize的值由那些屬性決定:
<LinearLayout android:layout_width="200dp" android:layout_height="300dp" android:paddingTop="20dp" android:layout_marginTop="30dp" android:background="@android:color/darker_gray" > <com.sean.myview.MyView android:layout_width="match_parent" android:layout_height="match_parent" android:paddingTop="10dp" android:layout_marginTop="15dp" android:background="@android:color/holo_red_light" /> </LinearLayout>
D/MyView ( 4959): ---speSize = 530
D/MyView ( 4959): ---speMode = 1073741824
D/MyView ( 4959): ---EXACTLY---
先說明一點,xml中用的單位是dp,log中獲得的單位是px,我所使用的機子屏幕密度爲2.0,只須要進行簡單的換算便可px = 2.0 * dp
咱們能夠經過控制變量法,逐一改變代碼中的LinearLayout和MyView中的相關屬性值,看看是哪些影響了specSize,這裏我直接給出答案:
530 = 300 * 2.0 - 20 * 2.0 - 15 * 2.0
影響specSize height的因素爲:父視圖的layout_height和paddingTop以及自身的layout_marginTop。可是咱們不要忘記有weight時的影響。