Android之View的知识(getWidth() 和getMeasuredWidth区别 如何在oncreate获取宽高)

1.View的getWidth()和getMeasuredWidth()的区别
2.如何在onCreate中拿到View的宽度和高度

问题1:View的getWidth()和getMeasuredWidth()的区别

首先 getWidth()和getMeasuredWidth()都是控件获取的宽度,但是这两者是有区别的,有什么区别呢,下面一步一步带你介绍:

先举个例子:
布局文件如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.nova.widthdemo.MainActivity">

    <Button
        android:id="@+id/btn_one"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
 />

</RelativeLayout>


MainActivity:

public class MainActivity extends Activity {

    private Button btn_one;
    private final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_one = (Button)findViewById(R.id.btn_one);
        Log.d(TAG,btn_one.getWidth()+"");
        Log.d(TAG,btn_one.getMeasuredWidth()+"");

    }
}

打印的结果如下:


发现得到的值都是0 因为在oncreate方法在控件没有测量好,所以获取不了,那下面在onWindowFocusChanged下获取对应的值。


xml文件:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.nova.widthdemo.MainActivity">

    <Button
        android:id="@+id/btn_one"
        android:layout_width="300dp"
        android:layout_height="200dp"
        android:text="Hello World!"
 />

</RelativeLayout>

MainActivity:


public class MainActivity extends Activity {

    private Button btn_one;
    private final String TAG = "MainActivity";
    private boolean isFocus=false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_one = (Button)findViewById(R.id.btn_one);


    }


    @Override
    protected void onResume(){
        super.onResume();
        Log.d(TAG,btn_one.getWidth()+"");
        Log.d(TAG,btn_one.getMeasuredWidth()+"");
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);

        if(!isFocus&&hasFocus){

            Log.i("MainActivity","btnTest.getWidth="+btn_one.getWidth());
            Log.i("MainActivity","btnTest.getMeasureWidth="+btn_one.getMeasuredWidth());

            isFocus=true;
        }

    }
}

运行结果:


可以发现getWidth和getMeasureWidth的值是一模一样的

那么两者到底有什么不同呢,就要从源码分析:

getWidth():

/**
     * Return the width of the your view.
     *
     * @return The width of your view, in pixels.
     */
    @ViewDebug.ExportedProperty(category = "layout")
    public final int getWidth() {
        return mRight - mLeft;
    }

可以发现返回的是mRight-mLeft,在《Android开发艺术探索》第四章有说到:Layout过程决定了View的四个顶点的坐标和实际View的宽/高,完成以后,可以通过getTop,getBottom,getLeft和getRight来拿到View的四个顶点的位置,并可以通过getWidth和getHeight方法来拿到View的最终宽/高。也就是mRight,mLeft这两个值是从onLayout传过来的,追述到onLayout源码:


  @SuppressWarnings({"unchecked"})
    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;

        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);

            if (shouldDrawRoundScrollbar()) {
                if(mRoundScrollbarRenderer == null) {
                    mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
                }
            } else {
                mRoundScrollbarRenderer = null;
            }

            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

        if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
            mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
            notifyEnterOrExitForAutoFillIfNeeded(true);
        }
    }


在艺术探索中有提到在layout方法会通过setFrame去设置子元素的四个顶点的位置,那么现在看看setFrame方法:



也就是说getWidth()方法是通过layout方法后才有值的。

getMeasuredWidth()源码:

   * @return The raw measured width of this view.
     */
    public final int getMeasuredWidth() {
        return mMeasuredWidth & MEASURED_SIZE_MASK;
    }

可以看到返回mMeasuredWidth 与 MEASURED_SIZE_MASK,MEASURED_SIZE_MASK是一个状态,是提供实际测量尺寸。那么主要关注mMeasuredWidth是哪里来的呢?追溯到:


原来是在setMeasuredDimensionRaw方法传入的,而setMeasuredDimensionRaw是在setMeasuredDimension方法里调用的,如下图:


而 setMeasuredDimension是在onMeasure方法里调用的:


也就是说在measure方法介绍后getMeasuredWidth()就会有值。

从这里可以得出:

在View的默认实现中:getMeasuredWidth()测量宽高和getWidth()最终宽高是相等的,只不过getMeasuredWidth()形成于measure过程,而getWidth()形成于layout过程,也就是说两者的赋值世纪不同,getMeasuredWidth()赋值的时机稍微早一些。


下面演示getMeasuredWidth和getWidth的值不相同:


首先自定义一个view:

public class MyGroup1 extends ViewGroup {
    public MyGroup1(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        super.onMeasure(widthMeasureSpec, widthMeasureSpec);

        View view = getChildAt(0);
        measureChild(view, MeasureSpec.EXACTLY + 100, MeasureSpec.EXACTLY + 100);
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        View childView = getChildAt(0);
        childView.layout(0, 0, 200, 200);
    }
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<com.nova.widthdemo.MyGroup1 xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <Button
        android:id="@+id/btn1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="你好" />

</com.nova.widthdemo.MyGroup1>


然后MainActivity:

public class MainActivity extends Activity {

    private Button btn_one;
    private final String TAG = "MainActivity";
    private boolean isFocus=false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_one = (Button)findViewById(R.id.btn_one);


    }


    @Override
    protected void onResume(){
        super.onResume();
        Log.d(TAG,btn_one.getWidth()+"");
        Log.d(TAG,btn_one.getMeasuredWidth()+"");
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);

        if(!isFocus&&hasFocus){

            Log.i("MainActivity","btnTest.getWidth="+btn_one.getWidth());
            Log.i("MainActivity","btnTest.getMeasureWidth="+btn_one.getMeasuredWidth());

            isFocus=true;
        }

    }
}

看打印结果:


可发现两者不一样。但是结果还是显示宽200的正方形,证明了测量宽/高可以不等于最终宽/高,但是这是没有任何意义。还有一种情况是,view需要经过多次measure才能确定自己的测量宽/高,前几次可能测出的宽/高和可能最终宽/高不一致,但是最后结果还是一样的。
2.如何在onCreate()如何获取view的宽高?
当创建某些View时,需要通过获取width和height来确定view的布局,但是在哦那oncreate方法内是获取不到view的宽高,因为view还没绘制完成。你必须等待view绘制完成才能获取,因此通过其他方法来等view的绘制来获取宽高。


1.view.post就是通过View的post()方法解决上述问题,该方法接收一个Runnable线程参数,并将其添加到消息队列中,有趣的是Runnable线程会在UI线程中执行


看下打印结果:


public class MainActivity extends Activity {

    private Button btn_one;
    private final String TAG = "MainActivity";
    private boolean isFocus=false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_one = (Button)findViewById(R.id.btn1);

        Log.d(TAG,btn_one.getWidth()+"");
        Log.d(TAG,btn_one.getHeight()+"");
        btn_one.post(new Runnable() {

            @Override
            public void run() {
                Log.d(TAG,btn_one.getHeight()+"");
                Log.d(TAG,btn_one.getWidth()+"");
            }
        });

    }


    @Override
    protected void onResume(){
        super.onResume();
        Log.d(TAG,btn_one.getWidth()+"");
        Log.d(TAG,btn_one.getMeasuredWidth()+"");
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);

        if(!isFocus&&hasFocus){

//            Log.i("MainActivity","btnTest.getWidth="+btn_one.getWidth());
//            Log.i("MainActivity","btnTest.getMeasureWidth="+btn_one.getMeasuredWidth());

            isFocus=true;
        }

    }
}



可发现是可以获取view的宽高。


2.ViewtreeObserver使用ViewTreeObserver的众多回调可以完成这个功能,比如使用OnGlobalLayoutListener这个接口,当view树的状态发生改变或者view树内部的view的可见性发生改变时,onGlobalLayout方法将被回调,因此这是获取view的宽高一个很好的时机。需要注意的是,伴随着view树的状态改变等,onGlobalLayout会被调用多次。

public class MainActivity extends Activity {

    private Button btn_one;
    private final String TAG = "MainActivity";
    private boolean isFocus=false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_one = (Button)findViewById(R.id.btn1);

        Log.d(TAG,btn_one.getWidth()+"");
        Log.d(TAG,btn_one.getHeight()+"");
//        btn_one.post(new Runnable() {
//
//            @Override
//            public void run() {
//                Log.d(TAG,btn_one.getHeight()+"");
//                Log.d(TAG,btn_one.getWidth()+"");
//            }
//        });
        btn_one.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                Log.d(TAG,btn_one.getWidth()+"");
                Log.d(TAG,btn_one.getHeight()+"");
            }
        });

    }


    @Override
    protected void onResume(){
        super.onResume();
        Log.d(TAG,btn_one.getWidth()+"");
        Log.d(TAG,btn_one.getMeasuredWidth()+"");
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);

        if(!isFocus&&hasFocus){

//            Log.i("MainActivity","btnTest.getWidth="+btn_one.getWidth());
//            Log.i("MainActivity","btnTest.getMeasureWidth="+btn_one.getMeasuredWidth());

            isFocus=true;
        }

    }
}



打印结果:



3.VIew.measure(int widthMeasureSpec,int heightMeasureSpec)

public class MainActivity extends Activity {

    private Button btn_one;
    private final String TAG = "MainActivity";
    private boolean isFocus=false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        btn_one = (Button)findViewById(R.id.btn1);

        Log.d(TAG,btn_one.getWidth()+"");
        Log.d(TAG,btn_one.getHeight()+"");
//        btn_one.post(new Runnable() {
//
//            @Override
//            public void run() {
//                Log.d(TAG,btn_one.getHeight()+"");
//                Log.d(TAG,btn_one.getWidth()+"");
//            }
//        });
//        btn_one.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
//            @Override
//            public void onGlobalLayout() {
//                Log.d(TAG,btn_one.getWidth()+"");
//                Log.d(TAG,btn_one.getHeight()+"");
//            }
//        });
        int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
        int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
        btn_one.measure(widthMeasureSpec, heightMeasureSpec);
        Log.d(TAG,btn_one.getMeasuredWidth()+"");
        Log.d(TAG,btn_one.getMeasuredHeight()+"");

    }


    @Override
    protected void onResume(){
        super.onResume();
//        Log.d(TAG,btn_one.getWidth()+"");
//        Log.d(TAG,btn_one.getMeasuredWidth()+"");
    }

    @Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);

        if(!isFocus&&hasFocus){

//            Log.i("MainActivity","btnTest.getWidth="+btn_one.getWidth());
//            Log.i("MainActivity","btnTest.getMeasureWidth="+btn_one.getMeasuredWidth());

            isFocus=true;
        }

    }
}


打印结果:
注意:这种方法,如果是view设置match_parent是无法获取得到宽高。



























  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值