自定义View-(1)先搞懂测量的所有细节

  • 这里我们要知道一个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);
}

用上面的代码一共验证三个场景

  1. match_parent
  2. wrap_content
  3. 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

参考:
http://tryenough.com/android-MeasureSpec

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容