Android自定义View-onMeasure介绍
来源:互联网 发布:黑马程序员薪资 编辑:程序博客网 时间:2024/05/21 01:31
推荐两篇博客:
- Android View系统解析(下) - 任玉刚 - CSDN博客
- ANDROID自定义视图——onMeasure,MeasureSpec源码 流程 思路详解 - 大苞米的专栏 - CSDN博客
重写onMeasure的作用
实现特定的测量方式,比如实现一个时钟的自定义View要求宽高比例1:1、像TextView一样根据内容自适应。
onMeasure(int, int)介绍
onMeasure(int, int):测量View及其内容以确定测量宽度和测量高度。当重写此方法时,必须调用setMeasuredDimension(int, int)来存储该View的测量宽度和测量高度,否则将触发一个IllegalStateException,或者调用超类的onMeasure(int,int)(超类做了默认的测量方法)。
如果重写了该方法,子类有责任确保测量的宽度和高度至少是View的最小宽度和高度(getSuggestedMinimumWidth()和getSuggestedMinimumHeight())。
参数:
- widthMeasureSpec:父布局对子View宽度的要求
- heightMeasureSpec:父布局对子View高度的要求
这两个参数都是由MeasureSpec类打包而成,里面包含测量模式和参考尺寸。widthMeasureSpec和heightMeasureSpec是由父布局确定的(除了DecorView )。
MeasureSpec介绍
一个测量规格封装了从父布局传递给子View的布局要求。每个测量规格代表对宽度和高度的要求,由大小和模式组成。有三种可能的模式:
- UNSPECIFIED:父布局没有对子View施加任何约束,它可以是它想要的大小
- EXACTLY:父布局已经确定了子View的确切尺寸,不管子View想要多大它都会得到这些界限
- AT_MOST:子View可以是它想要的大小一直到指定大小之间
MeasureSpec的实现是为了减少对象分配,这个类提供了打包和解包的方法:
- 打包:int makeMeasureSpec(int size, int mode)
- 获取测量模式:int getMode(int measureSpec)
- 获取参考大小:int getSize(int measureSpec)
步骤
- 计算当前View的测量宽度和测量高度(注意确保测量的宽度和高度至少是View的最小宽度和高度getSuggestedMinimumWidth()和getSuggestedMinimumHeight())
- 确定所有子View的widthMeasureSpec和heightMeasureSpec,并测量所有子View(自定义ViewGroup才需要这一步)
- 调用setMeasuredDimension(int, int)来存储当前View的测量宽度和测量高度
注:源码是23版本的
默认测量方式
View的onMeasure(int,int)代码
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
getDefaultSize(int,int)代码:返回默认大小(参考尺寸),如果测量规格没有施加约束(UNSPECIFIED模式),则使用所提供的尺寸(子View建议的最小尺寸)。
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; }
getSuggestedMinimumWidth():返回建议的最小宽度
protected int getSuggestedMinimumWidth() { return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth()); }
getSuggestedMinimumHeight():返回建议的最小高度
protected int getSuggestedMinimumHeight() { return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight()); }
getSuggestedMinimumWidth(),这个方法是从mBackground.getMinimumWidth()和mMinWidth中取最大的值返回,
mBackground是setBackgroundDrawable(Drawable background)设置的背景,
mMinWidth是setMinimumWidth(int minWidth)设置的最小宽度。
getSuggestedMinimumHeight()类似。
ViewGroup 提供的测量子View的方法
measureChildren(int, int) 代码:遍历所有子View,并调用measureChild(View, int, int)测量单个子View
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 = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
measureChild(View, int, int)代码:调用getChildMeasureSpec(int spec, int padding, int childDimension)确定子View的widthMeasureSpec、heightMeasureSpec,并调用子View的measure(int,int)进行测量。
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); }
getChildMeasureSpec(int, int, int)代码:ViewGroup提供确定子View的widthMeasureSpec、heightMeasureSpec的方法(根据父布局对当前ViewGroup的约束、当前ViewGroup已用空间和子View的LayoutParams),但布局不一定或不单单调用这个方法来确定子View的widthMeasureSpec、heightMeasureSpec。
/** @param spec 父布局的measureSpec* @param padding 当前布局已用空间(当前布局的padding、子View的margin)* @param childDimension 子View想要的大小* @return 子View的measureSpec*/public static int getChildMeasureSpec(int spec, int padding, int childDimension) { int specMode = MeasureSpec.getMode(spec); int specSize = MeasureSpec.getSize(spec); int size = Math.max(0, specSize - padding);//当前ViewGroup可用于布局的空间 int resultSize = 0; int resultMode = 0; switch (specMode) { // 父布局的测量模式是EXACTLY case MeasureSpec.EXACTLY: if (childDimension >= 0) {// 子View提供了明确的尺寸 // 子View得到明确的尺寸 resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) {// 子View想铺满父布局 // 子View得到父布局的尺寸 resultSize = size; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.WRAP_CONTENT) {// 子View想要可以包含内容的尺寸 // 子View想自己决定自己的尺寸,这个尺寸不能比父布局的尺寸大 resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // 父布局的测量模式是AT_MOST case MeasureSpec.AT_MOST: if (childDimension >= 0) {// 子View提供了明确的尺寸 // 子View得到明确的尺寸 resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) {// 子View想铺满父布局 // 子View想得到父布局的尺寸,但父布局的尺寸不是固定的 // 强迫子View不能大过父布局 resultSize = size; resultMode = MeasureSpec.AT_MOST; } else if (childDimension == LayoutParams.WRAP_CONTENT) {// 子View想要可以包含内容的尺寸 // 子View想自己决定自己的尺寸,这个尺寸不能比父布局的尺寸大 resultSize = size; resultMode = MeasureSpec.AT_MOST; } break; // 父布局的测量模式是UNSPECIFIED case MeasureSpec.UNSPECIFIED: if (childDimension >= 0) {// 子View提供了明确的尺寸 // 让子View得到明确的尺寸 resultSize = childDimension; resultMode = MeasureSpec.EXACTLY; } else if (childDimension == LayoutParams.MATCH_PARENT) { // 子View想得到父布局的尺寸,查找子View想要多大 resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } else if (childDimension == LayoutParams.WRAP_CONTENT) {// 子View想要可以包含内容的尺寸 // 子View想自己决定自己的尺寸,查找子View想要多大 resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; resultMode = MeasureSpec.UNSPECIFIED; } break; } return MeasureSpec.makeMeasureSpec(resultSize, resultMode); }
View.sUseZeroUnspecifiedMeasureSpec:boolean类型,判断当measureSpec的模式是UNSPECIFIED时,是否总是返回一个大小为0的值。在View的构造函数进行赋值,当targetSdkVersion <23时为true:
public View(Context context) { ... sUseZeroUnspecifiedMeasureSpec = targetSdkVersion < M; ... } }
下面这张图片是getChildMeasureSpec(int spec, int padding, int childDimension)的表格形式,来自Android View系统解析(下) - 任玉刚 - CSDN博客。
还有个跟measureChild(View, int, int)类似的方法void measureChildWithMargins(View,int,int,int,int)
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.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin + heightUsed,// lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}
这个方法和measureChild(View, int, int)
的区别
1、多接收两个参数widthUsed和heightUsed
2、调用getChildMeasureSpec确定子View measureSpec时,
确定宽度约束时:在传的padding参数中多加上widthUsed和子View的leftMargin、rightMargin,
确定宽度约束时:在传的padding参数中多加上heightUsed和子View的topMargin、bottomMargin
- Android自定义View-onMeasure介绍
- Android 自定义view 和 onMeasure方法介绍
- Android 自定义view 和 onMeasure方法介绍
- Android自定义View onMeasure
- android 自定义view-onMeasure
- android 自定义view onMeasure()
- android 自定义view之onMeasure
- Android 自定义View onMeasure理解
- android 自定义view中onMeasure()
- android 自定义view中onMeasure()理解
- android 自定义view中onMeasure()理解
- Android 自定义View 中的OnMeasure的用法
- android 自定义view中onMeasure()理解
- android自定义View中onMeasure的使用
- Android 自定义View 中的OnMeasure的用法
- Android自定义View基础之onMeasure详解
- Android 自定义View总结 —— onMeasure()
- Android 自定义View 中的OnMeasure的用法
- appium问题an X display is required for keycode conversions, consider using Xvfb解决sendKey卡住
- Treap模板(旋转)
- php如何修改SESSION的生存存储时间的实例代码
- 使用maven自动部署功能将war工程自动部署到远程tomcat服务器
- 机器学习2017最新资源
- Android自定义View-onMeasure介绍
- HDU-2017 多校训练赛10-1008-Monkeys
- Integer包装类易错点,面试常考
- jpa之jpql查询
- 链表实现栈和队列的方法
- scrapy-1-初窥scrapy
- ZOJ
- LIS的O(nlogn)算法(二分)
- JavaWeb导出excel