2018-08-06
在Android自定义开发ViewGroup时 总是避免不了对onMeasure方法的重写
那对这个方法应该如何理解?如何重写?有什么作用?等疑问接踵而来 这篇文章就来简洁地说明下这两个方法的使用
onMeasure此方法主要有两个使用目的
1.为本ViewGroup中所有的子view调用"它们自己的测量方法":View.measure(int,int)
2.在所有的子view的测量方法调用完成后 理论上所有子view都可以使用getMeasureWidth或getMeasureHeight获取测量宽度和高度 这时由此来确定ViewGroup自己的宽度和高度
现在我们通过阐述这两个目的来间接学习onMeasure的使用
1.调用"它们自己的测量方法":View.measure(int,int)
我们要先从子view测量方法开始说起
这个测量方式要情况而定 一般最基本也最通用的做法是使用Android已经封装好了的测量方法:
measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec)
measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed)
这两个方法都可以用于单个view的测量:
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view.getVisibility() != View.GONE) {
measureChildWithMargins(view, widthMeasureSpec, 0, heightMeasureSpec, 0);
}
}
在这段for循环完成后view便有了自己的宽高 可以使用getMeasureWidth或getMeasureHeight获取测量宽度和高度了
注1:
这两个方法有一定的区别 只有measureChildWithMargins()在测量时会将子view的margin属性考虑进去 在margin会影响子view宽高时(如 子view设置宽度match_parent 并设置了marginLeft为10dp 那么最后获取到的view的测量宽度应该比父控件宽度小10dp)会影响其测量宽高 而measureChild不会 所以有时会造成测量不准的问题
注2:measureChildWithMargins()中那两个被赋值为0的两个参数 在android说明中说是为了在平行 or 垂直布局中设置固定间隔而用的 在代码中其实是直接加在了margin和padding值里 其实算是当作另一个margin来用 详细请查看源码
measureChildren(int widthMeasureSpec, int heightMeasureSpec)
方法源码:
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);
}
}
}
没错 这个方法其实就是我们之前写的for循环子view进行测量的那部分代码 android早就给了封装方法 在实际开发中 直接使用这个方法基本就可以解决所有的测量问题
只是它用的是measureChild进行的测量 所以在子view使用了margin的情况下 有可能会影响实际测量值(一般只在子view设置为match_parent时有这种情况 所以也不是很大的问题) 必须在onLayout中考虑到这点
在调用了以上方法后 经过一系列计算 最终view.measure(int width,int height)会被调用 传入的就是view的测量高度和宽度 具体内容可看源码(实际和ViewGroup的onMeasure类似甚至更简单些 明白了onMeasure的用法 就很容易理解了)
2.确定ViewGroup自己的宽度和高度
这个问题要从ViewGroup的测量方法onMeasure(int widthMeasureSpec, int heightMeasureSpec)的参数意义开始说起
这个方法中的两个形参是两个int类型 但是它们不是单纯的数字 没有单纯的算术意义 而是记录了"测量模式"和测量高/宽度的数字(因为int类型共32位 前2位用来表示模式 后30位用来表示宽度或高度 具体可百度了解)
android提供了MeasureSpec工具类 方便我们提取测量模式和宽高:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int width = MeasureSpec.getSize(widthMeasureSpec);//获取宽度
int height = MeasureSpec.getSize(heightMeasureSpec);//获取高度
int modeW = MeasureSpec.getMode(widthMeasureSpec);//获取宽度测量模式
int modeH = MeasureSpec.getMode(heightMeasureSpec);//获取高度测量模式
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
if (view.getVisibility() != View.GONE) {
measureChildWithMargins(view, widthMeasureSpec, 0, heightMeasureSpec, 0);
}
}
switch (modeW) {//判断宽度模式以演示
case MeasureSpec.AT_MOST://当本控件在xml中宽度上写入 wrap_content时为此模式 这时返回的width一般为0
case MeasureSpec.EXACTLY://当本控件在xml中宽度上写入实际值时为此模式 如20dp match_parent(与父布局一样的数值) 这里返回的width也就是xml中写明的数值
case MeasureSpec.UNSPECIFIED://只有在ListView或类似的控件中会出现 表示不关心大小 这时返回的width一般为0
break;
}}
关键点在于如何去理解不同的测量模式给我们对确定ViewGroup宽高的影响
首先说明 确定了ViewGroup的宽高后 应该使用
setMeasuredDimension(int width, int height);
进行赋值 传入的两个Int是确定的数值
如何使用测量模式:
如 模式为 MeasureSpec.EXACTLY:
此时情况最简单 因为此模式下表示开发者使用XML传入了一个固定的值 我们直接设置为ViewGroup的宽高就可以了
setMeasuredDimension(width, height);
如模式为 MeasureSpec.AT_MOST:
此时情况为 XML要求宽高为wrap_content 也就是包裹内容 因为我们已经通过for测量了所有子view的宽高
所以我们可以这样:
int maxHeight = 0;
int maxWidth = 0;
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
maxHeight = view.getMeasuredHeight() > maxHeight ? view.getMeasuredHeight() : maxHeight;
maxWidth = view.getMeasuredWidth() > maxWidth ? view.getMeasuredWidth() : maxWidth;
}
setMeasuredDimension(maxWidth,maxHeight);
也可以这样:
int maxHeight = 0;
int maxWdith = 500;
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
maxHeight += view.getMeasuredHeight();
}
setMeasuredDimension(maxWidth,maxHeight);
上述前者部分是view没有排布规则(如没有任何其它设置的FrameLayout) 我们取所以子view里最宽和最高的值作为我们布局的宽高 很符合"包裹内容"的定义
后者部分是view有垂直排布规则(如LinearLayout) 先假设宽度一定 因为子view垂直排布 所以 我们viewGroup的高度应该是所以子view的高度的和
至于MeasureSpec.UNSPECIFIED模式
一般会出现这个模式的情况是作为listView的item 这时应该开发者根据实际情况处理(一般直接按包裹内容方式处理)
到这里应该就可以看出 测量方法的重要性 不但是设置子view宽高的方法(子view在调用了测量方法后才会有测量宽高 之前会一直为0 没有经过这个方法也就无法完成view的绘制和排布) 同时也是为了确保控件在"自定义排布功能"的情况下确定自身宽高的方法