View 的 getWidth()和 getMeasuredWidth()的区别及宽高获取方法

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()和 getMeasuredWidth()的区别及宽高获取方法)