Skip to content

Instantly share code, notes, and snippets.

@felipeslongo
Created May 14, 2024 19:36
Show Gist options
  • Save felipeslongo/42758581a2f977ac9a8bb1a28d31fbb6 to your computer and use it in GitHub Desktop.
Save felipeslongo/42758581a2f977ac9a8bb1a28d31fbb6 to your computer and use it in GitHub Desktop.
Modifier.positionAwareImePadding
package com.felipeslongo
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.findRootCoordinates
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInWindow
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.window.DialogProperties
import androidx.core.view.WindowCompat
/**
* Modifier that move a scrollable container content UP when keyboard is shown on screen.
* It combines the calls for [imePadding] and [consumeWindowInsets] to correctly add just the necessary
* padding so the Text Input content is just above the keyboard.
*
* It uses [consumeWindowInsets] to correctly calculate the distance between the bottom of the device
* physical screen and the bottom point coordinate of the scrollable target composable, relative to the
* device window. This distance then is consumed to avoid blank space being added by [imePadding].
*
* Use this when the main scrollable composable of your destination screen, that should handle the
* keyboard movement and padding behaviour:
* - Is NOT the root composable of the screen (example AddVariantDialog)
* - Does NOT fillMaxHeight of the screen (example Dialog with max fraction height)
* - Have other composable bellow it (example buttons or padding on bottom)
*
* Attentions: Ideally this modifier should NOT be applied to more than one scrollable composable
* in each nav destination screen. Behaviour if applied to multiple ones on the same screen is not
* guaranteed.
*
* Prerequisites:
* - Dialogs destinations should have [DialogProperties.decorFitsSystemWindows] set to false.
* - Composable destinations from root activity must have root activity window call for
* [WindowCompat.setDecorFitsSystemWindows] to false
* - The target composable of this modifier should be a scrollable container, either by scroll modifier
* or by using a [LazyColumn].
*
* For more information, see the following links:
* - https://stackoverflow.com/questions/76512200/reduce-ime-padding-for-child-composable-in-jetpack-compose
* - https://developer.android.com/develop/ui/compose/layouts/insets
* - https://nameisjayant.medium.com/window-insets-in-jetpack-compose-b0e0c737fe88
*/
@Composable
fun Modifier.positionAwareImePadding(): Modifier {
// Density for Pixel vs Dp conversion.
val density = LocalDensity.current
// Device physical screen height is unlikely to change. We better cache first result for
// performance.
var deviceBottom by remember { mutableIntStateOf(0) }
// The distance between the bottom part of the physical screen of the device, and the bottom
// coordinate point of the target composable.
var consumePadding by remember { mutableIntStateOf(0) }
return this then Modifier
// Calculate the distance to the bottom to be used as consumePadding.
.onGloballyPositioned { coordinates ->
// Calculate and cache.
deviceBottom = deviceBottom
.takeUnless { it == 0 } // Zero means its not initialized
?: coordinates.findRootCoordinates().size.height
// The bottom coordinate point of the target composable.
val bottom = coordinates.positionInWindow().y + coordinates.size.height
// Difference computation
consumePadding = (deviceBottom - bottom).toInt()
}
// Consume (update) the calculated bottom distance
.consumeWindowInsets(
PaddingValues(
bottom = with(density) { consumePadding.toDp() }
)
)
// Apply the keyboard padding and UP movement when shown.
.imePadding()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment