转自 csdn大神 aige
默认情况下onMeasure方法中,只是简单地将签名列表中的两个int型参数,回传给父类的onMeasure方法,然后由父类的方法去计算出最终的测量值。这两个参数是由view的父容器,代码中也就是我们的LinearLayout传递进来的
某个布局的上下级关系如下
界面的真正根视图应该是DecorView,但是问题是FrameLayout:onMeasure方法中的widthMeasureSpec和heightMeasureSpec又是从何而来呢?追溯上去我们又回到了View
View在设计过程中就注定了其只会对显示数据进行处理比如我们的测量布局和绘制还有动画等等,在Android中这一功能由ViewRootImpl承担,其负责的东西很多,比如我们窗口的显示、用户的输入输出当然还有关于处理我们绘制流程的方法:
private void performTraversals() { if (!mStopped) { int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); } }可以看到在performTraversals方法中通过getRootMeasureSpec获取原始的测量规格并将其作为参数传递给performMeasure方法处理,这里我们重点来看getRootMeasureSpec方法是如何确定测量规格的,首先我们要知道mWidth, lp.width和mHeight, lp.height这两组参数的意义,其中lp.width和lp.height均为MATCH_PARENT,其在mWindowAttributes(WindowManager.LayoutParams类型)将值赋予给lp时就已被确定,mWidth和mHeight表示当前窗口的大小,其值由performTraversals中一系列逻辑计算确定,这里跳过,而在getRootMeasureSpec中作了如下判断.........
private static int getRootMeasureSpec(int windowSize, int rootDimension) { int measureSpec; switch (rootDimension) { case ViewGroup.LayoutParams.MATCH_PARENT: // Window不能调整其大小,强制使根视图大小与Window一致 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY); break; case ViewGroup.LayoutParams.WRAP_CONTENT: // Window可以调整其大小,为根视图设置一个最大值 measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST); break; default: // Window想要一个确定的尺寸,强制将根视图的尺寸作为其尺寸 measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY); break; } return measureSpec; }
也就是说不管如何,我们的根视图大小必定都是全屏的……
至此,我们算是真正接触到根视图的测量规格,尔后这个规格会被由上至下传递下去,并由当前view与其父容器共同作用决定最终的测量大小
可以看到,View对控件的测量是在onMeasure方法中进行的,也就是文章开头我们在自定义View中重写的onMeasure方法,但是我们并没有对其做任何的处理,也就是说保持了其在父类View中的默认实现,其默认实现也很简单:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
public static int getDefaultSize(int size, int measureSpec) { // 将我们获得的最小值赋给result int result = size; // 从measureSpec中解算出测量规格的模式和尺寸 int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); /* * 根据测量规格模式确定最终的测量尺寸 */ switch (specMode) { case MeasureSpec.UNSPECIFIED: result = size; break; case MeasureSpec.AT_MOST: case MeasureSpec.EXACTLY: result = specSize; break; } return result; }
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FFFFFFFF" android:orientation="vertical" > <com.aigestudio.customviewdemo.views.IconView android:id="@+id/main_pv" android:layout_width="fill_parent" android:layout_height="fill_parent" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ssdfseStudio" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="ssdfseStudio" /> </LinearLayout>
package com.aigestudio.customviewdemo.activities; import android.app.Activity; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import com.aigestudio.customviewdemo.R; import com.aigestudio.customviewdemo.views.IconView; /** * 主界面 */ public class MainActivity extends Activity { private IconView mImgView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mImgView = (IconView) findViewById(R.id.main_pv); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.lovestory); mImgView.setBitmap(bitmap); } }
package com.aigestudio.customviewdemo.views; import android.app.Activity; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Typeface; import android.text.TextPaint; import android.util.AttributeSet; import android.view.View; import com.aigestudio.customviewdemo.R; import com.aigestudio.customviewdemo.utils.MeasureUtil; /** * * @author AigeStudio {@link http://blog.csdn.net/aigestudio} * @since 2015/1/13 * */ public class IconView extends View { private Bitmap mBitmap;// 位图对象 public IconView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onDraw(Canvas canvas) { // 绘制位图 canvas.drawBitmap(mBitmap, 0, 0, null); } /** * 设置位图 * * @param bitmap * 位图对象 */ public void setBitmap(Bitmap bitmap) { this.mBitmap = bitmap; } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // TODO Auto-generated method stub super.onMeasure(widthMeasureSpec, heightMeasureSpec); } }
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FFFFFFFF" android:orientation="vertical" > <com.aigestudio.customviewdemo.views.ImgView android:id="@+id/main_pv" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <!-- ……省略一些代码…… --> </LinearLayout>修改布局为包裹内容,结果效果也一样
MeasureSpec类中的三个Mode常量值的意义,其中UNSPECIFIED表示未指定,爹不会对儿子作任何的束缚,儿子想要多大都可以;EXACTLY表示完全的,意为儿子多大爹心里有数,爹早已算好了;AT_MOST表示至多,爹已经为儿子设置好了一个最大限制,儿子你不能比这个值大,不能再多了
MeasureSpec.EXACTLY是精确尺寸,当我们将控件的layout_width或layout_height指定为具体数值时如andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。
MeasureSpec.AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { // 声明一个临时变量来存储计算出的测量值 int resultWidth = 0; // 获取宽度测量规格中的mode int modeWidth = MeasureSpec.getMode(widthMeasureSpec); // 获取宽度测量规格中的size int sizeWidth = MeasureSpec.getSize(widthMeasureSpec); /* * 如果爹心里有数 */ if (modeWidth == MeasureSpec.EXACTLY) { // 那么儿子也不要让爹难做就取爹给的大小吧 resultWidth = sizeWidth; } /* * 如果爹心里没数 */ else { // 那么儿子可要自己看看自己需要多大了 resultWidth = mBitmap.getWidth(); /* * 如果爹给儿子的是一个限制值 */ if (modeWidth == MeasureSpec.AT_MOST) { // 那么儿子自己的需求就要跟爹的限制比比看谁小要谁 resultWidth = Math.min(resultWidth, sizeWidth); } } int resultHeight = 0; int modeHeight = MeasureSpec.getMode(heightMeasureSpec); int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); if (modeHeight == MeasureSpec.EXACTLY) { resultHeight = sizeHeight; } else { resultHeight = mBitmap.getHeight(); if (modeHeight == MeasureSpec.AT_MOST) { resultHeight = Math.min(resultHeight, sizeHeight); } } // 设置测量尺寸 setMeasuredDimension(resultWidth, resultHeight); }
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FFFFFFFF" android:orientation="vertical" > <com.example.customview.CustomImage android:id="@+id/main_pv" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="AigeStudio" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="AigeStudio" /> </LinearLayout>
-----