欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-07-14 17:50:36
...

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()+"");

    }
}

打印的结果如下:

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

发现得到的值都是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;
        }

    }
}

运行结果:

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

可以发现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方法:


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

也就是说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是哪里来的呢?追溯到:

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

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

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

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

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

也就是说在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;
        }

    }
}

看打印结果:

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

可发现两者不一样。但是结果还是显示宽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;
        }

    }
}


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

可发现是可以获取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;
        }

    }
}



打印结果:


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

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;
        }

    }
}

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

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





























相关标签: android