在 Android 里量控件宽高,onCreate 里直接 getHeight() / getMeasuredWidth() 经常是 0:界面还没走完 measure / layout。本文汇总控件、状态栏、标题栏、屏幕和内容区的取法,并用 PopupWindow 把这些尺寸串起来。
一、为什么 onCreate 里拿不到尺寸
onCreate 只负责数据初始化,控件还没真正绘制;等到 onDraw 再量又太晚。正确时机是:measure / layout 刚完成、绘制之前。可用 ViewTreeObserver 或 View.post() 卡住这一拍。
| 方法 |
时机 |
注意 |
| OnPreDrawListener |
绘制前 |
可能反复回调,用 hasMeasured 只算一次 |
| OnGlobalLayoutListener |
全局 layout 完成 |
同样可能多次触发,量完应移除监听 |
| View.post(Runnable) |
当前 layout 消息之后 |
只跑一次,语法更简单,一般优先用这个 |
二、获取控件尺寸
方法一:OnPreDrawListener
View layout = findViewById(R.id.popwindow_btn);
ViewTreeObserver vto = layout.getViewTreeObserver();
vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw() {
if (hasMeasured == false) {
int btWidth = layout.getMeasuredWidth();
int btHeight = layout.getMeasuredHeight();
hasMeasured = true;
}
return true;
}
});
方法二:OnGlobalLayoutListener
ViewTreeObserver vto = mPopupBtn.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (hasMeasured == false) {
int btWidth = mPopupBtn.getMeasuredWidth();
int btHeight = mPopupBtn.getMeasuredHeight();
hasMeasured = true;
}
}
});
方法三:View.post()
setContentView() 之后,UI 事件队列里会有一条要求重新 layout 的消息。你 post 进去的 Runnable 会排在它后面,因此 run 时宽高已经就绪。比 Observer 省事:只执行一次,也不用再禁用监听。
final View view = // smth;
view.post(new Runnable() {
@Override
public void run() {
view.getHeight(); // height is ready
}
});
三、状态栏、标题栏、屏幕与内容区
状态栏高度取可见窗口矩形的 frame.top:
public int getStatusBarHeight() {
Rect frame = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(frame);
int statusBarHeight = frame.top;
return statusBarHeight;
}
标题栏:内容区顶部减去状态栏;有 ActionBar 时也可直接 getActionBar().getHeight()(需确认 Title / ActionBar 没有隐藏)。
public int getTitleBarHeight() {
int statusBarHeight = getStatusBarHeight();
int contentTop = getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();
int titleBarHeight = contentTop - statusBarHeight;
return titleBarHeight;
}
public int getTitleBarHeight() {
return getActionBar().getHeight();
}
屏幕宽高用 DisplayMetrics;内容区高度走 android.R.id.content。
public int[] getScreenSize() {
DisplayMetrics dm = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(dm);
int screenWidth = dm.widthPixels;
int screenHeigh = dm.heightPixels;
return new int[] { screenWidth, screenHeigh };
}
int contentViewHeight = findViewById(android.R.id.content).getHeight();
四、弹框实例
弹窗宽 = 屏幕宽减两侧边距;高 = 内容区高度减按钮高度再减边距。点击空白关闭需设背景、可聚焦、外部可点。定位用 getLocationOnScreen,location[0] 是 x,location[1] 是 y。
public void showPopupWindow(int buttonHeight) {
View contentView = getPopWindowContentViewBySimpleAdatper();
int margin = 50;
int contentViewHeight = findViewById(android.R.id.content).getHeight();
int[] screenSize = getScreenSize();
int screenWidth = screenSize[0];
int screenHeigh = screenSize[1];
int titleHeight = getTitleBarHeight();
int statusBarHeight = getStatusBarHeight();
int popwindowWidth = screenWidth - 2 * margin;
int popwindowHeight = contentViewHeight - buttonHeight - margin;
// int popwindowHeight = screenHeigh - buttonHeight - titleHeight - statusBarHeight - margin;
mPopwindow = new PopupWindow(contentView, popwindowWidth, popwindowHeight);
mPopwindow.setBackgroundDrawable(new BitmapDrawable());
mPopwindow.setFocusable(true);
mPopwindow.setOutsideTouchable(true);
mPopupBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mPopwindow.isShowing()) {
mPopwindow.dismiss();
} else {
int[] location = new int[2];
v.getLocationOnScreen(location);
Logs.v("location[0] :" + location[0] + " location[1] :" + location[1]);
mPopwindow.showAtLocation(v, Gravity.NO_GRAVITY, location[0] + 50, location[1] + v.getHeight());
}
}
});
}
一句话总结:onCreate 量不到宽高,等 layout 完成再用 View.post 或 ViewTreeObserver;屏幕、状态栏、标题栏、内容区分开取,弹窗宽高才能算对。