Android how to adjust for soft keyboard

Ultimate pitfall guide for adjustResize with full screen mode

Yat Man, Wong
7 min readOct 2, 2020

Basic

In our AndroidManifest file we can set the windowSoftInputMode for the activity.

<activity
android:name=".MainActivity"
android:windowSoftInputMode="adjustPan" >
...
</activity>

adjustPan shift the whole page up to make room for the keyboard

adjustResize resize the page, shrink whatever it can to display in a smaller window

See the difference here

This is the baby level basic for every Android developer.

Common Problem

Notice adjustResize only have effect on layouts that are resizable. Like items in RelativeLayout with weight or page wrapped in ScrollView.

But if we have a ConstraintLayout with fixed margin for all its widgets, then adjustResize simply won’t work because there is nothing to resize. (See this example)

Options we have whenadjustResize don’t work

  • use adjustPan
  • change to a layout to that support resizing (wrap it in a ScrollView)
  • mark some views as GONE when detecting the keyboard
  • change some other values on the fly (font size, margin values…)
  • prepare 2 layout files
  • Manually do the shift up shift down ourselves

Pitfalls and AndroidBug5497

First and foremost:

Problem: How to detect soft keyboard open and close

The first result on StackOverflow is probably this one. Google didn’t provide us an API to check soft keyboard state, or callback for such events. Look at Google’s treatment to the Android soft keyboard.

On September of 2020, Google released android 11, which finally have the ability to check soft keyboard visibility. See this.

Sounds great except this feature requires API level 30, which means if we want to ship an app today, this feature is not going to work on most Android devices on the market.

In the mean while, what we can do is set a listener to our layout view to detect height changes. If the screen available height suddenly shrink by a certain amount, we can infer it is caused by the keyboard pop up.

Find a root view we like to listen to the height change. Can be our root view or the most outer view group of our activity layout. And set the listener in that view’s onGlobalLayout

Something like this

Problem: adjustResize does not work in full screen mode

Why? This is a known bug in Android, a hole dug by Google many years ago. This has been reported as issue 5497 in 2009 and still is an issue in 2020.

So, it is up to us developers to deal with it. We can either dodge or patch this hole.

It is easy to dodge, don’t use full screen mode with adjustResize. But if we cannot dodge, the best solution currently available is to use the AndroidBug5497Workaround helper class to specifically deal with this problem.

If you search about it, you will find this possible work around here.

How AndroidBug5497Workaround works?

It is not too complicated. We are just manually triggering adjustResize on keyboard open or close.

I paste the whole helper class here.

public class AndroidBug5497Workaround {

// For more information, see https://code.google.com/p/android/issues/detail?id=5497
// To use this class, simply invoke assistActivity() on an Activity that already has its content view set.

public static void assistActivity (Activity activity) {
new AndroidBug5497Workaround(activity);
}

private View mChildOfContent;
private int usableHeightPrevious;
private FrameLayout.LayoutParams frameLayoutParams;

private AndroidBug5497Workaround(Activity activity) {
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);
mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
possiblyResizeChildOfContent();
}
});
frameLayoutParams = (FrameLayout.LayoutParams) mChildOfContent.getLayoutParams();
}

private void possiblyResizeChildOfContent() {
int usableHeightNow = computeUsableHeight();
if (usableHeightNow != usableHeightPrevious) {
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();
int heightDifference = usableHeightSansKeyboard - usableHeightNow;
if (heightDifference > (usableHeightSansKeyboard/4)) {
// keyboard probably just became visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
} else {
// keyboard probably just became hidden
frameLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}

private int computeUsableHeight() {
Rect r = new Rect();
mChildOfContent.getWindowVisibleDisplayFrame(r);
return (r.bottom - r.top);
}

}

Here is how it works.

1. Get the rootView
FrameLayout content = (FrameLayout) activity.findViewById(android.R.id.content);
mChildOfContent = content.getChildAt(0);

The view from findViewById(android.R.id.content) is our root view.

The view from content.getChildAt(0)is the view we pass into setContentView in our Activity onCreate() method. It is the view of our whole layout, including the most outer layer constriantLayout/RelativeLayout.

2. Set a listener for the root view when the height change

mChildOfContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
possiblyResizeChildOfContent();
}
});

What this does is getting a callback when our root view’s height just changed. Similar to the problem above of how to detect soft keyboard open or close.

3. Calculate the height change to detect if the keyboard open or close

int usableHeightNow = computeUsableHeight();        
int usableHeightSansKeyboard = mChildOfContent.getRootView().getHeight();

usableHeightNow is the screen’s available height after the keyboard open. It becomes smaller when the keyboard is open and bigger when the keyboard is close.

usableHeightSansKeyboard is the total screen height.

Using the total screen height to subtract the currently available screen height, that height difference is how much space is taken up. (possibly by the keyboard)

If keyboard is open, we get a big difference.

If keyboard is close, normally we get 0.

4. Reset Height and request layout

if (heightDifference > (usableHeightSansKeyboard/4)) {
// keyboard probably just became visible
frameLayoutParams.height = usableHeightSansKeyboard - heightDifference;
} else {
// keyboard probably just became hidden
frameLayoutParams.height = usableHeightSansKeyboard;
}
mChildOfContent.requestLayout();
usableHeightPrevious = usableHeightNow;

The frameLayoutParams has our layout’s height property, by manually updating its height and requesting a new layout. We force adjustResize to take affect.

This should solve most problem for full screen mode with adjustResize

Most, not all?

Yup, it still has its own problem. The purposed way to detect keyboard close is just when the height difference does not reach 1/4 of the screen.

Notice our onGlobalLayout listener get trigger not only when keyboard open or close but also on the bottom navigation bar appear or disappear.

Some phone, like the motorola XT1097, will disable the full screen mode after opening the soft keyboard. This mean the bottom navigation bar will appear and cause our height to change. It does not reach 1/4 of the screen and our listener think the keyboard just closed. (which it is not)

So lets patch the 5497 patch?

Since we are using the heightDifference greater than 1/4 of the screen to determine soft keyboard open, we can use the heightDifference less than negative 1/4 of the screen to detect the keyboard just closed.

If previously keyboard is open, available screen height is small. Now the keyboard is close, the available screen height is big. heightDifference is previous height subtract current height, (small minus big) so on keyboard close the heightDifference should be negative.

I have example here. Look at my possiblyResizeChildOfContent method, how I use heightDifference to determine keyboard close.

Problem: How to pan the page up more

The given adjustPan is weak. It only pan the page just high enough to not block the focused editText. This is bad user experience, ideally we want to pan the page high up more to reveal more content below the editText. Again, Google didn’t give us a way to control how much to pan up.

default adjustPan

This StackOverflow post asked the same question, but eventually avoided the problem. Today we are solving it directly: how to pan the page up more.

Custom Pan Up

we can manually shift up and down our layout view to create the pan up effect

On any view, we can use the scrollTo method to to shift the page up.

Now is just to calculate how much distance to shift the page up. Let say we want to bring the current focused editText up to 1/4 of the screen height, then a large space below the editText will be showing.

So the distance to scroll is:

Distance to scroll = Where I currently am - where I want to be

After we scroll the distance, “Where I currently am” will be at the position of “Where I want to be”.

When we want to shift up, (may be detected from keyboard open or edittext gain focus) we call this handleShiftUpmethod.

We can call this method anywhere, just make sure to pass in current focusedView and mChildOfContent is the root layout

absY is “Where I currently am”

If absY is already above 1/4 of the screen, there is no need to shift up anymore. But if it is below 1/4 of the screen, we call scrollTo on the root layout to shift it up to 1/4.

public void handleShiftUp(View focusedView) {

if (focusedView == null){
Toast.makeText(activity, "focusedView is null", Toast.LENGTH_SHORT).show();
return;
}

int[] location = new int[2];
focusedView.getLocationInWindow(location);
int absY = location[1];

int oneFourth = totalScreenHeight/4;

if (absY > oneFourth){

int distanceToScroll = absY - oneFourth + currentlyScrolled;
currentlyScrolled = distanceToScroll;
mChildOfContent.scrollTo(0,distanceToScroll);
Toast.makeText(activity, "Shift up " + distanceToScroll, Toast.LENGTH_SHORT).show();
}
}

To shift down, scroll the root view back to 0

public void handleShiftDown() {
currentlyScrolled = 0;
mChildOfContent.scrollTo(0,0);
}

My own example here. If you don’t want 1/4, change it accordingly.

Problem: How to pan up more in full screen mode with AdjustResize

So who on earth need such requirement?

  • You have a layout that is not resizable and you don’t want to wrap it inside a scrollView
  • You need full screen mode
  • You want it to work with adjustPan or adjustResize and with or without full screen mode
  • You cannot change your requirement

-The project I am working on have all layout dynamically generated, I don’t have access to the view ids and all views are fixed distance in constraint layout.
- Boss want full screen mode.
- The pop up library I am using require adjustResize to work correctly.

So eventually I created this. A combination of the AndroidBug5497Workaround with modified behavior to adjustPan instead.

The reason I write these kind of articles is to potentially help anyone facing the same problem. If I can save you 1 minute, then this article is worthwhile to me

--

--

Yat Man, Wong
Yat Man, Wong

Written by Yat Man, Wong

Android developer, problem solver, real man in training

Responses (1)