专栏首页Android知识分享Android自定义View:源码解析通过getWidth() 与 getMeasuredWidth()获取宽高的区别

Android自定义View:源码解析通过getWidth() 与 getMeasuredWidth()获取宽高的区别

前言

  • 在自定义View的过程中,使用getMeasuredWidth() / getMeasuredHeight() 与 getWidth() / getHeight()都能获取View的宽 / 高,但是二者有什么区别呢?
  • 今天,我将深入源码,给大家分析二者之间的区别,希望你们会喜欢。

目录


1. getMeasuredWidth() / getMeasuredHeight()返回值

1.1 结论

返回的值是 View在Measure过程中测量的宽 / 高

1.2 源码分析

public final int getMeasuredWidth() {  
      return mMeasuredWidth & MEASURED_SIZE_MASK;  
      // getMeasuredWidth()的返回值是mMeasuredWidth(MEASURED_SIZE_MASK = 静态常量 = 限制mMeasuredWidth大小)
      // 该值的赋值来源:Measure过程中的setMeasuredDimension() -> 分析1
      //
  }  

/**
  * 分析1:setMeasuredDimension()
  * 作用:存储测量后的View宽 / 高
  * 注:该方法即为我们重写onMeasure()所要实现的最终目的
  **/
    protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {  
    //参数说明:测量后子View的宽 / 高值

        // 特别注意:
        // 将测量后子View的宽 / 高值进行传递
        // 正是这里,赋值mMeasuredWidth的 = measuredWidth
            mMeasuredWidth = measuredWidth;
            mMeasuredHeight = measuredHeight;  
          
            mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;  
        } 
    // setMeasuredDimension()的参数measuredWidth 是从getDefaultSize()获得的 
    // 在onMeasure()里调用 -> 分析2

/**
  * 分析2:onMeasure()
  * 作用:a. 根据View宽/高的测量规格计算View的宽/高值:getDefaultSize()
  *      b. 存储测量后的View宽 / 高:setMeasuredDimension()
  **/ 
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
    // 参数说明:View的宽 / 高测量规格

    // 特别注意,正是这句话,下面我们继续看getDefaultSize()的介绍 -> 分析3
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),  
                         getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));  
}

/**
  * 分析3:getDefaultSize()
  * 作用:根据View宽/高的测量规格计算View的宽/高值
  **/
  public static int getDefaultSize(int size, int measureSpec) {  

        // 参数说明:
        // size:提供的默认大小
        // measureSpec:宽/高的测量规格(含模式 & 测量大小)

            // 设置默认大小
            int result = size; 
            
            // 获取宽/高测量规格的模式 & 测量大小
            int specMode = MeasureSpec.getMode(measureSpec);  
            int specSize = MeasureSpec.getSize(measureSpec);  
          
            switch (specMode) {  
                // 模式为UNSPECIFIED时,使用提供的默认大小 = 参数Size
                case MeasureSpec.UNSPECIFIED:  
                    result = size;  
                    break;  

                // 模式为AT_MOST,EXACTLY时,使用View测量后的宽/高值 = measureSpec中的Size
                case MeasureSpec.AT_MOST:  
                case MeasureSpec.EXACTLY:  
                    result = specSize;  
                    break;  
            }  

         // 返回View的宽/高值
            return result;  
    } 

2. getWidth() / getHeight()返回值

2.1 结论

返回的值是 View在Layout过程中的宽 / 高,即最终的宽 / 高

2.2 源码分析

public final int getWidth() {  
      return mRight - mLeft;  
      // mRight、mLeft的值赋值是在layout过程中的setFrame()->分析1
  }  

  /**
  * 分析1:setFrame()
  * 作用:根据传入的4个位置值,设置View本身的四个顶点位置
  * 即:最终确定View本身的位置
  */ 
  protected boolean setFrame(int left, int top, int right, int bottom) {
        ...
    // 特别注意:就是这里赋值mRight、mLeft的
    // 通过以下赋值语句记录下了视图的位置信息,即确定View的四个顶点
    // 从而确定了视图的位置
    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;

    // setFrame()的参数left、right是从在layout()调用时传入的 -> 分析2

    }

/**
  * 分析2:layout()
  * 作用:确定View本身的位置,即设置View本身的四个顶点位置
  */ 
  public void layout(int l, int t, int r, int b) {  

	... 
      
    // 特别注意
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    // 而l、t、r、b,则是传入的View的四个顶点边界值
    // getWidth()的返回值 = mRight - mLeft = r - l = 子View的右边界 - 子view的左边界 = 宽
  ...

}   

2.3 总结


3. 应用场景

  • getMeasuredWidth() / getMeasuredHeight()是在Measure过程中赋值的,所以需在Measure过程后获取的值才有意义
  • 同理,getWidth() / getHeight()在Layout过程中赋值,所以在Layout过程后获取的值才有意义

所以,二者的应用场景是:

  • getMeasuredWidth() / getMeasuredHeight():在onLayout()中获取View的宽/高
  • getWidth() / getHeight():在除onLayout()外的地方获取View的宽/高

4. 额外注意

4.1 不相等情况

  • 问:上面提到,一般情况下,二者获取的宽 / 高是相等的。那么,“非一般” 情况是什么?(即二者不相等)
  • 答:人为设置:通过重写View的 layout()强行设置
@Override
public void layout( int l , int t, int r , int b){
   // 改变传入的顶点位置参数
   super.layout(l,t,r+100,b+100);
}
  • 效果:在任何情况下,getWidth() / getHeight()获得的宽/高 总比 getMeasuredWidth() / getMeasuredHeight()获取的宽/高大100px
  • 即:View的最终宽/高 总比 测量宽/高 大100px

虽然这样的人为设置无实际意义,但证明了:View的最终宽 / 高 与 测量宽 / 高是可以不一样

4.2 辟谣

网上流传这么一个原因描述二者的值的关系:

  • 在当屏幕可包裹内容时,他们的值是相等的;
  • 只有当view超出屏幕后,才能看出他们的区别:当超出屏幕后getMeasuredWidth() = getWidth() + 屏幕之外没有显示的大小,即:getMeasuredWidth()是实际View的大小,与屏幕无关;而getHeight的大小此时则是屏幕的大小

下面,我用一个实例来进行辟谣!

  • 实例说明:改变按钮大小(不超过屏幕 & 超过屏幕),在onWindowFocusChanged()里分别使用getWidth() & getMeasureWidth()获得按钮的宽,以进行验证
  • 注:因为在onWindowFocusChanged()时,View已经测量好了,即走完了Measure & Layout过程,所以选择在此方法中获取
  • 实现代码
public class MainActivity extends AppCompatActivity {

    private Button mBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBtn = (Button) findViewById(R.id.button);

    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        System.out.println("mBtn.getWidth() = " + mBtn.getWidth());
        System.out.println( "mBtn.getMeasureWidth() = " + mBtn.getMeasuredWidth());
    }
}

情况1:按钮大小不超出屏幕

<Button
      android:id="@+id/button"
      android:layout_width="300px"
      android:layout_height="300px"
      android:text="carson的demo"/>

测试结果:二者是相等的

情况2:按钮大小超出屏幕

<Button
      android:id="@+id/button"
      android:layout_width="2000px"
      android:layout_height="300px"
      android:text="carson的demo"/>

测试结果:二者仍然是相等的!

最终结论:在非人为设置的情况下,getWidth() / getHeight()获得的宽高(View的最终宽/高)与 getMeasuredWidth() / getMeasuredHeight()获得的宽/高(View的测量宽/高 )永远是相等的


5. 总结

  • 下面,用一张图总结getMeasuredWidth() / getMeasuredHeight() 与 getWidth() / getHeight()的区别:
  • 接下来我将继续对自定义View的应用进行讲解,感兴趣的同学可以继续关注本人运营的技术博客carson_ho的CSDN博客

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 自定义View基础 - 最易懂的自定义View原理系列(1)

    对于多View的视图,结构是树形结构:最顶层是ViewGroup,ViewGroup下可能有多个ViewGroup或View,如下图:

    Carson.Ho
  • 自定义View Layout过程 - 最易懂的自定义View原理系列(3)

    a. 步骤2 类似于 单一View的layout过程 b. 自上而下、一层层地传递下去,直到完成整个View树的layout()过程

    Carson.Ho
  • Android自定义View绘制前的准备:DecorView创建 & 显示

    回忆前文:Android自定义View基础:ViewRoot、DecorView & Window的简介,可看出最后1步 = 绘制

    Carson.Ho
  • python实现RESTful服务(基于flask)

    上一篇文章讲到如何用java实现RESTful服务,这就来讲讲怎么用python来实现吧,因为要搭建一套java和python互调的服务,两者都不能少啊。

    vell001
  • React Native中的FlexBox布局

    IT故事会
  • 基础篇章:React Native 之 TextInput 的讲解

    (友情提示:RN学习,从最基础的开始,大家不要嫌弃太基础,会的同学请自行略过,希望不要耽误已经会的同学的宝贵时间) 今天我们讲解的是React Native基础...

    非著名程序员
  • 犀牛鸟人物 | 专访中国科学院计算技术研究所敖翔

    ? 【导读】敖翔,博士,中国科学院计算技术研究所,中科院智能信息处理重点实验室副研究员、硕士生导师。2018年获得CCF-腾讯犀牛鸟基金。主要研究领域为金融大...

    腾讯高校合作
  • 搭建容器监控

    访问Grafana,通过ip+3000端口的方式访问,默认账户密码(admin/admin)。

    分母为零
  • BZOJ2462: [BeiJing2011]矩阵模板(二维hash)

    二维矩阵hash,就是对行和列分配一个不同的base,然后分别做一遍hash,这样会减少冲突的概率。

    attack
  • Mapped Statements collection does not contain

    Mapped Statements collection does not contain value for后面是什么类什么方法之类的 错误原因

    zhangheng

扫码关注云+社区

领取腾讯云代金券