Created
May 14, 2024 19:36
-
-
Save felipeslongo/42758581a2f977ac9a8bb1a28d31fbb6 to your computer and use it in GitHub Desktop.
Modifier.positionAwareImePadding
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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