美文网首页Android进阶之路Android开发经验谈Android开发
Android自定义View:你知道通过getWidth()

Android自定义View:你知道通过getWidth()

作者: Carson带你学安卓 | 来源:发表于2019-06-26 08:50 被阅读120次

    前言

    • 在自定义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()的区别:
    示意图

    请点赞!因为你们的赞同/鼓励是我写作的最大动力!

    相关文章阅读
    Android事件分发机制详解:史上最全面、最易懂
    Android开发:最全面、最易懂的Android屏幕适配解决方案
    Android开发:史上最全的Android消息推送解决方案
    Android开发:最全面、最易懂的Webview详解
    Android开发:JSON简介及最全面解析方法!
    Android四大组件:Service服务史上最全面解析
    Android四大组件:BroadcastReceiver史上最全面解析


    欢迎关注Carson_Ho的简书!

    不定期分享关于安卓开发的干货,追求短、平、快,但却不缺深度

    相关文章

      网友评论

        本文标题:Android自定义View:你知道通过getWidth()

        本文链接:https://www.haomeiwen.com/subject/poupaqtx.html