- 这里我们要知道一个View的绘制流程中重要的几个方法,如果你实现过嵌套Drawlayout 或者 ScrollView嵌套ListView 就应该了解过重绘和测量,篇幅过大我这里就不多赘述了,可以参考张兴业博客
三个方法的日志打印顺序为 - onMeasure → onLayout → onDraw
本篇主要记录我对onMeasure()探究学习记录,整个过程稍微有些反复但是理解起来会非常清晰。
这里我们使用源码比较简单的TextView来解析,创建一个CloneTextView 继承 TextView,给出必须构造并且重写 onMeasure()和onDraw()方法。
直接从super.onMeasure()我们进入TextView里查看
会发现这个测量规则类 MeasureSpec
我们来看看他都有什么
//绘制这里仅从源码中知道有三种模式
//TextView中的源码
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
//为了秒懂后面的运算结果,这里将2进制码都写出来
//MODE_MASK 换算后 2进制为 11000000000000000000000000000000
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
//换算后 2进制为 01000000000000000000000000000000
public static final int EXACTLY = 1 << MODE_SHIFT;// int 值 1073741824
//换算后 2进制为 10000000000000000000000000000000
public static final int AT_MOST = 2 << MODE_SHIFT;//int 值-2147483648
//目前我还没看到这个模式有什么实际卵用,希望有实例的同学能能够@我
public static final int UNSPECIFIED = 0 << MODE_SHIFT;//int 值 0
//下面我们看看他怎么计算大小的
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
//高2位被忽略,这样其实就是measureSpec的大小
//再看看如何计算模式的
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
//因为后MODE_MASK30位全是0 ,交集是高2位
//说明父类传入的spec高2位为测量模式,后30位为大小,搞懂了上面的计算方式,下面onMeasure()的获取模式和获取大小就非常容易懂了
我一番百度和翻译注释,暂时是这样理解的
- 模式一 强制模式 EXACTLY - 父类给出实际大小,并且作为默认值直接使用
- 模式二 半开放模式 AT_MOST- 父类给出最大值限定,没有默认值,子类自己计算大小但是不能超过父类限定
- 模式三 完全开放 UNSPECIFIED 大概意思就是没有限制,你想多大就多大 金箍棒模式
下面我看看在TextView的onMeasure方法中他是如何处理的
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthMode == MeasureSpec.EXACTLY) {
// Parent has told us how big to be. So be it.
width = widthSize;
} else {
...
// Check against our minimum width
width = Math.max(width, getSuggestedMinimumWidth());
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(widthSize, width);
}
}
}
上面的代码可以直观的解释为:
- 当模式为EXACTLY,直接将父类传入的大小赋值给自己使用
- 否则如果是AT_MOST模式,则以父类给的大小为上限参考取小的,也就是最大不能超过父类
上面只是我们从源码找到的一些头绪,实际场景还未验证,我们的属性基本都是在xml中设置的,下面我们就来验证从xml属性的设置到获取实际的SpecSize 到计算出测量模式和实际的Size。
我们将源码中 mode的计算 和 size的计算拷贝出来,然后使用一样的运算符来计算 最后对比一下就知道了方法是笨了点,但是理解吃透才是最重要的。
下面我们利用日志打印,直观的打印出前两种模式,和最后我们计算出的实际模式:
private int widSpec;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
widSpec = widthMeasureSpec;
L.printD("CT", "onMeasure");
}
//这里很简单了,就是绘制,大小已在onMeasure确定, 位置已经在onLayout 固定
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
L.printD("CT", "onDraw");
int MODE_SHIFT = 30;
int MODE_MASK = 0x3 << MODE_SHIFT;
int UNSPECIFIED = 0 << MODE_SHIFT;
int EXACTLY = 1 << MODE_SHIFT;
int AT_MOST = 2 << MODE_SHIFT;
L.printD("CT", "AT_MOST=" + AT_MOST);
L.printD("CT", "EXACTLY=" + EXACTLY);
int mode = widSpec & MODE_MASK;
L.printD("Ct", "mode==" + mode);
int size = widSpec & ~MODE_MASK;
L.printD("Ct", "size==" + size);
}
用上面的代码一共验证三个场景
- match_parent
- wrap_content
- 200dip
下面来分析日志信息:
1. match_parent
log-
AT_MOST=-2147483648
EXACTLY=1073741824
mode==1073741824
size==720
模式为 EXACTLY 父类指定大小
2. wrap_content
log-
AT_MOST=-2147483648
EXACTLY=1073741824
mode==-2147483648
size==720
模式为 AT_MOST
- 这里会有疑问咯,specSize还是720 那为什么显示的大小仅仅是包裹文本内容呢,细心的同学会发现上面的代码已经给出了答案 在TextView的onMeasure方法中,当模式为AT_MOST时他是这样处理的
// Check against our minimum width
width = Math.max(width, getSuggestedMinimumWidth());
if (widthMode == MeasureSpec.AT_MOST) {
width = Math.min(widthSize, width);
}
这里的widthSize 是父类传下来的值,再计算出文本包裹需要的大小,两者取最小,一般都是文本包裹值小,所以显示出来是刚好包裹文本。
200dip
log-
AT_MOST=-2147483648
EXACTLY=1073741824
mode==1073741824
size==400
模式为 EXACTLY 大小
- 根据实际验证我们可以得到如下结论
xml-attribute value = match_parent → MODE = EXCATLY
子类期望与父类一样大小,父类传入允许子类使用的最大值,并且默认使用这个值
xml-attribute value = wrap_content → MODE = AT_MOST
父类传入允许的最大值,子类需要为自己重新计算大小并且参考父类给出的上限
xml-attribute value = 200dp → MODE = EXCATLY
父类测量出子类的参数,再传给子类直接使用
ps:当然了这二种模式我们都可以再onMeasure方法中取动态更改绘制模式和大小
上面未验证View(页面最小单元) MeasureSpec.UNSPECIFIED。因为这里验证没有使用ViewGroup。也就疏忽了这个属性
感谢简书取名好难的指正
MeasureSpec.UNSPECIFIED 是由特殊父布局测量模式决定的。
通俗讲,父布局跟子View同时使用 MATCH_PARENT 或 WRAP_CONTENT时
则为MeasureSpec.UNSPECIFIED
逻辑在ViewGroup子类 例如ListView
注释:根据父布局测量模式确认子View测量模式
onMeasure()>measureScrapChild()>ViewGroup.getChildMeasureSpec()
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
···
// Parent asked to see how big we want to be
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
···
}
END