Android: How to make sure the soft keyboard slides up smoothly

Viewed 144

I have a RecyclerView and an EditText below it. They are the children of a vertical LinearLayout. When I click the EditText, the soft keyboard shows, but the animation isn't very smooth. It seems Android first creates dead space for the keyboard by pushing the RecyclerView up and then the keyboard translates into this space.

On Android 11's Messages app, I noticed that the keyboard AND the content above both translate together. How can I achieve a similar effect?

I'm using AdjustResize as my window soft input mode. I want to avoid using AdjustPan because it won't allow scrolling to the top of the RecyclerView if it contains many items. I noticed Messages can scroll to top just fine.

1 Answers

I had a similar problem where I needed to pan the edit text with the keyboard. I used "AdjustUnspecified" as my window soft input mode, and did the following to achieve the wanted behavior.

1- Implement the following utility functions for convenience

fun Activity.getRootView(): View {
    return findViewById<View>(android.R.id.content)
}
fun Context.convertDpToPx(dp: Float): Float {
    return TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP,
        dp,
        this.resources.displayMetrics
    )
}
fun Activity.isKeyboardOpen(): Boolean {
    val visibleBounds = Rect()
    this.getRootView().getWindowVisibleDisplayFrame(visibleBounds)
    val heightDiff = getRootView().height - visibleBounds.height()
    val marginOfError = Math.round(this.convertDpToPx(50F))
    return heightDiff > marginOfError
}

2- In your main activity, add the following; (It is assumed that the layout id of your main activity is "main_activity_layout")

Declare a listener in the top of the class;

    lateinit var listener: ViewTreeObserver.OnGlobalLayoutListener

Then, do the following;

  override fun onResume() {
    super.onResume()
    listener = object : ViewTreeObserver.OnGlobalLayoutListener {
        // Keep a reference to the last state of the keyboard
        private var lastState: Boolean = isKeyboardOpen()

        /**
         * Something in the layout has changed
         * so check if the keyboard is open or closed
         * and if the keyboard state has changed
         * save the new state and invoke the callback
         */
        override fun onGlobalLayout() {
            val isOpen = isKeyboardOpen()
            if (isOpen == lastState) {
                return
            } else {
                onKeyboardStateChanged(isOpen)
                lastState = isOpen
            }
        }
    }
    getRootView().viewTreeObserver?.addOnGlobalLayoutListener(listener)
}

private fun onKeyboardStateChanged(open: Boolean) {
    runOnUiThread {
        if (open) {
            val screenHeight = resources.displayMetrics.heightPixels
            TransitionManager.beginDelayedTransition(main_activity_layout as ViewGroup)
            main_activity_layout.scrollTo(0, screenHeight / 4)
        } else {
            TransitionManager.beginDelayedTransition(main_activity_layout as ViewGroup)
            main_activity_layout.scrollTo(0, 0)
        }
    }
}

override fun onPause() {
    super.onPause()
    getRootView().viewTreeObserver.removeOnGlobalLayoutListener(listener)
}

You can modify the amount of panning by modifying the scroll value here

   main_activity_layout.scrollTo(0, screenHeight / 4)
Related