2

display cut-out screenshot

I still want the bottom navigation bar to remain the same length, but I want to add some padding in the container to the left to shift the cards over to the right a bit. I have the following code in my MainActivity.kt right now:

WindowCompat.setDecorFitsSystemWindows(window, false)
    ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
        val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
        view.updateLayoutParams<ViewGroup.MarginLayoutParams> {
            topMargin = insets.top
        }
        binding.bottomNavigationView.updatePadding(bottom = insets.bottom)
        WindowInsetsCompat.CONSUMED
    }

My activity_main.xml is composed of a ConstraintLayout that encloses two elements: BottomNavigationView and FragmentContainerView.

dem
  • 125
  • 1
  • 2
  • 12

2 Answers2

2

Instead of padding the layout the documentation offers the default cutout mode option for that.

LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT - With this default setting, content renders into the cutout area when displayed in portrait mode, but content is letterboxed when displayed in landscape mode

So, you'd designate that in the themes/style file using:

<item name="android:windowLayoutInDisplayCutoutMode">default</item>

Update

I have it set to shortEdges because I don't want the default behavior. The default behavior doesn't go edge-to-edge when in landscape orientation. The problem I'm having is that the cards get obscured, so I'm wondering how to fix that with either inset margins or padding.

As the place to add padding would would change in terms of the device orientation direction, so you'd track changes in orientation direction with OrientationEventListener and add the padding accordingly.

The typical value of the cutout depth could be discovered as the max value of the screen insets (because the app would be launched in portrait, landscape left/right); in each case, the cutout position is different.

private var cutoutDepth = 0
WindowCompat.setDecorFitsSystemWindows(window, false)
    ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
        val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())

        cutoutDepth = Collections.max(listOf(insets.bottom, insets.left, insets.right))

        // reset of code is omitted

Notice that the cutoutDepth variable should be saved in a permanent storage like a ViewModel; you need to handle that.

Here by the padding is added to the root view; but you'd cascade that to the RecyclerView in the fragment.

private val orientationListener by lazy {
    object : OrientationEventListener(applicationContext, SensorManager.SENSOR_DELAY_NORMAL) {
        override fun onOrientationChanged(orientation: Int) {

            if (orientation == ORIENTATION_UNKNOWN) return
           
            val rotation =
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) display?.rotation
                else windowManager.defaultDisplay.rotation

            when (rotation) {
                Surface.ROTATION_0 -> {
                    // BOTTOM
                    binding.root.setPadding(0, 0, 0, 0) // reset the padding in portrait 
                    Log.d("LOG_TAG", "Orientation: BOTTOM")
                }
                Surface.ROTATION_90 -> {
                    // LEFT
                    binding.root.setPadding(cutoutDepth, 0, 0, 0)
                    Log.d("LOG_TAG", "Orientation: LEFT")
                }
                Surface.ROTATION_180 -> {
                    //  TOP
                    binding.root.setPadding(0, 0, 0, 0) // // reset the padding in upside down (if it's allowed)
                    Log.d("LOG_TAG", "Orientation: TOP")
                }

                Surface.ROTATION_270 -> {
                    //  RIGHT
                    binding.root.setPadding(0, 0, cutoutDepth, 0)
                    Log.d("LOG_TAG", "Orientation: RIGHT")
                }

            }

        }
    }
}

override fun onResume() {
    super.onResume()
    // start the orientation listener
    if (orientationListener.canDetectOrientation())
        orientationListener.enable()
}

override fun onPause() {
    super.onPause()
    // stop the orientation listener
    orientationListener.disable()
}
Zain
  • 37,492
  • 7
  • 60
  • 84
  • I have it set to `shortEdges` because I don't want the default behavior. The default behavior doesn't go edge-to-edge when in landscape orientation. The problem I'm having is that the cards get obscured, so I'm wondering how to fix that with either inset margins or padding. – dem Nov 15 '22 at 01:12
  • Hey, so I just tried out your code and it works really well for devices with a punch-hole type cutout (like Pixel 7). However, it even adds padding on devices without a cutout (like Pixel 4) and the padding is not enough on devices with a large notch (like Pixel 3 XL). I guess it's because of the hardcoded `dpVal` of 30f. Is there any way to get the display cutout size in the companion object? – dem Nov 15 '22 at 21:28
  • 1
    @dem I see, let me have a look on it again – Zain Nov 16 '22 at 07:21
  • @dem Just updated the answer, pls have a look; the value now is got from insets. it is working on Samsung A52. You would apply that conditionally if the device supports a notch; [this thread](https://stackoverflow.com/questions/52514075/how-to-detect-if-device-support-notch-display) and [that](https://stackoverflow.com/questions/61374468/how-to-detect-a-screen-cutout-notch-and-get-its-height) can have some sense. Excuse me as I had no chance to test that. – Zain Nov 17 '22 at 16:23
1

Fixed it thanks to @Zain's help!

private var cutoutDepth = 0

override fun onCreate(savedInstanceState: Bundle?) {
    [...]
    ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
        val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
        cutoutDepth = insets.top
        [...]
    }
    [...]
}

override fun onResume() {
    super.onResume()
    // Starts the orientation listener
    if (orientationListener.canDetectOrientation())
        orientationListener.enable()
}

override fun onPause() {
    super.onPause()
    // Stops the orientation listener
    orientationListener.disable()
}

private val orientationListener by lazy {
    object : OrientationEventListener(applicationContext, SensorManager.SENSOR_DELAY_NORMAL) {
        override fun onOrientationChanged(orientation: Int) {
            if (orientation == ORIENTATION_UNKNOWN)
                return
            when (display?.rotation) {
                Surface.ROTATION_0 -> {
                    // Bottom - reset the padding in portrait
                    binding.navHostFragmentActivityMain.setPadding(0, 0, 0, 0)
                }
                Surface.ROTATION_90 -> {
                    // Left
                    binding.navHostFragmentActivityMain.setPadding(cutoutDepth, 0, 0, 0)
                }
                Surface.ROTATION_180 -> {
                    // Top - reset the padding if upside down
                    binding.navHostFragmentActivityMain.setPadding(0, 0, 0, 0)
                }
                Surface.ROTATION_270 -> {
                    // Right
                    binding.navHostFragmentActivityMain.setPadding(0, 0, cutoutDepth, 0)
                }
            }
        }
    }
}

End result:

end result

dem
  • 125
  • 1
  • 2
  • 12