如何以编程方式获得 Android 导航栏的高度和宽度?

在 Android 中,屏幕底部的黑色导航条不容易移除。自3.0版本以来,它一直是 Android 的一部分,作为硬件按钮的替代品。下面是一张图片:

System Bar

如何得到这个 UI 元素的宽度和高度(以像素为单位) ?

166469 次浏览

尝试以下代码:

Resources resources = context.getResources();
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
return resources.getDimensionPixelSize(resourceId);
}
return 0;

实际上,平板电脑(至少是 Nexus 7)的导航栏在纵向和横向尺寸上都有所不同,所以这个功能应该是这样的:

private int getNavigationBarHeight(Context context, int orientation) {
Resources resources = context.getResources();


int id = resources.getIdentifier(
orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape",
"dimen", "android");
if (id > 0) {
return resources.getDimensionPixelSize(id);
}
return 0;
}

在 Kotlin:

private fun getNavigationBarHeight(): Int {
val resources: Resources = requireContext().resources


val resName = if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
"navigation_bar_height"
} else {
"navigation_bar_height_landscape"
}


val id: Int = resources.getIdentifier(resName, "dimen", "android")


return if (id > 0) {
resources.getDimensionPixelSize(id)
} else {
0
}
}

底部导航栏的高度为48dp (在纵向和横向模式下) ,垂直放置时为42dp。

我通过比较应用程序可用的屏幕大小和真实的屏幕大小来得到导航栏大小。我假设当应用程序可用的屏幕尺寸小于实际屏幕尺寸时,就会出现导航栏。然后计算导航条的大小。此方法适用于 API 14及以上版本。

public static Point getNavigationBarSize(Context context) {
Point appUsableSize = getAppUsableScreenSize(context);
Point realScreenSize = getRealScreenSize(context);


// navigation bar on the side
if (appUsableSize.x < realScreenSize.x) {
return new Point(realScreenSize.x - appUsableSize.x, appUsableSize.y);
}


// navigation bar at the bottom
if (appUsableSize.y < realScreenSize.y) {
return new Point(appUsableSize.x, realScreenSize.y - appUsableSize.y);
}


// navigation bar is not present
return new Point();
}


public static Point getAppUsableScreenSize(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point size = new Point();
display.getSize(size);
return size;
}


public static Point getRealScreenSize(Context context) {
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point size = new Point();


if (Build.VERSION.SDK_INT >= 17) {
display.getRealSize(size);
} else if (Build.VERSION.SDK_INT >= 14) {
try {
size.x = (Integer) Display.class.getMethod("getRawWidth").invoke(display);
size.y = (Integer) Display.class.getMethod("getRawHeight").invoke(display);
} catch (IllegalAccessException e) {} catch (InvocationTargetException e) {} catch (NoSuchMethodException e) {}
}


return size;
}

更新

对于一个解决方案,考虑到显示切割请检查 约翰的回答

NavigationBar 的高度因某些设备而异,但也因某些方向而异。首先你必须检查设备是否有导航栏,然后设备是平板电脑还是非平板电脑(手机) ,最后你必须查看设备的方向以得到正确的高度。

public int getNavBarHeight(Context c) {
int result = 0;
boolean hasMenuKey = ViewConfiguration.get(c).hasPermanentMenuKey();
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);


if(!hasMenuKey && !hasBackKey) {
//The device has a navigation bar
Resources resources = c.getResources();


int orientation = resources.getConfiguration().orientation;
int resourceId;
if (isTablet(c)){
resourceId = resources.getIdentifier(orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_height_landscape", "dimen", "android");
}  else {
resourceId = resources.getIdentifier(orientation == Configuration.ORIENTATION_PORTRAIT ? "navigation_bar_height" : "navigation_bar_width", "dimen", "android");
}


if (resourceId > 0) {
return resources.getDimensionPixelSize(resourceId);
}
}
return result;
}




private boolean isTablet(Context c) {
return (c.getResources().getConfiguration().screenLayout
& Configuration.SCREENLAYOUT_SIZE_MASK)
>= Configuration.SCREENLAYOUT_SIZE_LARGE;
}

Egidijus 提出的解决方案对 Build.VERSION.SDK _ INT > = 17非常有效

但是在我的设备上执行以下 Build.VERSION.SDK _ INT < 17语句时,我得到了“ NoSuchMethodException”:

Display.class.getMethod("getRawHeight").invoke(display);

我已经为这种情况修改了 getRealScreenSize ()方法:

else if(Build.VERSION.SDK_INT >= 14)
{
View decorView = getActivity().getWindow().getDecorView();
size.x = decorView.getWidth();
size.y = decorView.getHeight();
}

我希望这能帮到你

public int getStatusBarHeight() {
int result = 0;
int resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
result = getResources().getDimensionPixelSize(resourceId);
}
return result;
}


public int getNavigationBarHeight()
{
boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0 && !hasMenuKey)
{
return getResources().getDimensionPixelSize(resourceId);
}
return 0;
}

我是这么解决的。我做了一个隐藏的底部酒吧,需要填充,这取决于是否有一个导航栏(电容,屏幕上或只是前棒棒糖)。


观景

setPadding(0, 0, 0, Utils.hasNavBar(getContext()) ? 30 : 0);

Utils.java

public static boolean hasNavBar(Context context) {
// Kitkat and less shows container above nav bar
if (android.os.Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
return false;
}
// Emulator
if (Build.FINGERPRINT.startsWith("generic")) {
return true;
}
boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey();
boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);
boolean hasNoCapacitiveKeys = !hasMenuKey && !hasBackKey;
Resources resources = context.getResources();
int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
boolean hasOnScreenNavBar = id > 0 && resources.getBoolean(id);
return hasOnScreenNavBar || hasNoCapacitiveKeys || getNavigationBarHeight(context, true) > 0;
}


public static int getNavigationBarHeight(Context context, boolean skipRequirement) {
int resourceId = context.getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0 && (skipRequirement || hasNavBar(context))) {
return context.getResources().getDimensionPixelSize(resourceId);
}
return 0;
}

我解决了所有设备的这个问题(包括 Nexus 5,Galaxy Nexus 6 edge + ,三星 S10,三星 Note II 等)。我认为这将帮助您处理设备相关的问题。

这里我添加了两种类型的代码,

Java 代码(针对本地 Android) :

import android.content.Context;
import android.content.res.Resources;
import android.os.Build;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.ViewConfiguration;
import android.view.WindowManager;


public class DeviceSpec {


private int resourceID = -1;
private Display display = null;
private DisplayMetrics displayMetrics = null;
private DisplayMetrics realDisplayMetrics = null;
private Resources resources = null;
private WindowManager windowManager = null;


public double GetNavigationBarHeight(Context context) {
try {
windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
display = windowManager.getDefaultDisplay();
displayMetrics = new DisplayMetrics();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {
realDisplayMetrics = new DisplayMetrics();
display.getMetrics(displayMetrics);
display.getRealMetrics(realDisplayMetrics);
if(displayMetrics.heightPixels != realDisplayMetrics.heightPixels) {
resources = context.getResources();
return GetNavigationBarSize(context);
}
}
else {
resources = context.getResources();
resourceID = resources.getIdentifier("config_showNavigationBar", "bool", "android");
if (resourceID > 0 && resources.getBoolean(resourceID))
return GetNavigationBarSize(context);
}
}
catch (Exception e){
e.printStackTrace();
}
return 0;
}


private double GetNavigationBarSize(Context context) {
resourceID = resources.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceID > 0 && ViewConfiguration.get(context).hasPermanentMenuKey())
return (resources.getDimensionPixelSize(resourceID) / displayMetrics.density);
return 0;
}
}

和 C # 代码(用于 Xamarin Forms/Android)

int resourceId = -1;
IWindowManager windowManager = null;
Display defaultDisplay = null;
DisplayMetrics displayMatrics = null;
DisplayMetrics realMatrics = null;
Resources resources = null;


public double NavigationBarHeight
{
get
{
try
{
windowManager = Forms.Context.GetSystemService(Context.WindowService).JavaCast<IWindowManager>();
defaultDisplay = windowManager.DefaultDisplay;
displayMatrics = new DisplayMetrics();
if (Build.VERSION.SdkInt >= BuildVersionCodes.JellyBeanMr2)
{
realMatrics = new DisplayMetrics();
defaultDisplay.GetMetrics(displayMatrics);
defaultDisplay.GetRealMetrics(realMatrics);
if (displayMatrics.HeightPixels != realMatrics.HeightPixels)
{
resources = Forms.Context.Resources;
return GetHeightOfNivigationBar();
}
}
else {
resources = Forms.Context.Resources;
resourceId = resources.GetIdentifier("config_showNavigationBar", "bool", "android");
if (resourceId > 0 && resources.GetBoolean(resourceId))
return GetHeightOfNivigationBar();
}
}
catch (Exception e) { }
return 0;
}
}


private double GetHeightOfNivigationBar()
{
resourceId = resources.GetIdentifier("navigation_bar_height", "dimen", "android");
if (!ViewConfiguration.Get(Forms.Context).HasPermanentMenuKey && resourceId > 0)
{
return resources.GetDimensionPixelSize(resourceId) / displayMatrics.Density;
}
return 0;
}

这是我在视图中添加 paddingRight 和 paddingBottom 以避开导航栏的代码。我在这里结合了一些答案,并与 isInMultiWindowMode 一起为横向定位制作了一个特殊的子句。关键是要读取 导航 _ bar _ height,但也要检查 Config _ showNavigationBar,以确保我们实际上应该使用高度。

以前的解决方案对我都不管用。这打破了比较 Display realSize显示,大小的实现,因为 RealSize给出了整个屏幕的尺寸(两个分割窗口) ,而 尺寸只给出了应用程序窗口的尺寸。将填充设置为此差异将使整个视图处于填充状态。

/** Adds padding to a view to dodge the navigation bar.


Unfortunately something like this needs to be done since there
are no attr or dimens value available to get the navigation bar
height (as of December 2016). */
public static void addNavigationBarPadding(Activity context, View v) {
Resources resources = context.getResources();
if (hasNavigationBar(resources)) {
int orientation = resources.getConfiguration().orientation;
int size = getNavigationBarSize(resources);
switch (orientation) {
case Configuration.ORIENTATION_LANDSCAPE:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N &&
context.isInMultiWindowMode()) { break; }
v.setPadding(v.getPaddingLeft(), v.getPaddingTop(),
v.getPaddingRight() + size, v.getPaddingBottom());
break;
case Configuration.ORIENTATION_PORTRAIT:
v.setPadding(v.getPaddingLeft(), v.getPaddingTop(),
v.getPaddingRight(), v.getPaddingBottom() + size);
break;
}
}
}


private static int getNavigationBarSize(Resources resources) {
int resourceId = resources.getIdentifier("navigation_bar_height",
"dimen", "android");
return resourceId > 0 ? resources.getDimensionPixelSize(resourceId) : 0;
}


private static boolean hasNavigationBar(Resources resources) {
int hasNavBarId = resources.getIdentifier("config_showNavigationBar",
"bool", "android");
return hasNavBarId > 0 && resources.getBoolean(hasNavBarId);
}

结合@egis 和其他人的回答——这在许多设备上都能很好地工作,在 Pixel EMU,Samsung S6,Sony Z3,Nexus 4上测试过。此代码使用显示尺寸来测试导航条的可用性,然后使用实际的系统导航条大小(如果存在)。

/**
* Calculates the system navigation bar size.
*/


public final class NavigationBarSize {


private final int systemNavBarHeight;
@NonNull
private final Point navBarSize;


public NavigationBarSize(@NonNull Context context) {
Resources resources = context.getResources();
int displayOrientation = resources.getConfiguration().orientation;
final String name;
switch (displayOrientation) {
case Configuration.ORIENTATION_PORTRAIT:
name = "navigation_bar_height";
break;
default:
name = "navigation_bar_height_landscape";
}
int id = resources.getIdentifier(name, "dimen", "android");
systemNavBarHeight = id > 0 ? resources.getDimensionPixelSize(id) : 0;
navBarSize = getNavigationBarSize(context);
}


public void adjustBottomPadding(@NonNull View view, @DimenRes int defaultHeight) {
int height = 0;
if (navBarSize.y > 0) {
// the device has a nav bar, get the correct size from the system
height = systemNavBarHeight;
}
if (height == 0) {
// fallback to default
height = view.getContext().getResources().getDimensionPixelSize(defaultHeight);
}
view.setPadding(0, 0, 0, height);
}


@NonNull
private static Point getNavigationBarSize(@NonNull Context context) {
Point appUsableSize = new Point();
Point realScreenSize = new Point();
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (windowManager != null) {
Display display = windowManager.getDefaultDisplay();
display.getSize(appUsableSize);
display.getRealSize(realScreenSize);
}
return new Point(realScreenSize.x - appUsableSize.x, realScreenSize.y - appUsableSize.y);
}


}

在我的例子中,我希望有这样的东西:

Screenshot

我必须遵循 @ Mdlc提出的建议,但可能要稍微简单一些(针对 只有 > = 21) :

    //kotlin
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val realSize = Point()
windowManager.defaultDisplay.getRealSize(realSize);
val usableRect = Rect()
windowManager.defaultDisplay.getRectSize(usableRect)
Toast.makeText(this, "Usable Screen: " + usableRect + " real:"+realSize, Toast.LENGTH_LONG).show()


window.decorView.setPadding(usableRect.left, usableRect.top, realSize.x - usableRect.right, realSize.y - usableRect.bottom)

它也适用于景观:

landscape

剪辑 上述解决方案在多窗口模式下不能正常工作,因为可用的矩形不仅仅因为导航栏而变小,还因为自定义窗口大小。 我注意到,在多窗口中,导航栏没有悬浮在应用程序上方,所以即使没有更改 DecorView 填充,我们的行为也是正确的:

Multi-window with no changes to decor view padding Single-window with no changes to decor view padding

请注意在这些场景中,导航栏悬停在应用程序底部的方式与其他方式的区别。 幸运的是,这很容易修复。我们可以检查应用程序是否是多窗口。下面的代码还包括计算和调整工具栏位置的部分(完整解决方案: https://stackoverflow.com/a/14213035/477790)

    // kotlin
// Let the window flow into where window decorations are
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN)
window.addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS)


// calculate where the bottom of the page should end up, considering the navigation bar (back buttons, ...)
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager
val realSize = Point()
windowManager.defaultDisplay.getRealSize(realSize);
val usableRect = Rect()
windowManager.defaultDisplay.getRectSize(usableRect)
Toast.makeText(this, "Usable Screen: " + usableRect + " real:" + realSize, Toast.LENGTH_LONG).show()


if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N || !isInMultiWindowMode) {
window.decorView.setPadding(usableRect.left, usableRect.top, realSize.x - usableRect.right, realSize.y - usableRect.bottom)
// move toolbar/appbar further down to where it should be and not to overlap with status bar
val layoutParams = ConstraintLayout.LayoutParams(appBarLayout.layoutParams as ConstraintLayout.LayoutParams)
layoutParams.topMargin = getSystemSize(Constants.statusBarHeightKey)
appBarLayout.layoutParams = layoutParams
}

三星弹出模式结果:

enter image description here

用于获取导航条高度(以像素为单位)的测试代码:

public static int getNavBarHeight(Context c) {
int resourceId = c.getResources()
.getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
return c.getResources().getDimensionPixelSize(resourceId);
}
return 0;
}

获取状态栏高度的测试代码(以像素为单位) :

public static int getStatusBarHeight(Context c) {
int resourceId = c.getResources()
.getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
return c.getResources().getDimensionPixelSize(resourceId);
}
return 0;
}

转换像素到 dp:

public static int pxToDp(int px) {
return (int) (px / Resources.getSystem().getDisplayMetrics().density);
}

在三星 S8的情况下,以上提供的方法都没有给出合适的导航栏高度,所以我使用了 KeyboardHeightProvider键盘高度提供程序。它给了我负值的高度,为了我的布局定位,我在计算中调整了那个值。

以下是 KeyboardHeightProvider.java:

import android.app.Activity;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.WindowManager.LayoutParams;
import android.widget.PopupWindow;




/**
* The keyboard height provider, this class uses a PopupWindow
* to calculate the window height when the floating keyboard is opened and closed.
*/
public class KeyboardHeightProvider extends PopupWindow {


/** The tag for logging purposes */
private final static String TAG = "sample_KeyboardHeightProvider";


/** The keyboard height observer */
private KeyboardHeightObserver observer;


/** The cached landscape height of the keyboard */
private int keyboardLandscapeHeight;


/** The cached portrait height of the keyboard */
private int keyboardPortraitHeight;


/** The view that is used to calculate the keyboard height */
private View popupView;


/** The parent view */
private View parentView;


/** The root activity that uses this KeyboardHeightProvider */
private Activity activity;


/**
* Construct a new KeyboardHeightProvider
*
* @param activity The parent activity
*/
public KeyboardHeightProvider(Activity activity) {
super(activity);
this.activity = activity;


LayoutInflater inflator = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
this.popupView = inflator.inflate(R.layout.popupwindow, null, false);
setContentView(popupView);


setSoftInputMode(LayoutParams.SOFT_INPUT_ADJUST_RESIZE | LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);


parentView = activity.findViewById(android.R.id.content);


setWidth(0);
setHeight(LayoutParams.MATCH_PARENT);


popupView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {


@Override
public void onGlobalLayout() {
if (popupView != null) {
handleOnGlobalLayout();
}
}
});
}


/**
* Start the KeyboardHeightProvider, this must be called after the onResume of the Activity.
* PopupWindows are not allowed to be registered before the onResume has finished
* of the Activity.
*/
public void start() {


if (!isShowing() && parentView.getWindowToken() != null) {
setBackgroundDrawable(new ColorDrawable(0));
showAtLocation(parentView, Gravity.NO_GRAVITY, 0, 0);
}
}


/**
* Close the keyboard height provider,
* this provider will not be used anymore.
*/
public void close() {
this.observer = null;
dismiss();
}


/**
* Set the keyboard height observer to this provider. The
* observer will be notified when the keyboard height has changed.
* For example when the keyboard is opened or closed.
*
* @param observer The observer to be added to this provider.
*/
public void setKeyboardHeightObserver(KeyboardHeightObserver observer) {
this.observer = observer;
}


/**
* Get the screen orientation
*
* @return the screen orientation
*/
private int getScreenOrientation() {
return activity.getResources().getConfiguration().orientation;
}


/**
* Popup window itself is as big as the window of the Activity.
* The keyboard can then be calculated by extracting the popup view bottom
* from the activity window height.
*/
private void handleOnGlobalLayout() {


Point screenSize = new Point();
activity.getWindowManager().getDefaultDisplay().getSize(screenSize);


Rect rect = new Rect();
popupView.getWindowVisibleDisplayFrame(rect);


// REMIND, you may like to change this using the fullscreen size of the phone
// and also using the status bar and navigation bar heights of the phone to calculate
// the keyboard height. But this worked fine on a Nexus.
int orientation = getScreenOrientation();
int keyboardHeight = screenSize.y - rect.bottom;


if (keyboardHeight == 0) {
notifyKeyboardHeightChanged(0, orientation);
}
else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
this.keyboardPortraitHeight = keyboardHeight;
notifyKeyboardHeightChanged(keyboardPortraitHeight, orientation);
}
else {
this.keyboardLandscapeHeight = keyboardHeight;
notifyKeyboardHeightChanged(keyboardLandscapeHeight, orientation);
}
}


/**
*
*/
private void notifyKeyboardHeightChanged(int height, int orientation) {
if (observer != null) {
observer.onKeyboardHeightChanged(height, orientation);
}
}


public interface KeyboardHeightObserver {
void onKeyboardHeightChanged(int height, int orientation);
}
}

返回文章页面

<?xml version="1.0" encoding="utf-8"?>
<View
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/popuplayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:orientation="horizontal"/>

MainActivity中的用法

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*


/**
* Created by nileshdeokar on 22/02/2018.
*/
class MainActivity : AppCompatActivity() , KeyboardHeightProvider.KeyboardHeightObserver  {


private lateinit var keyboardHeightProvider : KeyboardHeightProvider




override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)


keyboardHeightProvider = KeyboardHeightProvider(this)
parentActivityView.post { keyboardHeightProvider?.start() }
}


override fun onKeyboardHeightChanged(height: Int, orientation: Int) {
// In case of 18:9 - e.g. Samsung S8
// here you get the height of the navigation bar as negative value when keyboard is closed.
// and some positive integer when keyboard is opened.
}


public override fun onPause() {
super.onPause()
keyboardHeightProvider?.setKeyboardHeightObserver(null)
}


public override fun onResume() {
super.onResume()
keyboardHeightProvider?.setKeyboardHeightObserver(this)
}


public override fun onDestroy() {
super.onDestroy()
keyboardHeightProvider?.close()
}
}

如果需要进一步的帮助,您可以查看这个 给你的高级用法。

我认为更好的答案在这里,因为它允许你得到甚至剪切高度也。

获取您的根视图,并添加 setOnApplicyWindowInsetsListener (或者您可以从中覆盖 ApplicyWindowInsets) ,并从中获取 insets。

在我的相机活动中,我在底部布局中添加了与 systemBars.bottom 相等的填充。最后,它修复剪切问题。

Camera activity insets

就像这样

ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
binding.takePictureLayout.apply {
setPaddingRelative(paddingStart, paddingTop, paddingEnd, systemBars.bottom)
}
return@setOnApplyWindowInsetsListener insets
}

如果没有 Appcompat,这个:

mCameraSourcePreview.setOnApplyWindowInsetsListener((v, insets) -> { ... })

如何获取导航栏和状态栏的高度。这个代码适用于我的一些华为设备和三星设备 。 Egis 的上述解决方案是好的,但是,它仍然是不正确的一些设备。所以,我改进了它。

这是获取状态栏高度的代码

private fun getStatusBarHeight(resources: Resources): Int {
var result = 0
val resourceId = resources.getIdentifier("status_bar_height", "dimen", "android")
if (resourceId > 0) {
result = resources.getDimensionPixelSize(resourceId)
}
return result
}

此方法总是返回导航栏的高度,即使导航栏是隐藏的。

private fun getNavigationBarHeight(resources: Resources): Int {
val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android")
return if (resourceId > 0) {
resources.getDimensionPixelSize(resourceId)
} else 0
}

注意: 在 Samsung A70上,此方法返回状态栏的高度 + 导航栏的高度。 在其他设备(华为)上,它只返回导航条的高度,当导航条隐藏时返回0。

private fun getNavigationBarHeight(): Int {
val display = activity?.windowManager?.defaultDisplay
return if (display == null) {
0
} else {
val realMetrics = DisplayMetrics()
display.getRealMetrics(realMetrics)
val metrics = DisplayMetrics()
display.getMetrics(metrics)
realMetrics.heightPixels - metrics.heightPixels
}
}

这是获取导航栏和状态栏高度的代码

val metrics = DisplayMetrics()
activity?.windowManager?.defaultDisplay?.getRealMetrics(metrics)


//resources is got from activity


//NOTE: on SamSung A70, this height = height of status bar + height of Navigation bar
//On other devices (Huawei), this height = height of Navigation bar
val navigationBarHeightOrNavigationBarPlusStatusBarHeight = getNavigationBarHeight()


val statusBarHeight = getStatusBarHeight(resources)
//The method will always return the height of navigation bar even when the navigation bar was hidden.
val realNavigationBarHeight = getNavigationBarHeight(resources)


val realHeightOfStatusBarAndNavigationBar =
if (navigationBarHeightOrNavigationBarPlusStatusBarHeight == 0 || navigationBarHeightOrNavigationBarPlusStatusBarHeight < statusBarHeight) {
//Huawei: navigation bar is hidden
statusBarHeight
} else if (navigationBarHeightOrNavigationBarPlusStatusBarHeight == realNavigationBarHeight) {
//Huawei: navigation bar is visible
statusBarHeight + realNavigationBarHeight
} else if (navigationBarHeightOrNavigationBarPlusStatusBarHeight < realNavigationBarHeight) {
//SamSung A70: navigation bar is still visible but it only displays as a under line
//navigationBarHeightOrNavigationBarPlusStatusBarHeight = navigationBarHeight'(under line) + statusBarHeight
navigationBarHeightOrNavigationBarPlusStatusBarHeight
} else {
//SamSung A70: navigation bar is visible
//navigationBarHeightOrNavigationBarPlusStatusBarHeight == statusBarHeight + realNavigationBarHeight
navigationBarHeightOrNavigationBarPlusStatusBarHeight
}

简单一行解决方案

例如,正如上述许多答案所暗示的那样

仅仅获得导航栏的高度可能是不够的。我们需要考虑1。导航条存在,2。是在底部,还是在左边,3。应用程序在多窗口模式下打开。

幸运的是,通过简单地在根布局中设置 android:fitsSystemWindows="true",您可以轻松地绕过所有的长代码。Android 系统会自动为根布局添加必要的填充,以确保子视图不会进入导航栏或状态栏区域。

有一个简单的一行解决方案

android:fitsSystemWindows="true"

或者程序化

findViewById(R.id.your_root_view).setFitsSystemWindows(true);

你也可以通过

findViewById(android.R.id.content).getRootView();
or
getWindow().getDecorView().findViewById(android.R.id.content)

有关获取 root-view 的更多细节,请参考 https://stackoverflow.com/a/4488149/9640177

我已经做到了,它可以在我测试的每个设备上工作,甚至在模拟器上:

// Return the NavigationBar height in pixels if it is present, otherwise return 0
public static int getNavigationBarHeight(Activity activity) {
Rect rectangle = new Rect();
DisplayMetrics displayMetrics = new DisplayMetrics();
activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rectangle);
activity.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
return displayMetrics.heightPixels - (rectangle.top + rectangle.height());
}

2021年的新答案来拯救世界


答案:

context.navigationBarHeight

扩展得分器在哪里

val Context.navigationBarHeight: Int
get() {
val windowManager = getSystemService(Context.WINDOW_SERVICE) as WindowManager


return if (Build.VERSION.SDK_INT >= 30) {
windowManager
.currentWindowMetrics
.windowInsets
.getInsets(WindowInsets.Type.navigationBars())
.bottom


} else {
val currentDisplay = try {
display
} catch (e: NoSuchMethodError) {
windowManager.defaultDisplay
}


val appUsableSize = Point()
val realScreenSize = Point()
currentDisplay?.apply {
getSize(appUsableSize)
getRealSize(realScreenSize)
}


// navigation bar on the side
if (appUsableSize.x < realScreenSize.x) {
return realScreenSize.x - appUsableSize.x
}


// navigation bar at the bottom
return if (appUsableSize.y < realScreenSize.y) {
realScreenSize.y - appUsableSize.y
} else 0
}
}

测试对象:

  • 带导航条的模拟器
    • 像素3a (api 30)
    • 像素2(api 28)
    • 像素3(api 25)
    • 像素2(api 21)
  • Xiaomi Poco f 2专业版,附有及不附导航条(全显示)

我的版本处理剪切 + 导航栏

fun View.getCutoutRect(): Rect {
return when {
isInEditMode -> {
val cutout = context.dpToPx(16f).roundToInt()
Rect(cutout, cutout, cutout, cutout)
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M -> {
val windowInsets = (context as? AppCompatActivity)?.window?.decorView?.rootWindowInsets ?: run {
requestLayout()
return Rect()
}
val cutout = WindowInsetsCompat.toWindowInsetsCompat(windowInsets).displayCutout
val systemBars = WindowInsetsCompat.toWindowInsetsCompat(windowInsets).getInsets(WindowInsetsCompat.Type.systemBars())


Rect(
maxOf(cutout?.safeInsetLeft ?: 0, systemBars.left),
maxOf(cutout?.safeInsetTop ?: 0, systemBars.top),
maxOf(cutout?.safeInsetRight ?: 0, systemBars.right),
maxOf(cutout?.safeInsetBottom ?: 0, systemBars.bottom),
)
}
else -> {
val savedRect = (this.getTag(R.id.view_insets_tag_id) as? Rect) ?: Rect()
ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets ->
val cutout = insets.displayCutout
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
val rect = Rect(
maxOf(cutout?.safeInsetLeft ?: 0, systemBars.left),
maxOf(cutout?.safeInsetTop ?: 0, systemBars.top),
maxOf(cutout?.safeInsetRight ?: 0, systemBars.right),
maxOf(cutout?.safeInsetBottom ?: 0, systemBars.bottom),
)
this.setTag(R.id.view_insets_tag_id, rect)
if (savedRect != rect) {
requestLayout()
}
return@setOnApplyWindowInsetsListener insets
}
this.requestApplyInsets()
savedRect
}
}
}

我建议使用两个 Context 扩展来获取状态栏高度(px)和底部导航栏高度(dp)

状态栏高度(以 dp 表示)

val Context.statusBarHeightInDp
get() = run {
val resourceId = this.resources.getIdentifier(
"status_bar_height",
"dimen",
"android"
)
this.resources.getDimensionPixelSize(resourceId) / this.resources.displayMetrics.density
}

底部导航条高度(以 dp 为单位)

val Context.navBarHeightInDp
get() = run {
val resourceId = this.resources.getIdentifier(
"navigation_bar_height",
"dimen",
"android"
)
this.resources.getDimensionPixelSize(resourceId) / this.resources.displayMetrics.density
}

从 Android R (SDK 30 +)中,您可以使用此代码来获取状态栏和导航栏的大小

WindowInsets insets = activity.getWindowManager().getCurrentWindowMetrics().getWindowInsets();
int statusBarHeight = insets.getInsets(WindowInsetsCompat.Type.statusBars()).top; //in pixels
int navigationBarHeight = insets.getInsets(WindowInsetsCompat.Type.navigationBars()).bottom; //in pixels

要获得布局 XML 本身的高度(对于 clipToPaddingfalse 时回收器视图中的最后一个元素很有用) ,可以使用属性 尺寸:

android:paddingBottom="?attr/actionBarSize"