Android自定义View(三、深入解析控件测量onMeasure).doc_第1页
Android自定义View(三、深入解析控件测量onMeasure).doc_第2页
Android自定义View(三、深入解析控件测量onMeasure).doc_第3页
Android自定义View(三、深入解析控件测量onMeasure).doc_第4页
Android自定义View(三、深入解析控件测量onMeasure).doc_第5页
已阅读5页,还剩4页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Android自定义View(三、深入解析控件测量onMeasure)1. onMeasure什么时候会被调用onMeasure方法的作用时测量空间的大小,什么时候需要测量控件的大小呢?我们举个栗子,做饭的时候我们炒一碗菜,炒菜的过程我们并不要求知道这道菜有多少分量,只有在菜做熟了我们要拿个碗盛放的时候,我们才需要掂量拿多大的碗盛放,这时候我们就要对菜的分量进行估测。 而我们的控件也正是如此,创建一个View(执行构造方法)的时候不需要测量控件的大小,只有将这个view放入一个容器(父控件)中的时候才需要测量,而这个测量方法就是父控件唤起调用的。当控件的父控件要放置该控件的时候,父控件会调用子控件的onMeasure方法询问子控件:“你有多大的尺寸,我要给你多大的地方才能容纳你?”,然后传入两个参数(widthMeasureSpec和heightMeasureSpec),这两个参数就是父控件告诉子控件可获得的空间以及关于这个空间的约束条件(好比我在思考需要多大的碗盛菜的时候我要看一下碗柜里最大的碗有多大,菜的分量不能超过这个容积,这就是碗对菜的约束),子控件拿着这些条件就能正确的测量自身的宽高了。 2. onMeasure方法执行流程上面说到onMeasure方法是由父控件调用的,所有父控件都是ViewGroup的子类,ViewGroup是一个抽象类,它里面有一个抽象方法onLayout,这个方法的作用就是摆放它所有的子控件(安排位置),因为是抽象类,不能直接new对象,所以我们在布局文件中可以使用View但是不能直接使用 ViewGroup。在给子控件确定位置之前,必须要获取到子控件的大小(只有确定了子控件的大小才能正确的确定上下左右四个点的坐标),而ViewGroup并没有重写View的onMeasure方法,也就是说抽象类ViewGroup没有为子控件测量大小的能力,它只能测量自己的大小。但是既然ViewGroup是一个能容纳子控件的容器,系统当然也考虑到测量子控件的问题,所以ViewGroup提供了三个测量子控件相关的方法(measuireChildrenmeasuireChildmeasureChildWithMargins),只是在ViewGroup中没有调用它们,所以它本身不具备为子控件测量大小的能力,但是他有这个潜力哦。为什么都有测量子控件的方法了而ViewGroup中不直接重写onMeasure方法,然后在onMeasure中调用呢?因为不同的容器摆放子控件的方式不同,比如RelativeLayout,LinearLayout这两个ViewGroup的子类,它们摆放子控件的方式不同,有的是线性摆放,而有的是叠加摆放,这就导致测量子控件的方式会有所差别,所以ViewGroup就干脆不直接测量子控件,他的子类要测量子控件就根据自己的布局特性重写onMeasure方法去测量。这么看来ViewGroup提供的三个测量子控件的方法岂不是没有作用?答案是NO,既然提供了就肯定有作用,这三个方法只是按照一种通用的方式去测量子控件,很多ViewGruop的子类测量子控件的时候就使用了ViewGroup的measureChildxxx系列方法;还有一个作用就是为我们自定义ViewGroup提供方便咯,自定义ViewGroup我会在以后的博客中专门探讨,这里就不大费篇章了。测量的时候父控件的onMeasure方法会遍历他所有的子控件,挨个调用子控件的measure方法,measure方法会调用onMeasure,然后会调用setMeasureDimension方法保存测量的大小,一次遍历下来,第一个子控件以及这个子控件中的所有子控件都会完成测量工作;然后开始测量第二个子控件;最后父控件所有的子控件都完成测量以后会调用setMeasureDimension方法保存自己的测量大小。值得注意的是,这个过程不只执行一次,也就是说有可能重复执行,因为有的时候,一轮测量下来,父控件发现某一个子控件的尺寸不符合要求,就会重新测量一遍。举个栗子,看下图:3. 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其实就是尺寸和模式通过各种位运算计算出的一个整型值,它提供了三种模式,还有三个方法(合成约束、分离模式、分离尺寸)。4. 从ViewGroup的onMeasure到View的onMeasure. ViewGroup中三个测量子控件的方法:通过上面的介绍,我们知道,如果要自定义ViewGroup就必须重写onMeasure方法,在这里测量子控件的尺寸。子控件的尺寸怎么测量呢?ViewGroup中提供了三个关于测量子控件的方法: /* *遍历ViewGroup中所有的子控件,调用measuireChild测量宽高 */ protected void measureChildren (int widthMeasureSpec, int heightMeasureSpec) final int size = mChildrenCount; final View children = mChildren; for (int i = 0; i size; +i) final View child = childreni; if (child.mViewFlags & VISIBILITY_MASK) != GONE) /测量某一个子控件宽高 measureChild(child, widthMeasureSpec, heightMeasureSpec); /* 测量某一个child的宽高*/protected void measureChild (View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) final LayoutParams lp = child.getLayoutParams(); /获取子控件的宽高约束规则 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp. width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp. height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec);/* 测量某一个child的宽高,考虑margin值*/protected void measureChildWithMargins (View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); /获取子控件的宽高约束规则 final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight + lp. leftMargin + lp.rightMargin + widthUsed, lp. th); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp. topMargin + lp.bottomMargin + heightUsed, lp. height); /测量子控件 child.measure(childWidthMeasureSpec, childHeightMeasureSpec);这三个方法分别做了那些工作大家应该比较清楚了吧?measureChildren 就是遍历所有子控件挨个测量,最终测量子控件的方法就是measureChild 和measureChildWithMargins 了,我们先了解几个知识点:measureChildWithMargins跟measureChild的区别就是父控件支不支持margin属性支不支持margin属性对子控件的测量是有影响的,比如我们的屏幕是1080x1920的,子控件的宽度为填充父窗体,如果使用了marginLeft并设置值为100; 在测量子控件的时候,如果用measureChild,计算的宽度是1080,而如果是使用measureChildWithMargins,计算的宽度是1080-100 = 980。怎样让ViewGroup支持margin属性?ViewGroup中有两个内部类ViewGroup.LayoutParams和ViewGroup. MarginLayoutParams,MarginLayoutParams继承自LayoutParams ,这两个内部类就是VIewGroup的布局参数类,比如我们在LinearLayout等布局中使用的layout_widthlayout_hight等以“layout_ ”开头的属性都是布局属性。在View中有一个mLayoutParams的变量用来保存这个View的所有布局属性。LayoutParams和MarginLayoutParams 的关系: LayoutParams 中定义了两个属性(现在知道我们用的layout_widthlayout_hight的来头了吧?): MarginLayoutParams 是LayoutParams的子类,它当然也延续了layout_widthlayout_hight 属性,但是它扩充了其他属性: 是不是对布局属性有了一个全新的认识?原来我们使用的margin属性是这么来的。为什么LayoutParams 类要定义在ViewGroup中? 大家都知道ViewGroup是所有容器的基类,一个控件需要被包裹在一个容器中,这个容器必须提供一种规则控制子控件的摆放,比如你的宽高是多少,距离那个位置多远等。所以ViewGroup有义务提供一个布局属性类,用于控制子控件的布局属性。为什么View中会有一个mLayoutParams 变量? 我们在之前学习自定义控件的时候学过自定义属性,我们在构造方法中,初始化布局文件中的属性值,我们姑且把属性分为两种。一种是本View的绘制属性,比如TextView的文本、文字颜色、背景等,这些属性是跟View的绘制相关的。另一种就是以“layout_”打头的叫做布局属性,这些属性是父控件对子控件的大小及位置的一些描述属性,这些属性在父控件摆放它的时候会使用到,所以先保存起来,而这些属性都是ViewGroup.LayoutParams定义的,所以用一个变量保存着。怎样让ViewGroup支持margin属性? 这一部分知识点我们在下一篇博客自定义ViewGroup中再去讲解 . getChildMeasureSpec方法measureChildWithMargins跟measureChild 都调用了这个方法,其作用就是通过父控件的宽高约束规则和父控件加在子控件上的宽高布局参数生成一个子控件的约束。我们知道View的onMeasure方法需要两个参数(父控件对View的宽高约束),这个宽高约束就是通过这个方法生成的。有人会问为什么不直接拿着子控件的宽高参数去测量子控件呢?打个比方,父控件的宽高约束为wrap_content,而子控件为match_perent,是不是很有意思,父控件说我的宽高就是包裹我的子控件,我的子控件多大我就多大,而子控件说我的宽高填充父窗体,父控件多大我就多大。最后该怎么确定大小呢?所以我们需要为子控件重新生成一个新的约束规则。只要记住,子控件的宽高约束规则是父控件调用getChildMeasureSpec方法生成。getChildMeasure方法代码不多,也比较简单,就是几个switch将各种情况考虑后生成一个子控件的新的宽高约束,这个方法的结果能够用一个表来概括:进行了上面的步骤,接下来就是在measureChildWithMarginsh或者measureChild中 调用子控件的measure方法测量子控件的尺寸了。. View的onMeasureView中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;从源码我们了解到:如果View的宽高模式为未指定,他的宽高将设置为android:minWidth/Height =”“值与背景宽

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论