除了全屏Java应用程序中的onAttachedToWindow之外,WindowInsets.getDisplayCutout均为NULL. [英] WindowInsets.getDisplayCutout is NULL everywhere except within onAttachedToWindow in fullscreen Java app

查看:413
本文介绍了除了全屏Java应用程序中的onAttachedToWindow之外,WindowInsets.getDisplayCutout均为NULL.的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am having issues with getDisplayCutout() in my fullscreen Java app. I can only seem to get the value of the DisplayCutout within the onAttachedToWindow function. After that function completes, I can never get it again. Code to get the cutouts:

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

问题

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

Question

How can I get the display cutouts reliably, from anywhere in the code, at any time once attached to the view hierarchy?

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

After much investigation I have narrowed it down to a VERY broad problem with fullscreen apps and I am surprised that no-one else is asking about it. We can in fact ignore my app and just work off two template projects which you can make yourselves right now.

在Android Studio中,我正在谈论称为基本活动"和全屏活动"的Phone和Tablet项目.如果您分别创建一个,然后进行以下更改:

In Android studio I'm talking about the Phone and Tablet projects called 'Basic Activity' and 'Fullscreen Activity'. If you create one of each, and make the following changes:

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

For Basic, change the Manifest to handle configuration changes yourself by adding android:configChanges="orientation|keyboardHidden|screenSize" under the Activity tag like so:

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

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

Now for both of them, add the following two functions to the activity file:

@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模拟器上运行了该模拟器,在该模拟器上启用了模拟抠图并选择了两者(因此底部和顶部都有一个抠图)

That's all you need to do to reproduce this issue. I run this on a simulator for Pixel 3 on API Q, on which I have enabled simulating cutouts and have chosen BOTH (so there's a cutout on the bottom and the top)

现在,如果您在我们尝试获取显示切口的行上设置断点( DisplayCutout displayCutout = insets.getDisplayCutout(); ),您会在Basic应用程序上看到它可以在启动时正常工作,当您更改方向时,但是在全屏应用中,它仅在启动时起作用.

Now if you breakpoint on the line where we try to get display cutouts (DisplayCutout displayCutout = insets.getDisplayCutout();), you'll see that on the Basic app, it works on startup and when you change orientation, but in the fullscreen app it only works on startup.

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

In fact on my application I have tested by using the following code within 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线程以检查切口.

This code starts a thread so that the onAttachedToWindow function can finish running; but the thread immediately sends execution back to the UI thread to check the cutouts.

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

The delay between the onAttachedToWindow function completing its execution and my code checking for the displayCutout must be in the order of nano seconds, but the cutouts are immediately unavailable.

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

Any ideas? Is this somehow expected?

在方向改变时无法访问切口,我只能求助于最大的插图(肖像的顶部或底部,因为长边无法容纳),然后将其应用于肖像的顶部和底部,或者在风景中向左和向右.

Without access to the cutouts when orientation changes I have no recourse but to record the Largest inset (top or bottom in portrait, since long edges cannot have them), and apply it to both the top and bottom in portrait, or the left and right in landscape.

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

This is because I cannot find a way in android to check WHICH kind of landscape is currently active (eg is the top of the phone on the left, or on the right). If I could check that, I could at least only apply the inset on the edge of the phone where it is needed.

推荐答案

我发现了很多可以帮助其他人的东西.
首先,我将回答最初的问题,然后,我将解释它为什么起作用,并为那些遇到同样问题的人们提供一些替代选择: 如何通过适当的方式在带有切口的手机上显示全屏应用程序对我的应用程序添加信箱 .
注意:我的所有布局都是以编程方式完成的(我的布局目录中甚至没有任何xml文件).这样做是出于跨平台的原因,也许这就是为什么我遇到这个问题而其他人则没有.

I've figured out a bunch of stuff that might help other people.
First I'll answer the original question, then I'll explain why it works, with some alternatives for people struggling with the same issue I was: How to display a fullscreen app on a phone with cutouts by properly letterboxing my app.
NOTE: All of my layout is done programatically (I don't even have any xml files in my layout directory). This was done for cross platform reasons and is perhaps why I am having this issue while others are not.

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

Use the following code to get display cutouts (with approriate null checks):

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

在我的测试中,这在 onConfigurationChanged 中返回了正确的切口,并且在 onAttachedToWindow 之外调用时,不为空(如果存在当然).

In my testing, this returned the correct cutouts in onConfigurationChanged and is not null when called outside of onAttachedToWindow (if there are cutouts of course).

getWindow().getDecorView().getRootWindowInsets().getDisplayCutout()似乎始终为NULL;我没有找到解决方法.
如果您通过以下方法将您的应用退出全屏模式:

getWindow().getDecorView().getRootWindowInsets().getDisplayCutout() appears to always be NULL if accessed outside the onAttachedToWindow function when your application is fullscreen; I did not find a way around that.
If you take your app out of fullscreen by doing this:

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

从外部 onAttachedToWindow 调用它时,您将从getDisplayCutout中获取一个值.
当然,这不是不是解决方案.
更糟糕的是,您在 onConfigurationChanged 内部时从此函数调用中获得的值在大多数情况下似乎实际上是错误;它既过时(显示方向更改之前的切口位置),有时甚至完全缺少切口(当有多个切口时),导致safeInsets错误.

You will get a value from getDisplayCutout when calling it from outside onAttachedToWindow.
This is of course not a solution.
Worse than that, the value you get from this function call when inside onConfigurationChanged seems to actually be wrong most of the time; it is both out of date (showing the cutout locations from before the orientation change) and sometimes completely missing a cutout (when there are multiple cutouts) resulting in the safeInsets being wrong.

在尝试解决此问题时,我发现了一些有用的知识,这些知识可能会帮助其他尝试处理保险开关的人.
在我的方案中,我有一个全屏应用程序,不想通过尝试使用整个屏幕来增加复杂性,并且不想根据切口的大小和位置来调整布局.
作为一个开发人员来开发应用程序,完美地处理抠图的工作量比我值得付出的努力要多得多.

I found out some useful stuff to know while trying to solve this issue, which may help others trying to deal with cutouts.
In my scenario I have a full-screen app and do not want to add complexity by trying to use the whole screen, and having to adjust layout based on cutout size and location.
Handling cutouts perfectly is far more effort than it is worth for me as a single developer working on an app.

我所希望的是能够以编程方式可靠地排列我的所有视图,而不必处理剪切点;这意味着我只需要知道可以安全放置内容的矩形的大小和位置即可.
这听起来很容易,但由于上述问题而变得困难,并且在Android的内容中出现了一些古怪的文字,这些文字涉及系统叠加和剪切.

All I want is to be able to reliably lay out all of my views programmatically without having to deal with cutouts; this means all I need to know is the size and position of a rectangle in which it is safe to place my content.
This sounds easy but was made difficult by the issue above, and some quirk in Android's letterboxing of content where system overlays and cutouts are involved.

下面是一些如何处理变化结果的技巧.

Below are some tips on how this can be handled with varying results.

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

You can get available screen size using

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

使用 LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER 自动插入内容以避免出现切口时,无论有无切口,此大小似乎总是可以安全使用的.

This size appears to always be safe to use, with or without cutouts, when using LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER to automatically inset the the content to avoid the cutouts.

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

It does seem to reserve space for the navigation bar though.
With the following flags set:

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 ,则此大小 将补偿切口,这意味着您的内容可以容纳在所提供的空间中,但是也将为导航栏保留额外的空间.
如果您使用的设备没有切口,则导航栏底部仍会保留空间.
这不是我想要的东西.

The getSize() function appears to reserve space for the navigation bar at the bottom of the screen in portrait.
If you are using LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER, then this size will have compensated for the cutouts which means your content can fit in the space provided, but it will also have reserved extra space for the nav bar.
If you are on a device without cutouts, there will still be space reserved at the bottom for the navigation bar.
This is not something that I want.

获取屏幕尺寸的另一种方法是

Another method for getting screen size is to do

DisplayMetrics metrics = new DisplayMetrics();
MainActivity.getWindowManager().getDefaultDisplay().getRealMetrics(metrics);

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

This will fetch the actual screen size in pixels, edge to edge, ignoring system overlays or bars, and phone cutouts.
This looks like the best way to go for a fullscreen app, when combined with an accurate and reliable method for determining the cutout insets.
The status bar and soft navigation will overlap a little strangely with the empty space at the edge of your app when the user swipes to show them, but it is by far the best solution I have found where I can avoid trying to actually wrap around the cutouts with my layout.

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

Use LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, then use the display cutouts to both shrink and reposition your root view.

// 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();
}

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

This is the method I ended up using, and have found no issues with it so far.

在这里,我们使用与#2 中相同的方法来确定可用的宽度和高度,但是使用 LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER ,以便Android处理该位置内容.
我以为这样会很好,毕竟我对抠图空间中的渲染不感兴趣.
不过,这会导致肖像出现奇怪的问题-android为状态栏保留的高度比顶部切口的高度更大.
这样做的结果是,我的代码计算出我的身高比我实际高了,结果我的内容在底部被切断了.

Here we are using the same method as in #2 to determine the Width and Height we have available, but using LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER so that Android deals with the position of that content.
I thought this would work fine, after all I am not interested in rendering in the cutout space.
This causes a strange issue in portrait though - the height that android reserved for the status bar was greater than the height of the top cutout.
This has the effect of my code calculating that I have more height than in fact I do, and my content gets cut off at the bottom as a result.

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

If you could determine the height of the status bar then you could adjust the height you calculate accordingly, but I couldn't be bothered since it seems to be an inferior solution.
I tried to find the status bar height for a while by the way, but I couldn't manage it (it seems to be zero while it is hidden).

使用< activity> .getWindowManager().getDefaultDisplay().getCutout(); 获取显示切口.
之后,只需确定使用哪种方法来确定要渲染的安全​​区域在哪里.

Use <activity>.getWindowManager().getDefaultDisplay().getCutout(); to get display cutouts.
After that just decide which method to use to determine where the safe area to render is.

我推荐方法2

这篇关于除了全屏Java应用程序中的onAttachedToWindow之外,WindowInsets.getDisplayCutout均为NULL.的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆