Android: Bottom Side EditText Position Shifting

Homan Huang
3 min readNov 8, 2020

You will never be satisfied with the Input position after the soft keyboard is ON if the input box is lower than the keyboard position. It’s too close to select anything else.

Please check the image for the problem. What can we do? Simple, let rise the view to be higher than the keyboard.

— === Menu === —

🌐1. UI Listener: OnGlobalLayoutListener
🔁2
. Deal with Keyboard Data
☕3
. Test Result

🌐1. UI Listener: OnGlobalLayoutListener

< === Menu

private var mGlobalLayoutListener: OnGlobalLayoutListener? = null

In Android, we need this listener to check the keyboard on/off. Usually, we can declare this listen as an Object in Kotlin. But we need to delete it when the activity is closed or you will have a memory leak. It’d be better to state it in a var and close it at onDestory().

override fun onDestroy() {
super.onDestroy()
rootView.viewTreeObserver
.removeOnGlobalLayoutListener(mGlobalLayoutListener)
}

Let’s add one in Activity — onCreate() or Fragment — onViewCreated():

override fun onViewCreated(...) {
super.onViewCreated(view, savedInstanceState)

...

// EditText adjustment
mGlobalLayoutListener = OnGlobalLayoutListener {
resolvePosition()
}
...

Now, you can attach the view which may have a keyboard issue. The fragment example,

rootView = view.findViewById(R.id.rootFL)
rootView // check keyboard and editText distance
.viewTreeObserver
.addOnGlobalLayoutListener(mGlobalLayoutListener)

🔁2. Deal with Keyboard Data

< === Menu

I put solution into a function, resolvePosition():

fun resolvePosition() {
// achieve keyboard and UI ratio
val kbStatus = isKeyboardShown(
customerEmailEt.rootView,
customerEmailEt,
::getKeyboardData
)

if (kbStatus) {
if (adjustEditText)
welcomeCL.translationY = diffGap.toFloat()
} else {
welcomeCL.translationY = 0f
}
}

(Check the image for welcomeCL.)

The isKeyboardShown(),

// Gather UI data from keyboard, Edittext + rootView
fun isKeyboardShown(
rootView: View,
mEt: EditText,
refSave: KFunction2<Int, Boolean, Unit>
): Boolean {
/* 128dp = 32dp * 4, minimum button height 32dp and
generic 4 rows soft keyboard */
val SOFT_KEYBOARD_HEIGHT_DP_THRESHOLD = 128
val r = Rect()
rootView.getWindowVisibleDisplayFrame(r)
val etR = Rect()

// EditText coordinate
val coords = intArrayOf(0, 0)
mEt.getLocationOnScreen(coords)
val etTop = coords[1]
val etBottom = coords[1] + mEt.height

mEt.getWindowVisibleDisplayFrame(etR)
val dm = rootView.resources.displayMetrics
/* heightDiff = rootView height - status bar height (r.top) -
visible frame height (r.bottom - r.top)
*/
val heightDiff = rootView.bottom - r.bottom
/* Threshold size: dp to pixels, x display density */
val isKeyboardShown = heightDiff >
SOFT_KEYBOARD_HEIGHT_DP_THRESHOLD * dm.density

var adjustEditText = false
var diffGap = 0
if (r.bottom < etBottom) {
adjustEditText = true
diffGap = r.bottom - etBottom - mEt.height / 2
}
refSave(diffGap, adjustEditText) // ejects data

val tag = "isKbShown: "
lgd(tag+"isKeyboardShown ? $isKeyboardShown, " +
"rootView bottom: ${rootView.bottom}\n" +
"heightDiff: $heightDiff, rect bottom :$r, " +
"et : $etTop :: $etBottom; diff: $diffGap"
)

return isKeyboardShown
}

It calculates the UI data and ejects the data to a local function, getKeyboardData().

The lgd() is from LogHelper.kt to display log on the console:

const val TAG = "MLOG"
fun lgd(s:String) = Log.d(TAG, s)
fun lgi(s:String) = Log.i(TAG, s)
fun lge(s:String) = Log.e(TAG, s)

The getKeyboardData() in Activity/Fragment:

private var diffGap = 0
private var adjustEditText = false
private fun getKeyboardData(
diffGap: Int, adjustEditText: Boolean
) {
this.diffGap = diffGap
this.adjustEditText = adjustEditText
}

☕3. Test Result

< === Menu

Looking good!

Here is my file tree:

--

--

Homan Huang

Computer Science BS from SFSU. I studied and worked on Android system since 2017. If you are interesting in my past works, please go to my LinkedIn.