WindowInsets.getDisplayCutout在全屏Java应用程序的onAttachedToWindow内都为NULL

问题描述 投票:0回答:1

我在全屏Java应用程序中遇到getDisplayCutout()的问题。我似乎只能在onAttachedToWindow函数中获取DisplayCutout的值。该功能完成后,我再也无法获得它。获取抠图的代码:

WindowInsets insets = myActivity.getWindow().getDecorView().getRootWindowInsets();
if (insets != null) {
    DisplayCutout displayCutout = insets.getDisplayCutout();
    if (displayCutout != null && displayCutout.getBoundingRects().size() > 0) {
        // we have cutouts to deal with
    }
}

问题

一旦附加到视图层次结构,如何随时随地从代码中的任何位置可靠地获取显示切口?

复制问题

[经过大量调查,我将其缩小为全屏应用程序的一个非常广泛的问题,令我惊讶的是,没有人问到这个问题。实际上,我们可以忽略我的应用程序,而只处理两个模板项目,您现在就可以自行制作。

[在Android Studio中,我谈论的是名为“基本活动”和“全屏活动”的电话和平板电脑项目。如果您分别创建一个,然后进行以下更改:

对于Basic,通过在Activity标签下添加android:configChanges="orientation|keyboardHidden|screenSize"来更改清单以处理配置更改,如下所示:

<activity
    android:name=".MainActivity"
    android:label="@string/app_name"
    android:configChanges="orientation|keyboardHidden|screenSize"
    android:theme="@style/AppTheme.NoActionBar">

现在同时为它们两个,将以下两个功能添加到活动文件中:

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    WindowInsets insets = getWindow().getDecorView().getRootWindowInsets();
    if (insets != null) {
        DisplayCutout displayCutout = insets.getDisplayCutout();
        if (displayCutout != null && displayCutout.getBoundingRects().size() > 0) {
            // we have cutouts to deal with
        }
    }
}

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);
    WindowInsets insets = getWindow().getDecorView().getRootWindowInsets();
    if (insets != null) {
        DisplayCutout displayCutout = insets.getDisplayCutout();
        if (displayCutout != null && displayCutout.getBoundingRects().size() > 0) {
            // we have cutouts to deal with
        }
    }
}

这是重现此问题所需要做的一切。我在API Q的Pixel 3模拟器上运行了该模拟器,在该模拟器上启用了模拟抠图并选择了BOTH(因此底部和顶部都有一个抠图)

现在,如果您在我们尝试获取显示切口(DisplayCutout displayCutout = insets.getDisplayCutout();)的行上设置断点,则会在Basic应用程序上看到它,当您更改方向时,它可以在启动and上使用,但在全屏模式下应用程序,仅在启动时有效。

实际上,在我的应用程序中,我已经通过在onAttachedToWindow中使用以下代码进行了测试:

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    // start a thread so that UI thread execution can continue
    WorkerThreadManager.StartWork(new WorkerCallback() {
        @Override
        public void StartWorkSafe() {
            // run the following code on the UI thread
            XPlatUtil.RunOnUiThread(new SafeRunnable() {
                public synchronized void RunSafe() {
                    WindowInsets insets = myActivity.getWindow().getDecorView().getRootWindowInsets();
                    if (insets != null) {
                        DisplayCutout displayCutout = insets.getDisplayCutout();
                        if (displayCutout != null && displayCutout.getBoundingRects().size() > 0) {
                            // we have cutouts to deal with
                        }
                    }
                }
            });
        }
    });
}

此代码启动一个线程,以便onAttachedToWindow函数可以完成运行;但线程会立即将执行发送回UI线程以检查切口。

onAttachedToWindow函数执行完成与我的代码对displayCutout进行检查之间的延迟必须在纳秒级的数量级,但是该剪切块立即不可用。

有什么想法吗?这是某种期望吗?

[方向更改时无法访问切口,我无权求助,只能记录最大的插图(纵向的顶部或底部,因为长边无法容纳),并将其应用于纵向的顶部和底部或左侧就在风景中。

这是因为我无法在android中找到一种方法来检查当前处于活动状态的风景(例如,手机的顶部在左侧还是右侧)。如果可以检查,至少可以将插图套在需要的手机边缘。

java android null fullscreen display-cutouts
1个回答
0
投票

我想出了一堆可以帮助其他人的东西。首先,我将回答原始问题,然后,我将解释为什么它起作用,并为那些同样遇到相同问题的人们提供一些替代方案: app。注意:我所有的布局都是以编程方式完成的(我的布局目录中甚至没有任何xml文件)。这样做是出于跨平台的原因,也许这就是为什么我遇到这个问题而其他人则没有。

OP回答使用以下代码获取显示切口(带有适当的空检查):

<activity>.getWindowManager().getDefaultDisplay().getCutout();

在我的测试中,这在onConfigurationChanged中返回了正确的切口,并且在onAttachedToWindow外部调用时(当然有切口)时为

not null

解释getWindow().getDecorView().getRootWindowInsets().getDisplayCutout()在应用程序全屏显示时,如果在onAttachedToWindow函数外部访问,则似乎始终为NULL;我没有找到解决方法。如果您通过执行以下操作将应用从全屏模式移开:

rootView.setSystemUiVisibility(0 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);

onAttachedToWindow外部调用getDisplayCutout会得到一个值。这当然是一个解决方案。更糟糕的是,当您在onConfigurationChanged内部时,从函数调用中获得的值在大多数情况下似乎实际上是

错误。它既过时(显示方向更改之前的剪切位置),有时甚至完全缺少剪切(当有多个剪切时),导致safeInsets错误。

有用的观察

我在尝试解决此问题时发现了一些有用的知识,这可能会帮助其他尝试处理保险开关的人。在我的方案中,我有一个全屏应用程序,不想通过尝试使用整个屏幕来增加复杂性,并且不想根据切口的大小和位置来调整布局。作为一个开发人员开发应用程序,完美地处理抠图要付出的努力远远超过我的付出。[我所希望的是能够以编程方式可靠地列出我的所有视图,而不必处理剪切点;这意味着我只需要知道可以安全放置内容的矩形的大小和位置即可。这听起来很容易,但由于上述问题而变得困难,并且在涉及系统叠加和剪切的Android信箱中出现了一些古怪之处。

下面是一些如何处理结果不一的技巧。

获取可用的屏幕尺寸(全屏应用)

方法1

您可以使用来获得可用的屏幕尺寸

<activity>.getWindowManager().getDefaultDisplay().getSize()

[使用LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER自动插入内容以避免出现切口时,无论有无切口,使用此尺寸似乎总是安全的。

尽管它似乎确实为导航栏保留了空间。设置以下标志:

setSystemUiVisibility(0
    | View.SYSTEM_UI_FLAG_LOW_PROFILE
    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION // hide nav bar
    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN // hide status bar
    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
    | View.SYSTEM_UI_FLAG_FULLSCREEN // hide status bar
    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY // if they swipe to show the soft-navigation, hide it again after a while.
);

getSize()功能似乎以纵向方式为屏幕底部的导航栏保留空间。如果您使用的是LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER,则此大小

已补偿了切口,这意味着您的内容可以容纳在提供的空间中,但是还将为导航栏保留额外的空间。如果您使用设备上的[[without
抠图,在底部仍然为导航栏保留了空间。这不是我想要的东西。
方法2

获得屏幕尺寸的另一种方法DisplayMetrics metrics = new DisplayMetrics(); MainActivity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);

这将获取

实际屏幕尺寸

以像素为单位,并排显示,而忽略系统覆盖图或条形图以及电话剪裁。当与一种准确可靠的方法来确定切口插图时,这似乎是全屏应用程序的最佳方法。当用户滑动以显示状态栏和软导航时,其边缘会与应用程序边缘的空白区有些奇怪的重叠,但这是迄今为止我发现的最佳解决方案,可以避免实际包裹在应用程序边缘带有我的布局的切口。

使用LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES,然后使用显示切口缩小和重新放置根视图。

// Get the actual screen size in pixels
DisplayMetrics metrics = new DisplayMetrics();
MainActivity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);
int width = metrics.widthPixels;
int height = metrics.heightPixels;
// if there are no cutouts, top and left offsets are zero
int top = 0;
int left = 0;
// get any cutouts
DisplayCutout displayCutout = MainActivity.getWindowManager().getDefaultDisplay().getCutout();
// check if there are any cutouts
if (displayCutout != null && displayCutout.getBoundingRects().size() > 0) {
    // add safe insets together to shrink width and height
    width -= (displayCutout.getSafeInsetLeft() + displayCutout.getSafeInsetRight());
    height -= (displayCutout.getSafeInsetTop() + displayCutout.getSafeInsetBottom());
    // NOTE:: with LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, we can render on the whole screen
    // NOTE::    and therefore we CAN and MUST set the top/left offset to avoid the cutouts.
    top = displayCutout.getSafeInsetTop();
    left = displayCutout.getSafeInsetLeft();
}

这是我最终使用的方法,到目前为止尚未发现任何问题。

方法3

这里我们使用与

#2中相同的方法来确定可用的宽度和高度,但是使用LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER以便Android处理该内容的位置。我以为这样做会很好,毕竟我对抠图空间中的渲染不感兴趣。但是,这会导致肖像出现奇怪的问题-android为状态栏保留的高度比顶部切口的高度

greater

大。这会影响我的代码计算出的高度,使我比实际上高[,结果我的内容在底部被切断。

如果可以确定状态栏的高度,则可以相应地调整计算出的高度,但是我不为所动,因为这似乎是一个较差的解决方案。顺便说一句,我试图找到状态栏的高度,但是我无法管理它(隐藏时,它似乎为零)。

结论

使用<activity>.getWindowManager().getDefaultDisplay().getCutout();获取显示切口。之后,只需确定使用哪种方法来确定要渲染的安全​​区域在哪里。我推荐方法2
© www.soinside.com 2019 - 2024. All rights reserved.