一些有必要的废话:做Android两年, 作为一个不折不扣的伸手党,从第一个项目开始到现在,拿到UI 切图看看特别的交互设计,直接去github 搜,大部分都能搜到,实在不行就下个差不多的源码稍微改改。对于API里的源码和实现细节几乎没有怎么研究过。我相信大部分前两年出家做Android的同学和我一样,一头扎进项目进度里,一切以快速实现功能为第一前提,当然就工作上来说这没什么不对,但是弊端显而易见,无法按自己想法,自由 的实现、炫酷、帅气潮流的自定义控件,总是感觉身体被掏空,深知根基不稳的利害。在一顿挣扎过后,我毅然决定从头认认真真搞一遍,虽然这会花费我大部分休息时间,但是无论是对于以后的工作还是对Android单纯的执着,我相信回报都是不小的。
ps: 自定义View我想以仔细到苛刻的步骤来学习,不想在以后实现时有隐患
希望大猿老猿们能为我指点遗漏和错误
- 这里我们要知道一个View的绘制流程中重要的几个方法,如果你实现过嵌套Drawlayout 或者 ScrollView嵌套ListView 就应该了解过重绘和测量不知道的同学自行百度View的绘制流程,篇幅过大我这里就不多赘述了,可以参考张兴业博客 http://blog.csdn.net/xyz_lmn/article/details/20385049
三个方法的日志打印顺序为 – 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的测量就有非常清晰的的认识,如有遗漏和疑问欢迎大家指出。
源码地址:
https://github.com/HarkBen/RainBowLibrary/blob/master/simpleDemo/src/main/java/com/zsw/testmodel/ui/act/customview/CloneTextView.java