Android - 弹出菜单项显示在导航栏下方

Android - Popup menu items appear under navigation bar

所以这个问题已经让我发疯了很长一段时间,我找不到任何解决方案。基本上我有一个对话框片段,顶部有两个浮动操作按钮。单击右边的那个,创建这个弹出菜单。到目前为止,一切都很好。但是正如您从图片中看到的那样,弹出菜单打破了导致导航栏出现的沉浸式模式,并且由于某种原因,菜单中的最后一个元素没有被剪裁并绘制在它下面。这显然是一个问题,因为如果您尝试单击它,您最终会与导航栏进行交互。

这是我的按钮和弹出菜单的代码:

        notificationSchedule.setOnClickListener(v -> {
            PopupMenu notificationMenu = new PopupMenu(requireActivity(), v);
            notificationMenu.inflate(R.menu.notification_changer);

            SpannableString spannableString1 = new SpannableString(notificationMenu.getMenu().getItem(databaseNotification.getIndex1()).getTitle());
            spannableString1.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorPrimary)), 0, spannableString1.length(), 0);
            notificationMenu.getMenu().getItem(databaseNotification.getIndex1()).setTitle(spannableString1);

            if (notificationMenu.getMenu().getItem(databaseNotification.getIndex1()).getSubMenu() != null) {
                spannableString1 = new SpannableString(notificationMenu.getMenu().getItem(databaseNotification.getIndex1()).getSubMenu().getItem(databaseNotification.getIndex2()).getTitle());
                spannableString1.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorPrimary)), 0, spannableString1.length(), 0);
                notificationMenu.getMenu().getItem(databaseNotification.getIndex1()).getSubMenu().getItem(databaseNotification.getIndex2()).setTitle(spannableString1);
            }

            notificationMenu.show();

            notificationMenu.setOnMenuItemClickListener(item -> {
                boolean value = managePopupMenuClick(-1, item);

                if (notificationMenu.getMenu().findItem(item.getItemId()).getSubMenu() != null) {
                    SpannableString spannableString2 = new SpannableString(notificationMenu.getMenu().findItem(item.getItemId()).getTitle() + " +");
                    spannableString2.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorPrimary)), 0, spannableString2.length(), 0);
                    notificationMenu.getMenu().findItem(item.getItemId()).getSubMenu().setHeaderTitle(spannableString2);
                }

                return value;
            });
        });

到目前为止我已经尝试过:

1. 我最接近解决这个问题的是我的 onCreateView() 方法中的代码和平:

dialogWindow.getDecorView().setOnSystemUiVisibilityChangeListener(visibility -> {
        if (visibility == 4) dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
});

根据我的测试,这解决了问题,但在极少数设备上。

2. 我尝试在显示弹出菜单后立即隐藏导航栏,但效果不佳。

3.我搜索了一种在显示弹出菜单的同时保持身临其境模式的方法,但找不到方法。

4. 我还搜索了一种方法来尝试限制弹出菜单中可见项目的数量。例如,如您所见,此弹出菜单中有 11 个项目。例如,如果有一种方法可以将可见项的数量限制为 4,然后滚动查看其他项也可以修复,但我再次找不到方法。

如有任何建议,我们将不胜感激!

  1. 弹出菜单打破了导致导航栏出现的沉浸式模式。

我认为这是 Android UI 的预期规范。弹出菜单时,您的 window 失去焦点,隐藏的导航栏重新出现。

  1. 由于某种原因,菜单中的最后一个元素没有被剪裁,而是绘制在其下方。这显然是一个问题,因为如果您尝试单击它,您最终会与导航栏进行交互。

导航栏位于弹出窗口上方。我再次认为这是有意的。例如,您可以导航回之前的状态(关闭弹出窗口等)。

您可以手动向上滚动弹出菜单,避免这种重叠状态。

免责声明

This is not a direct solution to the problem, but you can use it as a last resort.

如果您注意到弹出菜单向屏幕底部下拉,原因是锚视图位于屏幕的上半部分。

如果您将锚点更改为底部的其他视图(可能需要创建一些不可见视图),则弹出菜单将向屏幕顶部打开;在这种情况下,它永远不会与底部导航栏相交。

我想我终于知道发生了什么事了。有几件事需要返工。因此,对于遇到此问题的任何其他人,请尝试以下操作:

1.我有一种隐藏系统的方法UI和一种显示它的方法。

    private void hideSystemUI() {
        dialogWindow.getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_FULLSCREEN
                        | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
    }
    private void showSystemUI() {
        dialogWindow.getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
    }

注意 1:原来是标志 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION 导致了这个问题,您可以从标志描述中读到.

2. 我最终从 onCreateView():

中删除了这段代码
dialogWindow.getDecorView().setOnSystemUiVisibilityChangeListener(visibility -> {
        if (visibility == 4) dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
});

注意 2: 但是再次证明,需要清除这些标志。所以这段代码dialogWindow.clearFlags(...);就不得不用了

所以我的 onClickListener() 的最终版本看起来像这样(在每个注释块下面都有代码更改):

        notificationSchedule.setOnClickListener(v -> {
            // Show the system UI before creating the popup menu
            // but without the View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION flag !!!
            dialogWindow.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
            // Instead of having a setOnSystemUiVisibilityChangeListener() in the onCreateView()
            // clear the flags here !!!
            dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);

            // I also ended up using another constructor for the popup menu because I wanted
            // to use the R.attr.actionOverflowMenuStyle
            // This is not needed, but if you do want to use it know that it requires API 22
            PopupMenu notificationMenu;
            
            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
                notificationMenu = new PopupMenu(requireActivity(), v, Gravity.NO_GRAVITY, R.attr.actionOverflowMenuStyle, 0);
            } else {
                notificationMenu = new PopupMenu(requireActivity(), v);
            }

            notificationMenu.inflate(R.menu.notification_changer);

            SpannableString spannableString1 = new SpannableString(notificationMenu.getMenu().getItem(databaseNotification.getIndex1()).getTitle());
            spannableString1.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorPrimary)), 0, spannableString1.length(), 0);
            notificationMenu.getMenu().getItem(databaseNotification.getIndex1()).setTitle(spannableString1);

            if (notificationMenu.getMenu().getItem(databaseNotification.getIndex1()).getSubMenu() != null) {
                spannableString1 = new SpannableString(notificationMenu.getMenu().getItem(databaseNotification.getIndex1()).getSubMenu().getItem(databaseNotification.getIndex2()).getTitle());
                spannableString1.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorPrimary)), 0, spannableString1.length(), 0);
                notificationMenu.getMenu().getItem(databaseNotification.getIndex1()).getSubMenu().getItem(databaseNotification.getIndex2()).setTitle(spannableString1);
            }

            notificationMenu.show();

            notificationMenu.setOnMenuItemClickListener(item -> {
                boolean value = managePopupMenuClick(-1, item);

                if (notificationMenu.getMenu().findItem(item.getItemId()).getSubMenu() != null) {
                    SpannableString spannableString2 = new SpannableString(notificationMenu.getMenu().findItem(item.getItemId()).getTitle() + " +");
                    spannableString2.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorPrimary)), 0, spannableString2.length(), 0);
                    notificationMenu.getMenu().findItem(item.getItemId()).getSubMenu().setHeaderTitle(spannableString2);
                }

                return value;
            });

            // Finally add a onDismissListener() and hide the system UI inside it
            notificationMenu.setOnDismissListener(menu -> hideSystemUI());
        });