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是无法获取得到宽高。
上一篇: Flex 一些资源共享