Better

业精于勤荒于嬉

Android 状态栏高度-屏幕划分-正确获取标题栏高度

Better's Avatar 2017-11-19 Android

  1. 1. 需求
  2. 2. 联系之屏幕的划分
    1. 2.1. 获取屏幕的宽、高
    2. 2.2. 状态栏
    3. 2.3. View布局绘制区域即Content
    4. 2.4. 标题栏
    5. 2.5. 应用内容区域=>标题栏+View布局绘制区域
    6. 2.6. 导航栏
    7. 2.7. 结果
  3. 3. 参考

需求

今天的需求是要获取到状态栏的高度没因为UI是全屏填充到顶部。通过margin来控制是否显示到状态栏下面。
在网上看到一个见解:
不同的Android设备状态栏的高度不一样。240x320高度是20px,320x480高度是25px,480x800是38px。记得以前有人说是25px,所以是不对的。获取方式是根据Window来获取:

1
2
3
4
5
6
7
Rect rectangle = new Rect();
Window window = getWindow();
window.getDecorView().getWindowVisibleDisplayFrame(rectangle);
int statusBarHeight = rectangle.top;
int contentViewTop =
window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
int titleBarHeight= contentViewTop - statusBarHeight;

height-of-status-bar-in-android

将这块整理一下。
getWindowVisibleDisplayFrame()从方法的描述老看:获取窗口可视区域,方法需要IPC的支持,所以不应该用在关键处的代码(critical code),比如draw方法中。而且需要View已经attach到Window的时候才会有值。

由于getWindowVisibleDisplayFrame()方法是View类下的一个方法,所以只能通过View对象来调用。一个窗口中通常都会有多个View,getWindowVisibleDisplayFrame()方法的返回结果和该窗口中选取的View并没有关系。在某个时刻,使用当前窗口中的任意View执行getWindowVisibleDisplayFrame()返回的结果都是一样的

getWindowVisibleDisplayFrame()方法返回的是窗口的可视区域大小,并非某个View的可视区域大小,所以用窗口中的任意View来执行都是没有差别的。

联系之屏幕的划分

手机展示的界面从上到下依次是

  1. 状态栏(包含通知,系统时间),
  2. 然后是应用的标题栏,
  3. 应用的View布局栏,
  4. 以及底部的导航栏。

有的手机有实体按钮所有没有底部的导航栏。
IMAGE

获取屏幕的宽、高

1
2
3
4
5
6
DisplayMetrics metrics = new DisplayMetrics();
getWindowManager().getDefaultDisplay().getMetrics(metrics);
int widthPixels = metrics.widthPixels;
int heightPixels = metrics.heightPixels;
//获取包含导航栏一起的高度
((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getRealMetrics(mDisplayMetrics);

状态栏

1
2
3
4
5
6
7
Rect rect= new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
int statusBarHeight = rectangle.top;
//或者
Resources resources = mActivity.getResources();
int resourceId = resources.getIdentifier("status_bar_height", "dimen","android");
int height = resources.getDimensionPixelSize(resourceId);

View布局绘制区域即Content

1
2
3
4
5
Rect rect = new Rect(); 
getWindow().findViewById(Window.ID_ANDROID_CONTENT)
.getDrawingRect(rect);
int width = rect.width();
int height = rect.height();

标题栏

1
2
3
int contentViewTop = 
window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
int titleBarHeight= contentViewTop - statusBarHeight;

应用内容区域=>标题栏+View布局绘制区域

1
2
3
4
Rect rect = new Rect();     
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
int width = rect.width();
int height = rect.height();

导航栏

1
2
3
Resources resources = getResources();  
int resourceId = resources.getIdentifier("navigation_bar_height","dimen", "android");
int height = resources.getDimensionPixelSize(resourceId);

结果

一个Activity是附着在一个Window上的。Window中包含一个顶级视图DecorView,
DecorView中只有一个线性布局的child叫mContentRoot。mContentRoot包含mContentparent、标题栏。而mContentParent就是setContentView所加载的布局的容器,资源id是content。

  • getDecorView() 是不包含状态栏的。所以他的区域rect.top就是状态栏高度。
  • getWindow().findViewById(Window.ID_ANDROID_CONTENT).getDrawingRect(rect)就是获取的内容区域,所以rect.top获取的就是状态栏的标题栏的高度。

所以屏幕的高度=状态栏高度+DecorView高度+导航栏的高度
       =状态栏高度+标题栏高度+content高度+导航栏高度

但是结果并不是这样:
IMAGE
(Px_XL_1440x2560)

标题栏和content的高度与decorView的高度之间差了一个状态栏的高度。奇怪?

状态栏和导航栏肯定是对的,因为直接从资源里面取出来的,decorView的高度是直接获取的没有问题。问题就出在标题栏高度、content高度上。

手动将标题栏和content高度打印出来:
IMAGE
以及content的top,windowFrame以及DrawingRect打印出来:
IMAGE
日志:
IMAGE

发现window的高度是2392,content的bottom-top等于2112,获取到的高度也是2112。所以content的高度是对的。哪问题就出在title的高度了,两个的差值是196。
刚刚求标题栏高度是contentViewTop-statusBarHeight。所以问题就出在这,因为:
IMAGE

contentViewTop就是content在父容器DecorView的坐标位置,就是标题栏的高度。
所以标题栏高度:

1
2
3
int contentViewTop = 
window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
int titleBarHeight=contentViewTop;

IMAGE
noActionBar:
IMAGE

参考

Android获取窗口可视区域大小: getWindowVisibleDisplayFrame()
android 屏幕划分
深入浅析Android坐标系统

This article was last updated on days ago, and the information described in the article may have changed.