It is a very old post but I have also struggled with this very recently in my project. I have come up with a solution that solves this case perfectly, so I wanted to share to help someone avoid the struggles I've been through.
Setting windowSoftInputMode to "Adjust Resize" did not work since my application was full screen.(it is due to an android bug mentioned here) . Even if it did work, I believe this option is not very good UI. So I also wanted to make the "Adjust Pan" option work with "more pan", and unfortunately Android does not provide an API to adjust this margin. And adding "paddingBottom" to the editText makes the UI of the screen unbalanced.
After a lot of research, I was able to solve this by following these steps;
(I used this very helpful medium post)
1 - Make windowSoftInputMode="adjustUnspecified" from AndroidManifest.
2 - Add the following methods as extension functions;
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
}
3 - Create a global layout listener for detecting when the keyboard is visible as below;
val listener = object : ViewTreeObserver.OnGlobalLayoutListener {
// Keep a reference to the last state of the keyboard
private var lastState: Boolean = isKeyboardOpen() ?: false
/**
* 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() ?: false
if (isOpen == lastState) {
return
} else {
onKeyboardStateChanged(isOpen)
lastState = isOpen
}
}
}
4 - In the onKeyboardStateChanged method, I scroll my main activity layout up by screenHeight/4 which is generally enough, but you can play around with the amount of scroll as you see fit and change the layout to whichever layout you want to scroll by. Also, I use a transition animation to make the transitions smoother, which is not necessary for functionality.
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)
}
}
}
5 - Add the view to listen on your onResume, and remove the listener on onPause as follows;
override fun onResume() {
super.onResume()
val view = getRootView()
view.viewTreeObserver?.addOnGlobalLayoutListener(listener)
}
override fun onPause() {
super.onPause()
val view = getRootView()
view.viewTreeObserver?.removeOnGlobalLayoutListener(listener)
}
THIS WILL NOT WORK IF YOU SET THE windowSoftInputMode TO "adjustNothing". Because the listener logic will fail.
Also, it will not work as intended if you use it with "adjustPan", because this logic adjusts the pan from the current scroll point. So let's say you have two edit texts in the same view, user clicks the first one, it pans the layout up a little from Android logic, and then scrolls from the logic we implemented here. Then the user clicks on the second view, Android logic pans from the current scroll y value, so it is again very close to the editText, and our logic does not work because the scroll y is already increased. Alternatively you can keep incrementing the scroll y which will end up in your layout becoming out of proportion and is not desired.