Android开发中偶尔会用到自定义View,一般情况下,自定义View都需要继承View类的onMeasure方法,那么,为什么要继承onMeasure()函数呢?什么情况下要继承onMeasure()?系统默认的onMeasure()函数行为是怎样的 ?本文就探究探究这些问题。这篇文章获取可以加深多自定义view的理解。
首先,我们写一个自定义View,直接调用系统默认的onMeasure函数,看看会是怎样的现象:
package com.tuke.customviewonmeasure; import android.content.Context; import android.util.AttributeSet; import android.view.View; /** * 作者:tuke on 2017/6/15 11:05 * 邮箱:2297535832@qq.com */ public class CustomViewOnMeasure extends View { public CustomViewOnMeasure(Context context) { super(context); } public CustomViewOnMeasure(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } }
<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.tuke.customviewonmeasure.CustomViewOnMeasure android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="50dp" android:background="#000000"/> </LinearLayout>1. 父控件使用match_parent,CustomView使用match_parent。
这里加了10dp的margin并且把View的背景设置为了黑色,是为了方便辨别我们的CustomView,效果如下:
LinearLayout.LayoutParams继承了ViewGroup.MarginLayoutParams,并且增加了weight,gravity两个属性,而RelativeLayout.LayoutParams也继承了ViewGroup.MarginLayoutParams,并且增加了对齐属性,如left_to,right_to,above,below,align_baseline,align_left,align_top,align_right等等
所以在下一篇自定义ViewGroup中,如果要使得margin属性有效,必须创建一个静态内部类并继承ViewGroup.MarginLayoutParams,这个下次再讲。
2. 父控件使用match_parent,CustomView使用wrap_content
把layout文件中,CustomViewOnMeasure的layout_width/layout_height替换为wrap_content,你会发现,结果依然是充满父控件。
3. 父控件使用match_parent,CustomView使用固定的值
把layout文件中,CustomView的layout_width/layout_height替换为200dp,你会发现,CustomView的显示结果为200dpx200dp,如图所示:
5 结论
如果自定义的CustomView采用默认的onMeasure函数,行为如下:
(1) CustomView设置为 match_parent 或者 wrap_content 没有任何区别,其显示大小由父控件决定,它会填充满整个父控件的空间。
(2) CustomView设置为固定的值,则其显示大小为该设定的值。
如果你的自定义控件的大小计算就是跟系统默认的行为一致的话,那么你就不需要重写onMeasure函数了。
那么我们就要看看这个默认的系统onMeasure函数是个什么样子了。View中onMeasure方法已经默认为我们的控件测量了宽高,我们看看它做了什么工作:
protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension( getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); } /** * 为宽度获取一个建议最小值 */ protected int getSuggestedMinimumWidth () { return (mBackground == null) ? mMinWidth : max(mMinWidth , mBackground.getMinimumWidth()); } /** * 获取默认的宽高值 */ public static int getDefaultSize (int size, int measureSpec) { int result = size; 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; }onMeasure方法的作用时测量空间的大小,什么时候需要测量控件的大小呢?创建一个View(执行构造方法)的时候不需要测量控件的大小,只有将这个view放入一个容器(父控件)中的时候才需要测量,而这个测量方法就是父控件唤起调用的。当控件的父控件要放置该控件的时候,父控件会调用子控件的onMeasure方法询问子控件:“你有多大的尺寸,我要给你多大的地方才能容纳你?”,然后传入两个参数(widthMeasureSpec和heightMeasureSpec),这两个参数就是父控件告诉子控件可获得的空间以及关于这个空间的约束条件(好比我在思考需要多大的碗盛菜的时候我要看一下碗柜里最大的碗有多大,菜的分量不能超过这个容积,这就是碗对菜的约束),子控件拿着这些条件就能正确的测量自身的宽高了。
那么现在就需要认识widthMeasureSpec和heightMeasureSpec参数,MeasureSpec类,自定义View的三种测量模式。
widthMeasureSpec和heightMeasureSpec参数其实就是此自定义View的父控件(此处是LinearLayout)根据layout文件中自定义控件的layout_height和layout_width设置的值测量的宽和高,并加入模式通过MeasureSpec类计算得出的widthMeasureSpec和heightMeasureSpec参数,传入我们重写的OnMeasure中,其实就是父控件已经知道了此自定义view的宽和高,传过来供开发者参考修改。
widthMeasureSpec和heightMeasureSpec 并不是真正的宽高。是一个32位int值。高2位 代表SpecMode,低30为代表SpecSize 。
了解了这两个参数的来源,还要知道这两个值的作用。我们只取heightMeasureSpec作说明。这个值由高32位和低16位组成,高32位保存的值叫specMode,可以通过如代码中所示的MeasureSpec.getMode()获取;低16位为specSize,同样可以由MeasureSpec.getSize()获取。那么specMode和specSize的作用有是什么呢?要想知道这一点,我们需要知道代码中的最后一行,所有的View的onMeasure()的最后一行都会调用setMeasureDimension()函数的作用——这个函数调用中传进去的值是View最终的视图大小。也就是说onMeasure()中之前所作的所有工作都是为了最后这一句话服务的。
MeasureSpec类
上面说到MeasureSpec约束是由父控件传递给子控件的,这个类里面到底封装了什么东西?我们看一看源码:
public static class MeasureSpec { private static final int MODE_SHIFT = 30; private static final int MODE_MASK = 0x3 << MODE_SHIFT; /** * 父控件不强加任何约束给子控件,它可以是它想要任何大小 */ public static final int UNSPECIFIED = 0 << MODE_SHIFT; /** * 父控件已为子控件确定了一个确切的大小,孩子将被给予这些界限,不管子控件自己希望的是多大 */ public static final int EXACTLY = 1 << MODE_SHIFT; /** * 父控件会给子控件尽可能大的尺寸 */ public static final int AT_MOST = 2 << MODE_SHIFT; /** * 根据所提供的大小和模式创建一个测量规范 */ public static int makeMeasureSpec(int size, int mode) { if (sUseBrokenMakeMeasureSpec) { return size + mode; } else { return (size & ~MODE_MASK) | (mode & MODE_MASK); } } /** * 从所提供的测量规范中提取模式 */ public static int getMode(int measureSpec) { return (measureSpec & MODE_MASK); } /** * 从所提供的测量规范中提取尺寸 */ public static int getSize(int measureSpec) { return (measureSpec & ~MODE_MASK); } ... }从源码中我们知道,MeasureSpec其实就是尺寸和模式通过各种位运算计算出的一个整型值,它提供了三种模式。
1,EXACTLY:表示设置了自定义的view精确的值,一般当自定义的view设置其宽、高为精确值、match_parent时,其父控件为其计算出的widthMeasureSpec和heightMeasureSpec参数中的Mode为EXACTLY,specSize是父控件测量所得的值,就是精确值或者match_parent,父控件都能精确测量;
2,AT_MOST:表示自定义的view被限制在一个最大值内,一般当自定义的view设置其宽、高为wrap_content时,其父控件为其计算出的widthMeasureSpec和heightMeasureSpec参数中的Mode为为AT_MOST,specSize不确定,需要在重写OnMeasure中为其设置最大值;
3,UNSPECIFIED:自定义view想要多大就多大,一般出现在AadapterView的item的heightMode中、ScrollView的childView的heightMode中;此种模式比较少见。
待续。。。