android 判断布局上是或否存在,Android 判断虚拟导航栏是否存在

博客介绍了在全面屏手机出现前后,判断Android虚拟导航栏是否存在的多种方法。全面屏手机打破了物理与虚拟导航键对立格局,使老方法失效。网上新的间接判断法有缺陷,最后提出进入Android系统层面,通过分析布局层级和监听窗口插入事件来判断的新方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

判断虚拟导航栏的老方法

在全面屏手机之前,我们对虚拟导航栏的判断就有很多种方法,

比如方法1:

{

// 判断系统是否写入了关于定义虚拟导航栏的高度相关变量。

//如果高度大于0,则表示该手机有虚拟导航栏

Resources res = activity.getResources();

int resourceId = res.getIdentifier("status_bar_height", "dimen", "android");

if (resourceId > 0) {

return res.getDimensionPixelSize(resourceId)>0;

}

}

又或者是这种方法2:

{

int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");

// 判断系统是否写入了关于是否显示虚拟导航栏的相关变量,如果为true,表示有虚拟导航栏

return id > 0 && resources.getBoolean(id);

}

又或者方法3:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {

Display display = context.getWindowManager().getDefaultDisplay();

Point size = new Point();

Point realSize = new Point();

display.getSize(size); // app绘制区域

display.getRealSize(realSize);

return realSize.y != size.y;

} else {

boolean menu = ViewConfiguration.get(context).hasPermanentMenuKey();

boolean back = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK);// 判断是否存在物理按键

if (menu || back) {

return false;

} else {

return true;

}

}

以上三个方法,基本上都是看系统中是否有虚拟导航栏的相关定义,即如果我们能发现系统中由虚拟导航栏相关的定义,就认定虚拟导航栏存在。这个思考方式源于手机的物理导航键和虚拟导航键一直以来都是对立存在的,即去掉了物理导航键,那么就会使用虚拟导航栏,如果存在虚拟导航栏,那么就没有物理按键。有了A就没有B,如果存在了B,那就没有A。在这种前提下,那种思考方式不会有什么问题。

然而全面屏手机打破了这种对立存在的格局,去掉了物理导航键,但同时也隐去了虚拟导航栏(即手机确实集成了虚拟导航栏,但是没有使用),取而代之的是通过全面屏手势实现三个按键的功能。所以说,全面屏手机+全面屏手势。是导致以往判断方法失效的原因。

fa08170a846c

回过头想,导致判断失效更本质的原因,其实是因为我们的判断方法都是间接判断,是去寻找必要条件,而非充分条件,就好比我们在夜晚看到了月亮的光芒,并不能证明月亮是自发光的物体,除非假设一个前提:能发光的物体都是自发光的。证明才能成立。而全面屏的到来,正好打破了这个前提,导致了我们的推导出了问题。

现在,由于全面屏手机里一般都存在虚拟导航栏和全面屏手势这两中操作方式,且二者必取其一,因此,网上就又出现了另一种间接判断法,即判断当前手机是否在用全面屏手势,如果否,则表示在用虚拟导航栏。

以下是针对vivo,小米的全面屏虚拟导航栏的判断方法:

/**

* @returnv false 表示使用的是虚拟导航键(NavigationBar), true 表示使用的是手势, 默认是false

*/

public static boolean vivoNavigationGestureEnabled(Context context) {

int val = Settings.Secure.getInt(context.getContentResolver(), NAVIGATION_GESTURE, NAVIGATION_GESTURE_OFF);

return val != NAVIGATION_GESTURE_OFF;

}

public static boolean isXiaoMiNavigationBarShow(Activity context) {

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {

if (Settings.Global.getInt(context.getContentResolver(), "force_fsg_nav_bar", 0) != 0) {

//开启手势,不显示虚拟键

return false;

}

}

}

但是这种方法也有一些缺陷,比如,判断方法都是厂商方面给出的,也就是说没有通用性,还有其他厂商系统判断方法未知;而且,这种方法很难判断那些可隐藏/呼出的虚拟导航栏。更重要的是,通过必要条件做间接判断始终是有隐患的。

新的解决方案

因此,为了寻找一个更加通用,准确的判断方法,我们尝试进入Android系统层面去尝试寻找判断虚拟导航栏的方案。

虚拟导航栏也是一个View,如果这个View绘制了自己,并显示在Window布局中,那么虚拟导航栏就一定存在。也就是说,我们只要找到这个View,并证明它是否存在即可。

于是我们尝试通过Layout Inspector分析了虚拟导航栏的布局层级,发现它是DecorView的Child View(Android5.0以上是这样),同时我们在DecorView中找到了代表虚拟导航栏的View,那么,接下来的问题就很简单了咯。代码如下:

{

private static final String NAVIGATION= "navigationBarBackground";

// 该方法需要在View完全被绘制出来之后调用,否则判断不了

//在比如 onWindowFocusChanged()方法中可以得到正确的结果

public static boolean isNavigationBarExist(@NonNull Activity activity){

ViewGroup vp = (ViewGroup) activity.getWindow().getDecorView();

if (vp != null) {

for (int i = 0; i < vp.getChildCount(); i++) {

vp.getChildAt(i).getContext().getPackageName();

if (vp.getChildAt(i).getId()!= NO_ID && NAVIGATION.equals(activity.getResources().getResourceEntryName(vp.getChildAt(i).getId()))) {

return true;

}

}

}

return false;

}

}

当然,还有一种判断方案,也很好。

public static void isNavigationBarExist(Activity activity, final OnNavigationStateListener onNavigationStateListener) {

if (activity == null) {

return;

}

final int height = getNavigationHeight(activity);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {

activity.getWindow().getDecorView().setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {

@Override

public WindowInsets onApplyWindowInsets(View v, WindowInsets windowInsets) {

boolean isShowing = false;

int b = 0;

if (windowInsets != null) {

b = windowInsets.getSystemWindowInsetBottom();

isShowing = (b == height);

}

if (onNavigationStateListener != null && b <= height) {

onNavigationStateListener.onNavigationState(isShowing, b);

}

return windowInsets;

}

});

}

}

public static int getNavigationHeight(Context activity) {

if (activity == null) {

return 0;

}

Resources resources = activity.getResources();

int resourceId = resources.getIdentifier("navigation_bar_height",

"dimen", "android");

int height = 0;

if (resourceId > 0) {

//获取NavigationBar的高度

height = resources.getDimensionPixelSize(resourceId);

}

return height;

}

<think>我们参考用户提供的引用内容,总结获取底部导航栏高度和判断存在的方法如下: ### 判断底部导航栏是否存在 引用[1]提供了一种通过检查`DecorView`中是否存在`navigationBarBackground`视图来判断导航栏是否存在的方法: ```java public static boolean isNavigationBarExist(Activity activity) { ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); for (int i = 0; i < decorView.getChildCount(); i++) { View child = decorView.getChildAt(i); int id = child.getId(); if (id != View.NO_ID) { String resourceEntryName = activity.getResources().getResourceEntryName(id); if ("navigationBarBackground".equals(resourceEntryName)) { return true; } } } return false; } ``` ### 获取底部导航栏高度 引用[2]和[3]提供了获取导航栏高度的方法: 1. 通过资源ID获取导航栏高度(如果存在) 2. 需要先判断导航栏是否显示(例如,是否在显示状态) ```java public static int getNavigationBarHeight(Context context) { if (context == null) return 0; Resources resources = context.getResources(); int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); if (resourceId > 0) { // 获取导航栏高度 int height = resources.getDimensionPixelSize(resourceId); // 但是,即使有高度,也可能因为导航栏当前没有显示而返回0(者我们需要判断是否显示) // 所以,通常我们会结合导航栏是否显示来判断 if (isNavigationBarShowing(context)) { return height; } } return 0; } ``` ### 判断导航栏是否显示 引用[2]提到需要判断导航栏是否显示。我们可以通过检查设备是否有物理按键(菜单键、返回键)以及当前导航栏是否可见来判断。但更直接的方法是检查窗口的可见性标志: ```java public static boolean isNavigationBarShowing(Context context) { // 检查设备是否有物理按键(老式设备) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { if (context instanceof Activity) { Activity activity = (Activity) context; View decorView = activity.getWindow().getDecorView(); // 检查窗口的可见性标志 int uiOptions = decorView.getSystemUiVisibility(); // 判断导航栏是否被隐藏 if ((uiOptions & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0) { return false; } } } // 检查屏幕底部是否导航栏(通过可用区域和屏幕高度的比较) Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); Point realSize = new Point(); display.getRealSize(realSize); // 包含导航栏的屏幕高度 Point availableSize = new Point(); display.getSize(availableSize); // 可用区域(不包括导航栏) // 如果可用区域的高度小于实际高度,说明导航栏存在并且显示 return availableSize.y < realSize.y; } ``` ### 综合方法 我们可以将上述方法结合起来: 1. 首先判断导航栏是否存在(通过检查`navigationBarBackground`视图) 2. 如果存在,再判断当前是否显示(通过可用区域和实际区域的比较,者系统UI标志) 3. 如果显示,则返回导航栏高度;则返回0 注意:不同设备可能有不同的实现,因此需要测试。 ### 示例代码 以下是一个综合示例: ```java public class NavigationBarUtils { // 判断导航栏是否存在 public static boolean isNavigationBarExist(Activity activity) { ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); for (int i = 0; i < decorView.getChildCount(); i++) { View child = decorView.getChildAt(i); int id = child.getId(); if (id != View.NO_ID) { String resourceEntryName = activity.getResources().getResourceEntryName(id); if ("navigationBarBackground".equals(resourceEntryName)) { return true; } } } return false; } // 获取导航栏高度(如果存在且显示) public static int getNavigationBarHeight(Context context) { if (context == null) return 0; Resources resources = context.getResources(); int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android"); if (resourceId > 0) { // 获取导航栏高度 int height = resources.getDimensionPixelSize(resourceId); // 判断导航栏是否显示 if (isNavigationBarShowing(context)) { return height; } } return 0; } // 判断导航栏是否显示 public static boolean isNavigationBarShowing(Context context) { if (context instanceof Activity) { Activity activity = (Activity) context; // 检查窗口的可见性标志 View decorView = activity.getWindow().getDecorView(); int uiOptions = decorView.getSystemUiVisibility(); // 如果设置了隐藏导航栏的标志,则返回false if ((uiOptions & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0) { return false; } // 通过比较实际屏幕尺寸和可用屏幕尺寸来判断 Display display = activity.getWindowManager().getDefaultDisplay(); Point realSize = new Point(); display.getRealSize(realSize); // 需要API 17+ Point availableSize = new Point(); display.getSize(availableSize); // 如果可用高度小于实际高度,说明导航栏显示 return availableSize.y < realSize.y; } return false; } } ``` ### 注意事项 1. `getRealSize()`方法需要API级别17(Android 4.2)及以上。 2. 在全面屏设备上,导航栏可能以手势操作代替,但仍然会占用空间(通常为底部一条横线),此时导航栏高度可能不为0。 3. 有些设备可能没有标准的导航栏,而是使用物理按键,这种情况下导航栏高度为0。 ### 相关问题 1. 如何适配全面屏手机的底部导航栏?[^1] 2. 在沉浸式模式下如何正确处理导航栏?[^2] 3. 如何监听底部导航栏的显示和隐藏事件?[^3] 4. 如何动态调整布局以避免底部导航栏遮挡?[^1] 5. 不同厂商(如华为、小米)的导航栏获取方法是否有差异?[^2] [^1]: 引用布局设置 [^2]: 引用 BottomNavigationView 使用 [^3]: 引用 Gravity 属性设置</think>### Android 获取底部导航栏高度及判断存在的方法 #### 一、判断底部导航栏是否存在 1. **通过系统资源标识符判断**(推荐) ```java public static boolean isNavigationBarShowing(Context context) { // 检查设备是否支持导航栏(物理按键设备可能没有) boolean hasMenuKey = ViewConfiguration.get(context).hasPermanentMenuKey(); boolean hasBackKey = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK); if (hasMenuKey || hasBackKey) { // 设备有物理按键,可能没有虚拟导航栏 return false; } // 通过系统资源检查 int id = context.getResources().getIdentifier( "config_showNavigationBar", "bool", "android" ); return (id > 0) && context.getResources().getBoolean(id); } ``` 2. **通过 DecorView 检查**(需在视图绘制完成后调用) ```java public static boolean isNavigationBarExist(Activity activity) { ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView(); for (int i = 0; i < decorView.getChildCount(); i++) { View child = decorView.getChildAt(i); int id = child.getId(); if (id != View.NO_ID) { String entryName = activity.getResources().getResourceEntryName(id); if ("navigationBarBackground".equals(entryName)) { return true; } } } return false; } ``` #### 二、获取导航栏高度 ```java public static int getNavigationBarHeight(Context context) { if (!isNavigationBarShowing(context)) return 0; Resources resources = context.getResources(); int resourceId = resources.getIdentifier( "navigation_bar_height", "dimen", "android" ); return (resourceId > 0) ? resources.getDimensionPixelSize(resourceId) : 0; } ``` #### 三、完整工具类实现 ```java public class NavigationBarUtils { // 判断导航栏是否存在 public static boolean isNavigationBarShowing(Context context) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)) .getDefaultDisplay(); Point realSize = new Point(); Point availableSize = new Point(); display.getRealSize(realSize); // 包含导航栏的屏幕尺寸 display.getSize(availableSize); // 可用区域尺寸 return realSize.y != availableSize.y; } return false; } // 获取导航栏高度 public static int getNavigationBarHeight(Context context) { Resources resources = context.getResources(); int resourceId = resources.getIdentifier( "navigation_bar_height", "dimen", "android" ); if (resourceId > 0 && isNavigationBarShowing(context)) { return resources.getDimensionPixelSize(resourceId); } return 0; } // 检查设备是否有物理按键 private static boolean hasPhysicalButtons(Context context) { return ViewConfiguration.get(context).hasPermanentMenuKey() || KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_BACK); } } ``` #### 四、使用示例 ```java // 在 Activity 中使用 public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 检查导航栏是否存在 boolean isNavBarExist = NavigationBarUtils.isNavigationBarShowing(this); // 获取导航栏高度(单位:像素) int navBarHeight = NavigationBarUtils.getNavigationBarHeight(this); Log.d("NavigationBar", "Exists: " + isNavBarExist); Log.d("NavigationBar", "Height: " + navBarHeight + "px"); } } ``` #### 五、注意事项 1. **时机问题**:需要在视图绘制完成后调用(如`onWindowFocusChanged()`中) ```java @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { int height = NavigationBarUtils.getNavigationBarHeight(this); } } ``` 2. **全面屏适配**:全面屏设备的手势导航栏高度通常为: ```java // 手势导航栏高度(约24dp-34dp) int gestureHeight = resources.getDimensionPixelSize( resources.getIdentifier("navigation_bar_gesture_height", "dimen", "android") ); ``` 3. **横屏处理**:横屏时导航栏可能出现在右侧: ```java boolean isVertical = resources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT; ``` #### 六、兼容性处理 ```java // 兼容旧版 Android(API < 17) public static Point getScreenRealSize(WindowManager wm) { Point point = new Point(); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { wm.getDefaultDisplay().getRealSize(point); } else { wm.getDefaultDisplay().getSize(point); } return point; } ``` --- ### 相关问题 1. 如何适配全面屏手机的底部安全区域?[^1] 2. 沉浸式模式下如何处理底部导航栏?[^2] 3. 如何动态调整布局避免被导航栏遮挡?[^1] 4. 不同厂商(华为/小米)的导航栏获取方法是否一致?[^3] 5. 如何监听底部导航栏的显示/隐藏事件?[^2] [^1]: 引用布局设置 [^2]: 引用 BottomNavigationView 使用 [^3]: 引用 Gravity 属性设置
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值