Determine keyboard presence and change layout accordingly in Jetpack Compose
Asked Answered
E

3

9

When I click on TextField, I need to scroll UI upwards to show login button to the user and not hide it behind keyboard.

I am using RelocationRequester for the same.

I am using this for detecting keyboard show/hide event:

fun listenKeyboard() {
val activityRootView =
  (requireActivity().findViewById<View>(android.R.id.content) as ViewGroup).getChildAt(0)
activityRootView.viewTreeObserver.addOnGlobalLayoutListener(object :
  ViewTreeObserver.OnGlobalLayoutListener {
  private var wasOpened = false
  private val DefaultKeyboardDP = 100
  private val EstimatedKeyboardDP =
    DefaultKeyboardDP + 48
  private val r: Rect = Rect()
  override fun onGlobalLayout() {
    val estimatedKeyboardHeight = TypedValue
      .applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        EstimatedKeyboardDP.toFloat(),
        activityRootView.resources.displayMetrics
      )
      .toInt()

    activityRootView.getWindowVisibleDisplayFrame(r)
    val heightDiff: Int = activityRootView.rootView.height - (r.bottom - r.top)
    val isShown = heightDiff >= estimatedKeyboardHeight
    if (isShown == wasOpened) {
      return
    }
    wasOpened = isShown
    keyboardVisibleState(isShown)
  }
})
  }

and once the keyboard is visible, I am calling the relocationRequestor's bringIntoView().

coroutineScope.launch {     
    delay(250)
    relocationRequester.bringIntoView()
}

Its behaving randomly, working on some devices and not on others. Is there any better solution to deal with this issue?

Erek answered 24/8, 2021 at 8:13 Comment(1)
WindowInsets.isImeVisibleDichotomous
H
14

Since Compose 1.2.0, Accompanist Insets was mostly moved into Compose Foundation, check out migration guide for more details. The main changes to below answer is that ProvideWindowInsets is no longer needed and some imports should be replaced.


You can use accompanist insets. You'll be able to check if keyboard is presented using LocalWindowInsets.current.ime.isVisible or add .imePadding() to your screen container.

This works great. But to make it work you'll have to disable window decor fitting:

This library does not disable window decor fitting. For your view hierarchy to able to receive insets, you need to make sure to call: WindowCompat.setDecorFitsSystemWindows(window, false) from your Activity. You also need to set the system bar backgrounds to be transparent, which can be done with our System UI Controller library.

If you don't want to do this, you will have to look for another solution.


Example in onCreate:

WindowCompat.setDecorFitsSystemWindows(window, false)

setContent {
    ProvideWindowInsets {
        ComposePlaygroundTheme {
            Column(
                horizontalAlignment = Alignment.CenterHorizontally,
                modifier = Modifier
                    .fillMaxWidth()
                    .statusBarsPadding()
                    .navigationBarsWithImePadding()
                    .padding(10.dp)
            ) {
                TextField(value = "", onValueChange = {})
                Spacer(Modifier.weight(1f))
                Button(onClick = {}) {
                    Text(text = "Proceed")
                }
            }
        }
    }
}


p.s. Also this won't help with lazy views: it's gonna decrease container size, but won't scroll to selected item. Waiting this issue to be resolved

Hallsy answered 24/8, 2021 at 8:23 Comment(4)
By lazy views, you mean this won't work with LazyColumn?Erek
@Erek yes, as I've said this modifier will work - it'll decrease view size, but it won't change scroll position, so visually there'll be no effect. Have to wait for compose maintainers to make some workaround. As a tmp solution you can try listen to LocalWindowInsets.current.ime.isVisible and scroll to item by index.Hallsy
LocalWindowInsets.current.ime.isVisible Even listening for this, do I have to disable the window decor fitting?Erek
@Erek yes.Hallsy
I
6

Edit July 2024:

WindowInsets.isImeVisible, imeAnimationSource and imeAnimationTarget can be used nowadays.

See below for the March 2022 answer.


As the insets from accompanist are deprecated, the ones at androidx.compose.foundation should be used.

I was able to use WindowInsets.Companion.ime.getBottom(LocalDensity.current) > 0 to check whether the IME is shown or not. Otherwise Modifier.imePadding() can also be used if it fits your usecase better. I am not exactly sure whether this properly works for landscape mode & other weird keyboard combinations, but it seems to work in portrait mode with a "bottom" keyboard.

The "official" API for this is still in the Feature Request stage, it's on the issue tracker: https://issuetracker.google.com/issues/217770337

Ineligible answered 17/3, 2022 at 9:4 Comment(1)
If you use Modifier.imePadding(), remember to add android:windowSoftInputMode="adjustResize" in the manifest.Encyclopedia
B
0

You can also just add the following line to your Manifest in the activity part :

android:windowSoftInputMode="adjustResize"

Like in this example:

        <activity
        android:name=".MainActivity"
        android:exported="true"
        android:label="@string/app_name"
        android:windowSoftInputMode="adjustResize"
        android:theme="@style/Theme.MyApp">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>

        <meta-data
            android:name="android.app.lib_name"
            android:value="" />
        </activity>
Biquarterly answered 24/1, 2023 at 13:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.