问题描述:
版主今天下午一直纠结这个问题,为什么有时候用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,这是最保险的方法。