1、getHeight()无效
我们先来看看在 onCreate() 中用控件的 getHeight() 和 getWidth() 方法会出现什么情况。
public class MainActivity extends AppCompatActivity {
private ImageView mImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (ImageView) findViewById(R.id.id_iv);
Log.i("TAG", "height=" + mImageView.getHeight() +
" width=" + mImageView.getWidth());
}
}
TAG:height=0 width=0
在 onCreate() 中获取到的控件宽高为0,这是为什么呢?我们来重写下控件的 onMeasure(),看看控件的绘制测量与 Activity 的 onCreate() 的先后关系:
public class MyImageView extends AppCompatImageView {
public MyImageView(Context context) {
this(context, null);
}
public MyImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public MyImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Log.i("TAG", "onMeasure()被调用了" + System.currentTimeMillis());
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Log.i("TAG", "onCreate()执行完毕" + System.currentTimeMillis());
}
TAG: height=0 width=0
TAG: onCreate()执行完毕 1502893100701
TAG: onMeasure()被调用了1502893100781
TAG: onMeasure()被调用了1502893101006
TAG: onMeasure()被调用了1502893101066
由此可见,onCreate() 执行完了,我们定义的控件才会被测量,所以我们在 onCreate() 里面通过 view.getHeight() 获取控件的高度肯定是0,因为它还没有被测量,也就是说它自己都不知道自己有多高,而我们这时候去获取它的尺寸,肯定是不行的。
2、调用View的measure方法
从上面的分析我们可以得出,之所以无法测量,是因为控件是在 onCreate() 执行完后才被测量的,所以我们要想得到结果可以主动调用方法去测量它。
public class MainActivity extends AppCompatActivity {
private MyImageView mImageView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mImageView = (MyImageView) findViewById(R.id.id_iv);
mImageView.setImageResource(R.drawable.t3);
Log.i("TAG", "width=" + getViewWidth(mImageView));
}
public int getViewWidth(ImageView view) {
view.measure(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
return view.getMeasuredWidth();
}
}
TAG: width=2048
我们在 onCreate() 中要用到控件的宽高的时候可以主动去为它测量,也就是直接调用一个 view 或者 viewgroup 的 measure() 去测量。
测量之后该 view 的 getMeasuredHeight() 就会返回刚才测量所得的高,getMeasuredWidth() 返回测量所得宽。
本来在布局加载的过程中,view的 measure方法一定会被系统调用(在onResume() 中已经调用了 measure方法),但这发生在我们所不知道的某个时间点,为了在这之前提前得到测量结果,我们主动调用 measure方法,但是这样做的好处是可以立即获得宽和高,坏处是多了一次测量过程。
该方法测量的宽度和高度可能与视图绘制完成后的真实的宽度和高度不一致。
3、OnGlobalLayoutListener获取
另一种方法是利用 ViewTreeObserver 的 OnGlobalLayoutListener 来测量。关于 ViewTreeObserver 我在我的博客Android–ViewTreeObserver介绍有详细介绍,不了解的朋友可以看看。
在布局发生改变或者某个视图的可视状态发生改变时调用该事件,会被多次调用,因此需要在获取到视图的宽度和高度后执行 remove 方法移除该监听事件。
ViewTreeObserver observer = mImageView.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mImageView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
final int w = mImageView.getMeasuredWidth();
final int h = mImageView.getMeasuredHeight();
}
});
4、OnPreDrawListener获取
在视图将要绘制时调用该监听事件,会被调用多次,因此获取到视图的宽度和高度后要移除该监听事件。这同样是 ViewTreeObserver 的接口。
mText.getViewTreeObserver().addOnPreDrawListener(
new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
mText.getViewTreeObserver().removeOnPreDrawListener(this);
int w = mText.getWidth();
int h = mText.getHeight();
return true;
}
}
);
5、OnLayoutChangeListener获取
在视图的 layout 改变时调用该事件,会被多次调用,因此需要在获取到视图的宽度和高度后执行 remove 方法移除该监听事件。
mText.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int left, int top, int right, int bottom,
int oldLeft, int oldTop, int oldRight, int oldBottom) {
mText.removeOnLayoutChangeListener(this);
int w = mText.getWidth();
int h = mText.getHeight();
}
});
6、重写View的onSizeChanged()
在视图的大小发生改变时调用该方法,会被多次调用,因此获取到宽度和高度后需要考虑禁用掉代码。
该实现方法需要继承 View,且多次被调用,不建议使用。
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
Log.i("TAG", "width = " + getWidth() + "height = " + getHeight());
}
7、重写View的onLayout()
该方法会被多次调用,获取到宽度和高度后需要考虑禁用掉代码。
该实现方法需要继承 View,且多次被调用,不建议使用。
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Log.i("TAG", "width = " + getWidth() + "height = " + getHeight());
}
8、使用View.post()方法
Runnable 对象中的方法会在 View 的 measure()、layout() 等事件完成后触发。
UI 事件队列会按顺序处理事件,在 setContentView() 被调用后,事件队列中会包含一个要求重新 layout 的 message,所以任何 post 到队列中的 Runnable 对象都会在 Layout 发生变化后执行。
该方法只会执行一次,且逻辑简单,建议使用。
mImageView.post(new Runnable() {
@Override
public void run() {
Log.i("TAG", "width = " + mImageView.getWidth() +
"height = " + mImageView.getHeight());
}
});
以上就是各种获取 View 的宽高的方法,充分了解它们适用的场合,我们去做布局会更加简单、轻松。
结束语:本文仅用来学习记录,参考查阅。