美文网首页
View 的 getWidth()和 getMeasuredWi

View 的 getWidth()和 getMeasuredWi

作者: 空山Echo | 来源:发表于2019-05-10 09:58 被阅读0次

    getMeasureWidth():测量宽度,onMeasure之后即可获得
    getWidth():实际显示宽度,onLayout之后可获得
    测量宽度和实际宽度,几乎所有情况相等,特殊情况除外如:多次测量只有最后一次相等,或重写onLayout方法了

    getMeasureWidth调用时机

    好习惯是在onLayout方法中获取测量的最终宽高

    通过View的绘制流程,我们知道在View绘制完成之后就可以通过getMeasureWidth正确获得View的测量宽度。注意,某些极端情况下系统需要多次测量才能确定最终的宽高,这样onMeasure方法中取到的值很可能不正确。

    Activity中如何获取宽高?

    在onCreate或者onResume中获取getMeasureWidth是不可行的。

    因为View的measure过程和生命周期方法不是同步执行的(Activity的启动流程和Activity的布局文件加载绘制流程,其实没有相关的关系的,其实两个异步的加载流程)。不能保证Activity执行了onCreate、onStart、onResume时某个View已经测量完毕,此时获取宽高就是0。

    onCreate中getWidth为0的解决方案(getMeasureWidth与getWidth都适用)

    1. 重写Activity/View的onWindowFocusChanged方法
    /**
     * 重写Acitivty的onWindowFocusChanged方法
     */ 
    @Override
        public void onWindowFocusChanged(boolean hasFocus) {
            super.onWindowFocusChanged(hasFocus);
            /**
             * 当hasFocus为true的时候,说明Activity的Window对象已经获取焦点,进而Activity界面已经加载绘制完成
             */
            if (hasFocus) {
                int widht = titleText.getWidth();
                int height = titleText.getHeight();
                Log.i(TAG, "onWindowFocusChanged width:" + widht + "   "
                                + "  height:" + height;
            }
        }
    

    这样重写onWindowFocusChanged方法,当获取焦点的时候我们就可以通过getWidth和getHeight方法得到组件的宽和高了。但是这时候这个方法的逻辑可能会执行多次,也就是说只要我们的Activity的window对象获取了焦点就会执行该语句,所以我们需要做一些逻辑判断,让它在我们需要打印获取组件宽高的时候在执行。

    2. 使用ViewTreeObserver 的众多回调方法
    • 为组件添加OnGlobalLayoutListener事件监听
    /**
     * 为Activity的布局文件添加OnGlobalLayoutListener事件监听,
    *当回调到onGlobalLayout方法的时候我们通过getMeasureHeight和getMeasuredWidth方法可以获取到组件的宽和高
     */
    private void initOnLayoutListener() {
            final ViewTreeObserver viewTreeObserver = this.getWindow().getDecorView().getViewTreeObserver();
            viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    Log.i(TAG, "开始执行onGlobalLayout().........");
                    int height = titleText.getMeasuredHeight();
                    int width = titleText.getMeasuredWidth();
    
                    Log.i(TAG, "height:" + height + "   width:" + width);
               // 移除GlobalLayoutListener监听     
                       MainActivity.this.getWindow().getDecorView().getViewTreeObserver().removeOnGlobalLayoutListener(this);
                }
            });
        }
    

    需要说明的是这里的onGlobalLayout方法会在Activity的组件执行完onLayout方法之后执行,这里的onLayout方法主要用于计算组件的宽高操作,具体可参考:Android源码Activity布局绘制流程,这样当我们计算完组件的宽高之后再执行获取组件的宽高操作,自然能够获取到组件的宽度和高度。

    • 为组件添加OnPreDrawListener事件监听
    /**
         * 初始化viewTreeObserver事件监听,重写OnPreDrawListener获取组件高度
         */
        private void initOnPreDrawListener() {
            final ViewTreeObserver viewTreeObserver = this.getWindow().getDecorView().getViewTreeObserver();
            viewTreeObserver.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                @Override
                public boolean onPreDraw() {
                    Log.i(TAG, "开始执行onPreDraw().........");
                    int height = titleText.getMeasuredHeight();
                    int width = titleText.getMeasuredWidth();
    
                    Log.i(TAG, "height:" + height + "   width:" + width);
                    // 移除OnPreDrawListener事件监听
                    MainActivity.this.getWindow().getDecorView().getViewTreeObserver().removeOnPreDrawListener(this);
                    return true;
                }
            });
        }
    

    需要说明的是这里的onPreDraw方法会在Activity的组件执行onDraw方法之前执行,熟悉我们Activity组件加载绘制流程的同学应该知道,这里的onDraw方法主要用于执行真正的绘制组件操作,而这时候我们已经计算出来了组件的位置,宽高等操作,这样之后再执行获取组件的宽高操作,自然能够获取到组件的宽度和高度。

    3. 使用View.post方法获取组件的宽高
    /**
     * 使用View的post方法获取组件的宽度和高度
     */
    private void initViewHandler() {
            titleText.post(new Runnable() {
                @Override
                public void run() {
                    int width = titleText.getWidth();
                    int height = titleText.getHeight();
    
                    Log.i(TAG, "initViewHandler height:" + height + "  width:" + width);
                }
            });
        }
    

    这里的view的post方法底层使用的是Android的异步消息机制,消息的执行在MainActivity主进程Loop执行之后,所以这时候也可以获取组件的宽高,更多关于Android中异步消息的内容可参考我的:Android源码解析之(二)–>异步消息机制

    4. 通过Handler对象使用异步消息获取组件的宽高
    /**
     * 在onCreate方法中发送异步消息,在handleMessage中获取组件的宽高
     */
    private Handler mHandler = new Handler() {
    
            @Override
            public void handleMessage(Message msg) {
                if (msg.what == 101) {
                    int width = titleText.getWidth();
                    int height = titleText.getHeight();
    
                    Log.i(TAG, "initViewHandler height:" + height + "  width:" + width);
                }
            }
        };
    

    和上面使用view.post方法类似,这里用的是异步消息获取组件的宽高,而这里的异步消息的执行过程是在主进程的主线程的Activity绘制流程之后,所以这时候可以获取组件的宽高。

    5. view.measure(int widthMeasureSpec,int heightMeasureSpec)

    通过手动对View进行measure来得到View的宽高,但需要根据不同情况来进行处理

    • match_parent
      这个无法measure具体的宽高,是因为需要知道父容器的parentSize,但这个时候是无法获取的。
    • 具体的数值
    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
    view.measure(widthMeasureSpec, heightMeasureSpec);
    
    • wrap_content
    int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
    int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1 << 30) - 1, View.MeasureSpec.AT_MOST);
    view.measure(widthMeasureSpec, heightMeasureSpec);
    

    原文:https://blog.csdn.net/qq_23547831/article/details/51764304

    相关文章

      网友评论

          本文标题:View 的 getWidth()和 getMeasuredWi

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