Search code examples
androidandroid-layoutwindowinsets

onApplyWindowInsets called with strange top inset


I have an Android app which has the following settings in its theme:

<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@android:color/transparent</item>

<item name="android:windowDrawsSystemBarBackgrounds">true</item>

The top View in the hierarchy overrides onApplyWindowsInsets(). The implementation just logs the WindowInsets it was passed, then calls through to the superclass method, logs the result and returns it—nothing is modified. The log also shows that the superclass method does not modify the insets.

Now I am getting an odd-looking top inset, which always seems to be some 56–72 dp more than what I would expect it to be:

Platform           DPI    Layout                  Inset         Bar          Difference

Android x86_64 8.1 MDPI   Landscape               88 px/dp      24 px/dp     64 px/dp
                          Landscape, split        80 px/dp      24 px/dp     56 px/dp
Anbox              MDPI   Window                  64 px/dp      None         64 px/dp
LineageOS 15.1     XXHDPI Portrait                240 px/80 dp  72 px/24 dp  168 px/56 dp
                          Portrait, split/top     216 px/73 dp  72 px/24 dp  144 px/48 dp
                          Portrait, split/bottom  144 px/48 dp  None         144 px/48 dp
SDK Emulator (6.0) HDPI   Portrait                120 px/80 dp  36 px/24 dp  84 px/56 dp

Note: in landscape mode, there are two consecutive calls to onApplyWindowInsets(), only the last ones are reported here.

The view is not actually extended beyond the top of what is visible: If I draw a 24-px area at the top of my view, it appears at the top of the visible area (i.e. underneath the status bar if there is one), never off-screen.

If I were to trust these inset values and offset content by the number of pixels indicated to keep it clear of the system bars, I would end up with a gap at the top of the view.

Left/right/bottom values seem OK as far as I can tell.

How can I correct this? Am I overlooking something?


Solution

  • As I still have no explanation for the values reported, I assume this is either a bug or a poorly documented feature, which is nevertheless present in many (if not all) versions of Android.

    Luckily, at least on API 23+, there is another method to get window insets, namely by calling View#getWindow().getRootView().getRootWindowInsets(). This seems to have some bugs of its own, namely reporting incorrect insets for the navigation bar, but merging the two results has worked for me.

    • Store the insets passed to onApplyWindowsInsets(), discarding only the top one.
    • Override onSizeChanged() for the view, and in it call View#getRootWindowInsets() to get the top inset (ignore the others).

    The combined values have worked for me.

    Unfortunately this will not work on API 20–22, which does not implement View#getRootWindowInsets(). Here, you will need to do some guesswork. Fortunately, this is easier as these APIs do not implement split screen yet—the top inset will be the height of the status bar, unless you’ve explicitly requested that it be hidden (in which case it is 0). If the status bar is visible, its height can be determined as follows:

    Resources resources = view.getResources();
    int shid = resources.getIdentifier("status_bar_height", "dimen", "android");
    padding_top = (shid > 0) ? resources.getDimensionPixelSize(shid) : 0;