检测Android导航栏方向

24

有没有一种方法可以知道这个导航栏在横屏时会显示在哪里:

  • 在Nexus 7上,它显示在底部
  • 在Nexus 5上,它显示在右侧

输入图像描述


2
你为什么需要知道那个? - Robert Harvey
菜单按钮已经被废弃了。 - ianhanniballake
无论是 Nexus 7 还是 Nexus 5 都没有永久的 MENU 键。 - CommonsWare
所以我可以避免将交互式UI放置在返回按钮附近,例如。此外,我想知道横向和纵向可用的宽度/高度,在任何方向更改之前。 - ebtokyo
@ianhanniballake...我的错,我是在问导航栏,而不是菜单键。我已经编辑了帖子。 - ebtokyo
@RobertHarvey 这些信息在使用具有半透明导航栏的主题时非常重要,特别是当 fitsSystemWindows=true 失败时,因为它只会添加填充而不是边距。 - 0101100101
7个回答

8

部分基于Pauland的回答(又基于PhoneWindowManager的实现),以下是我当前使用的代码:

  public static boolean isSystemBarOnBottom(Context ctxt) {
    Resources res=ctxt.getResources();
    Configuration cfg=res.getConfiguration();
    DisplayMetrics dm=res.getDisplayMetrics();
    boolean canMove=(dm.widthPixels != dm.heightPixels &&
        cfg.smallestScreenWidthDp < 600);

    return(!canMove || dm.widthPixels < dm.heightPixels);
  }

这适用于运行Android 5.1的Nexus 7 2012和Nexus 4。

在具有永久MENU键的设备上,没有系统栏。根据您的使用情况,您可能需要检查这种情况:

ViewConfiguration.get(ctxt).hasPermanentMenuKey()

(其中ctxt是某个Context)

个人而言,我使用这个方法来尝试使滑动面板与系统栏相反方向,因为在带有系统栏的侧边触发边缘划痕比较困难。对于任何关键任务,我不会使用此方法或任何其他算法(如依赖于getDecorView()的算法)。


嗨,CommonsWare。这个解决方案对导航栏也适用吗?另外,您能否提供一些关于为什么需要canMove的上下文信息? - sudocoder
@sudocoder:“这个解决方案对导航栏也适用吗?”--我不确定你所考虑的“导航栏”是什么。自从Honeycomb以来,这个术语就没有被使用过了,如果我没记错的话。“为什么需要canMove?”--因为在某些设备和操作系统版本上,系统栏会改变位置。 - CommonsWare
“导航栏”是指 OP 提供的图片中红色箭头所指的部分。我会花一些时间来解析“(!canMove || dm.widthPixels < dm.heightPixels);”,因为它不是立即直观的为什么会起作用。感谢 Murphy 先生的帮助 :) - sudocoder
1
@sudocoder:“我的意思是OP提供的图片中红箭头指向的是什么”--那就是系统栏。“为什么这样做不是立即直观的”--“它能工作”的唯一原因是PhoneWindowManager使用它来决定在哪里放置系统栏。如果PhoneWindowManager发生变化,isSystemBarOnBottom()也必须更改。这都是一个hack,但我们只有这个hack。 - CommonsWare
使用DisplayMetrics对我来说并不总是有效的。当我启动我的Activity时,DisplayMetrics可能会显示错误方向的宽度/高度(手机处于横向模式,但显示纵向的宽度和高度),从而错误地确定SystemBar是否在底部。因此,我使用了https://dev59.com/hnNA5IYBdhLWcg3wUL5Y#1016941。 - sudocoder
1
@CommonsWare,我不太同意这个术语。正如PhoneWindowManager所声明的那样,那些“返回、主页、最近使用的应用”按钮是导航栏:WindowState mStatusBar = null; WindowState mNavigationBar = null; 此外,即使是导航栏视图的布局文件也被称为navigation_bar.xml - Dmitry Gryazin

6
我的解决方案
public static boolean hasNavBar (Resources resources)
{
    int id = resources.getIdentifier("config_showNavigationBar", "bool", "android");
    if (id > 0)
        return resources.getBoolean(id);
    else
        return false;
}

public static int getNavigationBarHeight (Resources resources)
{
    if (!Utils.hasNavBar(resources))
        return 0;

    int orientation = resources.getConfiguration().orientation;

    //Only phone between 0-599 has navigationbar can move
    boolean isSmartphone = resources.getConfiguration().smallestScreenWidthDp < 600;
    if (isSmartphone && Configuration.ORIENTATION_LANDSCAPE == orientation)
        return 0;

    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;
}

public static int getNavigationBarWidth (Resources resources)
{
    if (!Utils.hasNavBar(resources))
        return 0;

    int orientation = resources.getConfiguration().orientation;

    //Only phone between 0-599 has navigationbar can move
    boolean isSmartphone = resources.getConfiguration().smallestScreenWidthDp < 600;

    if (orientation == Configuration.ORIENTATION_LANDSCAPE && isSmartphone)
    {
        int id = resources.getIdentifier("navigation_bar_width", "dimen", "android");
        if (id > 0)
            return resources.getDimensionPixelSize(id);
    }

    return 0;
}

基于解决方案:https://android.googlesource.com/platform/frameworks/base/+/9f65c4c34abb07bdda54649ed510af26f16e9c1b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java

6

通过使用装饰视图的属性与当前DisplayMetrics相结合,您可以找出导航栏位于哪一侧。

// retrieve the position of the DecorView
Rect visibleFrame = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(visibleFrame);

DisplayMetrics dm = getResources().getDisplayMetrics();
// check if the DecorView takes the whole screen vertically or horizontally
boolean isRightOfContent = dm.heightPixels == visibleFrame.bottom;
boolean isBelowContent   = dm.widthPixels  == visibleFrame.right;

如果软键盘正在显示,这可能无法正常工作。键盘的高度将从显示框架中减去。 - Maik
3
在 Nexus 5 上,isRightOfContent 和 isBelowContent 都为 true。 - support_ms

3

对我有效的解决方案是:

public static boolean hasNavBar(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Point realPoint = new Point();
        Display display = wm.getDefaultDisplay();
        display.getRealSize(realPoint);
        DisplayMetrics metrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(metrics);
        return metrics.heightPixels + metrics.widthPixels != realPoint.y + realPoint.x;
    }

    public static boolean isSystemBarOnBottom(Context context) {
        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
        Point realPoint = new Point();
        Display display = wm.getDefaultDisplay();
        display.getRealSize(realPoint);
        DisplayMetrics metrics = new DisplayMetrics();
        wm.getDefaultDisplay().getMetrics(metrics);
        Configuration cfg = context.getResources().getConfiguration();
        boolean canMove = (metrics.widthPixels != metrics.heightPixels &&
                cfg.smallestScreenWidthDp < 600);

        return (!canMove || metrics.widthPixels < metrics.heightPixels);
    }

2

所有现有的答案都无法与FLAG_LAYOUT_NO_LIMITS一起使用。我找到了一个新的解决方案:

@Override
    public void onResume() {
        super.onResume();

        ViewCompat.setOnApplyWindowInsetsListener(requireActivity().getWindow().getDecorView(), (v, insets) -> {    
            int sysbottom = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom;
            int systop = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top;
            int sysleft = insets.getInsets(WindowInsetsCompat.Type.systemBars()).left;
            int sysright = insets.getInsets(WindowInsetsCompat.Type.systemBars()).right;

            return insets;
        });
    }

这里有sysbottom、systop、sysleft和sysright,它们是系统栏在导航栏和状态栏中的缩进值(以像素为单位)。

1
这适用于我的应用程序,我正在使用它与半透明状态栏和导航栏一起设置填充。
boolean navBarOnTheBottom(){
        DisplayMetrics displaymetrics = new DisplayMetrics();
        getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
        int viewHeight = displaymetrics.heightPixels;
        if (bkg.getHeight() == viewHeight)
        {
            Log.d(TAG, "nav bar on the side");
            return false;
        }
        else{
            Log.d(TAG, "nav bar on the bottom");
            return true;
        }
    }
bkg是包含所有应用程序视图的主要LinearLayout。 请确保bkg.getHeight()不会返回0,因为在某些布局中它会返回0。 编辑: 如果上面的方法返回0,请使用以下方法获取布局高度。
@Override
    public void onWindowFocusChanged (boolean hasFocus) {
        // the height will be set at this point
        bkgHeight = bkg.getHeight();
    }

1
请注意,从Android 7.1开始,导航栏也可能位于屏幕的左侧。为了确保您始终知道导航栏的位置,请使用DisplayListener在Android 7.1及以上版本上检查180°屏幕更改,例如:如何检测从横向到横向方向的屏幕旋转180度?
我一直成功且可靠地使用此代码来确定导航栏位置的任何更改:
DisplayManager.DisplayListener displayListener;

@Override
protected void onCreate(Bundle savedInstanceState) {

    ...

    displayListener = new DisplayManager.DisplayListener() {
        private int lastRotation =
                ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
                                           .getDefaultDisplay().getRotation();

        @Override
        public void onDisplayAdded(int displayId) {
        }

        @Override
        public void onDisplayChanged(int displayId) {
            int rotation = ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
                                   .getDefaultDisplay().getRotation();
            if (rotation == Surface.ROTATION_90
                        && lastRotation == Surface.ROTATION_270
                    || rotation == Surface.ROTATION_270
                        && lastRotation == Surface.ROTATION_90) {
                onNavigationBarMoved(true);
            }
            lastRotation = rotation;
        }

        @Override
        public void onDisplayRemoved(int displayId) {
        }
    };
    DisplayManager displayManager =
            (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
    displayManager.registerDisplayListener(displayListener, null);
    onNavigationBarMoved(false);
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    onNavigationBarMoved(false);
}

@Override
protected void onDestroy() {
    if (displayListener != null) {
        DisplayManager displayManager =
                (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);
        displayManager.unregisterDisplayListener(displayListener);
    }
    super.onDestroy();
}

protected void onNavigationBarMoved(boolean landscapeScreenRotation) {
    boolean leftSideNavigationBar = Build.VERSION.SDK_INT > Build.VERSION_CODES.N
                         && ((WindowManager) getSystemService(Context.WINDOW_SERVICE))
                                .getDefaultDisplay().getRotation() == Surface.ROTATION_270;
    // add whatever else you need here
}

请记住,当设备已经旋转了180度时,活动不会重新创建,因此在 onNavigationBarMoved 中,landscapeScreenRotation 的值将告诉您何时发生这种情况,以便您相应地调整UI。

在华为P30 Pro上,当Surface.ROTATION_90和Surface.ROTATION_270时,导航栏位于右侧。 - woltran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接