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

一些有必要的废话:做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);
}

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

  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的测量就有非常清晰的的认识,如有遗漏和疑问欢迎大家指出。
源码地址:
https://github.com/HarkBen/RainBowLibrary/blob/master/simpleDemo/src/main/java/com/zsw/testmodel/ui/act/customview/CloneTextView.java

    原文作者:_RANGO
    原文地址: https://www.jianshu.com/p/9808f03c1354
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞