关于控件getWidth和getHeight方法都为0的解释及解决方法

问题描述:
版主今天下午一直纠结这个问题,为什么有时候用getWidth和getHeight方法得到的控件的宽度和高度为0?
思路:
1.在我调用上述两个方法的时候控件并没有绘制,所以不知道宽高度。那么控件什么时候加载呢?我试着通过试验说明之。
先试验思路一:

package com.testwidthheight;

import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

public class MainActivity extends Activity {

    private EditText et1,et2;
    //测试oncreate
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        et1 = (EditText) findViewById(R.id.et1);
        et2 = (EditText) findViewById(R.id.et2);
        System.out.println("et1宽:"+et1.getWidth()+"---et1高:"+et1.getHeight());
        System.out.println("et2宽:"+et2.getWidth()+"---et2高:"+et2.getHeight());
        System.out.println("执行完毕"+System.currentTimeMillis());
    }
    public void onClick(View view){
        System.out.println("按钮被点击"+System.currentTimeMillis());
        System.out.println("et1宽:"+et1.getWidth()+"---et1高:"+et1.getHeight());
        System.out.println("et2宽:"+et2.getWidth()+"---et2高:"+et2.getHeight());
    }
}

R.layout.activity_main如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical">
    <EditText  android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/et1"/>
    <EditText  android:layout_width="50px" android:layout_height="60px" android:id="@+id/et2"/>
    <Button  android:layout_width="fill_parent" android:layout_height="wrap_content" android:onClick="onClick" android:text="不通过oncreate"/>
</LinearLayout>

在这个例子中,对于et1,我没有指定具体的宽高,对于et2,我指定了具体宽高度。输入结果如下:

12-24 20:03:30.500 8321-8321/com.testwidthheight I/System.out: et1宽:0---et1高:0
12-24 20:03:30.500 8321-8321/com.testwidthheight I/System.out: et2宽:0---et2高:0
12-24 20:03:30.500 8321-8321/com.testwidthheight I/System.out: 执行完毕1450958610509

//当我点击按钮后
12-24 20:03:34.660 8321-8321/com.testwidthheight I/System.out: 按钮被点击1450958614665
12-24 20:03:34.660 8321-8321/com.testwidthheight I/System.out: et1宽:78---et1高:118
12-24 20:03:34.660 8321-8321/com.testwidthheight I/System.out: et2宽:50---et2高:60

从这里,可以看出在oncreate方法中并没有能够显示出具体值,当我点击按钮后,理论上并没有额外多余的干扰试验的动作发生,但是却能够显示出宽高度。所以,我有理由相信,在oncreate方法执行时,edittext(可能对于所有控件都是一样)他们并没有真正加载。当oncreate方法执行完后,控件才进行加载,加载完后就有了宽高度。这样,当我点击按钮时就能够得到宽高度了。这样就是所谓的“回调机制”?
为了验证我的想法:我们自定义一个控件MyImageView:

package com.testwidthheight;

import android.content.Context;
import android.graphics.Canvas;
import android.media.Image;
import android.util.AttributeSet;
import android.widget.ImageView;

import java.sql.Time;
import java.util.Timer;

/** * Created by Administrator on 2015/12/24. */
public class MyImageView extends ImageView{
    public MyImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        System.out.println("有attrs构造被调用了" + System.currentTimeMillis());
    }

    public MyImageView(Context context) {
        super(context);
        System.out.println("无attrs构造被调用了" + System.currentTimeMillis());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        System.out.println("onMeasure被调用了"+ System.currentTimeMillis());
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        System.out.println("onDraw被调用了" + System.currentTimeMillis());
    }
}

通过system.out来判断函数调用的先后。
主函数:

package com.testwidthheight;

import android.app.Activity;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;

public class MainActivity extends Activity {

    //测试oncreate
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        System.out.println("执行完毕"+System.currentTimeMillis());
    }
}

输出结果:

12-24 20:17:24.790 1125-1125/? I/System.out: 有attrs构造被调用了1450959444799
12-24 20:17:24.790 1125-1125/? I/System.out: 执行完毕1450959444799
12-24 20:17:24.810 1125-1125/? I/System.out: onMeasure被调用了1450959444822
12-24 20:17:24.860 1125-1125/? I/System.out: onMeasure被调用了1450959444873
12-24 20:17:24.860 1125-1125/? I/System.out: onDraw被调用了1450959444874

函数从主函数开始,先执行oncreate方法,在该方法中由于setContentView(R.layout.activity_main);引用了R.layout.activity_main,而R.layout.activity_main中又有我们自定义的MyImageView.所以有attrs的构造函数被调用了。oncreate方法执行完毕以后才执行onMeasure和onDraw。同时注意onMeasure被执行了两次。这就说明了oncreate中定义的控件,你想调用它的真实宽度高度都是错误的。因为在oncreate方法执行中,他们都没有被真正的绘制。所以宽高度永远是0.
这里插一句关于回调机制的通俗理解(知乎获赞过千):
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。
那么,我们不禁要想,有没有方法在oncreate方法中也能够知道控件的真实宽高度呢?其实是有的,我在网上找了三种方法:
方法一:

final ImageView imageView = (ImageView) findViewById(R.id.imageview); 
int w = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED); 
int h = View.MeasureSpec.makeMeasureSpec(0,View.MeasureSpec.UNSPECIFIED); 
imageView.measure(w, h); 
int height =imageView.getMeasuredHeight(); 
int width =imageView.getMeasuredWidth(); 
textView.append("\n"+height+","+width); 

方法二:

final ImageView imageView = (ImageView) findViewById(R.id.imageview);  
ViewTreeObserver vto = imageView.getViewTreeObserver();  
        vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {  
            public boolean onPreDraw() {  
                int height = imageView.getMeasuredHeight();  
                int width = imageView.getMeasuredWidth();  
                textView.append("\n"+height+","+width);  
                return true;  
            }  
        });  

方法三:

final ImageView imageView = (ImageView) findViewById(R.id.imageview); 
ViewTreeObserver vto2 = imageView.getViewTreeObserver(); 
    vto2.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {  
        @Override   
        public void onGlobalLayout() {  
            imageView.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
            textView.append("\n\n"+imageView.getHeight()+","+imageView.getWidth()); 
        }   
    }); 

我们分别调用这几个函数得到不同的输出:
方法一:

12-24 20:53:34.760 32119-32119/? I/System.out: 有attrs构造被调用了1450961614773
12-24 20:53:34.770 32119-32119/? I/System.out: onMeasure被调用了1450961614780
12-24 20:53:34.770 32119-32119/? I/System.out: 执行完毕1450961614781
12-24 20:53:34.780 32119-32119/? I/System.out: onMeasure被调用了1450961614789
12-24 20:53:34.820 32119-32119/? I/System.out: onMeasure被调用了1450961614830
12-24 20:53:34.820 32119-32119/? I/System.out: onDraw被调用了1450961614832

方法二:

12-24 20:55:16.140 2119-2119/com.testwidthheight I/System.out: 有attrs构造被调用了1450961716152
12-24 20:55:16.160 2119-2119/com.testwidthheight I/System.out: 执行完毕1450961716171
12-24 20:55:16.170 2119-2119/com.testwidthheight I/System.out: onMeasure被调用了1450961716185
12-24 20:55:16.210 2119-2119/com.testwidthheight I/System.out: onMeasure被调用了1450961716225
12-24 20:55:16.220 2119-2119/com.testwidthheight I/System.out: onDraw被调用了1450961716233

方法三:

12-24 20:58:34.460 6048-6048/? I/System.out: 有attrs构造被调用了1450961914467
12-24 20:58:34.460 6048-6048/? I/System.out: 执行完毕1450961914475
12-24 20:58:34.490 6048-6048/? I/System.out: onMeasure被调用了1450961914501
12-24 20:58:34.530 6048-6048/? I/System.out: onMeasure被调用了1450961914543
12-24 20:58:34.540 6048-6048/? I/System.out: onDraw被调用了1450961914551

总结:
对于输出来说,第二种方法无论是getMeasuredHeight还是getHeight结果都相同,但对于本例来说,这两者显然是不相同的,其结果不准确,应该排除!并且其输出很多,几乎是死循环,改为false后也不能改善。

对于求getMeasuredHeight推荐用第一种方法,对于求getHeight推荐用第三种方法。当然如果可以在oncreate方法外求宽高度的话,那么肯定推荐使用在oncreate方法外调用getHeight,这是最保险的方法。

你可能感兴趣的:(控件)