Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
public class SoftInputAssist {
private View rootView;
private ViewGroup contentContainer;
private ViewTreeObserver viewTreeObserver;
private ViewTreeObserver.OnGlobalLayoutListener listener = () -> possiblyResizeChildOfContent();
private Rect contentAreaOfWindowBounds = new Rect();
private FrameLayout.LayoutParams rootViewLayout;
private int usableHeightPrevious = 0;
public SoftInputAssist(Activity activity) {
contentContainer = (ViewGroup) activity.findViewById(android.R.id.content);
rootView = contentContainer.getChildAt(0);
rootViewLayout = (FrameLayout.LayoutParams) rootView.getLayoutParams();
}
public void onPause() {
if (viewTreeObserver.isAlive()) {
viewTreeObserver.removeOnGlobalLayoutListener(listener);
}
}
public void onResume() {
if (viewTreeObserver == null || !viewTreeObserver.isAlive()) {
viewTreeObserver = rootView.getViewTreeObserver();
}
viewTreeObserver.addOnGlobalLayoutListener(listener);
}
public void onDestroy() {
rootView = null;
contentContainer = null;
viewTreeObserver = null;
}
private void possiblyResizeChildOfContent() {
contentContainer.getWindowVisibleDisplayFrame(contentAreaOfWindowBounds);
int usableHeightNow = contentAreaOfWindowBounds.height();
if (usableHeightNow != usableHeightPrevious) {
rootViewLayout.height = usableHeightNow;
rootView.layout(contentAreaOfWindowBounds.left, contentAreaOfWindowBounds.top, contentAreaOfWindowBounds.right, contentAreaOfWindowBounds.bottom);
rootView.requestLayout();
usableHeightPrevious = usableHeightNow;
}
}
}
@ikun03

This comment has been minimized.

Copy link

@ikun03 ikun03 commented Apr 26, 2018

Worked excellently.

@untungs

This comment has been minimized.

Copy link

@untungs untungs commented Aug 2, 2018

Anyone knows why it flickers when the keyboard is opened/closed? It's like the toolbar jumps down for a split second.
I'm using support library v28.0.0-alpha1.

@jpvs0101

This comment has been minimized.

Copy link

@jpvs0101 jpvs0101 commented Sep 10, 2018

How to use () -> possiblyResizeChildOfContent() without lambda, because lambda need Java 8, but I am using Java 7!

@CoolMind

This comment has been minimized.

Copy link

@CoolMind CoolMind commented Oct 17, 2018

@jpvs0101, I think, it is:

private ViewTreeObserver.OnGlobalLayoutListener listener;

public SoftInputAssist(Activity activity) {
    ...
    listener = new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            possiblyResizeChildOfContent();
        }
    };
}

Also in Activity you should use (sorry, in Kotlin):

    override fun onResume() {
        super.onResume()
        softInputAssist.onResume()
    }

    override fun onPause() {
        softInputAssist.onPause()
        super.onPause()
    }

    override fun onDestroy() {
        softInputAssist.onDestroy()
        super.onDestroy()
    }

where softInputAssist = new SoftInputAssist(this);.

@CoolMind

This comment has been minimized.

Copy link

@CoolMind CoolMind commented Oct 17, 2018

In Kotlin (see also https://stackoverflow.com/a/42261118/2914140).

class SoftInputAssist(activity: Activity) {
    private var rootView: View? = null
    private var contentContainer: ViewGroup? = null
    private var viewTreeObserver: ViewTreeObserver? = null
    private val listener: () -> Unit
    private val contentAreaOfWindowBounds = Rect()
    private val rootViewLayout: FrameLayout.LayoutParams
    private var usableHeightPrevious = 0

    init {
        contentContainer = activity.findViewById(android.R.id.content) as ViewGroup
        rootView = contentContainer!!.getChildAt(0)
        rootViewLayout = rootView!!.layoutParams as FrameLayout.LayoutParams
        listener = { possiblyResizeChildOfContent() }
    }

    fun onResume() {
        if (viewTreeObserver == null || viewTreeObserver?.isAlive == false) {
            viewTreeObserver = rootView?.viewTreeObserver
        }

        viewTreeObserver?.addOnGlobalLayoutListener(listener)
    }

    fun onPause() {
        if (viewTreeObserver?.isAlive == true) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                viewTreeObserver?.removeOnGlobalLayoutListener(listener)
            } else {
                //noinspection deprecation
                viewTreeObserver?.removeGlobalOnLayoutListener(listener)
            }
        }
    }

    fun onDestroy() {
        rootView = null
        contentContainer = null
        viewTreeObserver = null
    }

    private fun possiblyResizeChildOfContent() {
        contentContainer?.getWindowVisibleDisplayFrame(contentAreaOfWindowBounds)
        val usableHeightNow = contentAreaOfWindowBounds.height()

        if (usableHeightNow != usableHeightPrevious) {
            rootViewLayout.height = usableHeightNow
            // Change the bounds of the root view to prevent gap between keyboard and content, and top of content positioned above top screen edge.
            rootView?.layout(contentAreaOfWindowBounds.left, contentAreaOfWindowBounds.top,
                contentAreaOfWindowBounds.right, contentAreaOfWindowBounds.bottom)
            rootView?.requestLayout()

            usableHeightPrevious = usableHeightNow
        }
    }
}
@shobhitpuri

This comment has been minimized.

Copy link

@shobhitpuri shobhitpuri commented Dec 10, 2018

Anyone knows why it flickers when the keyboard is opened/closed? It's like the toolbar jumps down for a split second.
I'm using support library v28.0.0-alpha1.

Yaa, I see the flickering even with 27.1.1

@andreynovikov

This comment has been minimized.

Copy link

@andreynovikov andreynovikov commented Dec 11, 2018

Worked for me after I have replaced

int usableHeightNow = contentAreaOfWindowBounds.height();

with

int usableHeightNow = contentAreaOfWindowBounds.bottom;

otherwise I had a large gap at the bottom of the screen.

@BackPackerDz

This comment has been minimized.

Copy link

@BackPackerDz BackPackerDz commented May 5, 2020

Not working.

@Fauzdar1

This comment has been minimized.

Copy link

@Fauzdar1 Fauzdar1 commented May 15, 2020

Not working, I'm using ConstraintLayout and it's doing nothing, no change at all.

@grennis

This comment has been minimized.

Copy link
Owner Author

@grennis grennis commented May 16, 2020

This gist is over 3 years old and hacking around limitations in the framework at the time. I am not surprised to hear it doesn’t work properly anymore. I believe similar functionality is now available in androidx libs. I should probably just delete this.

@tonyshkurenko

This comment has been minimized.

Copy link

@tonyshkurenko tonyshkurenko commented Jun 2, 2020

For those, who found it not working, use solution in this comment: https://gist.github.com/grennis/2e3cd5f7a9238c59861015ce0a7c5584#gistcomment-2783151

@mahbub-java

This comment has been minimized.

Copy link

@mahbub-java mahbub-java commented Jun 19, 2020

fantastic. work still greatly in 2020. Two problem has remain. Flickering and bottom height. bottom height solve by int usableHeightNow = contentAreaOfWindowBounds.bottom; still has 10 dp gap in android pie but lower version it works.

@bhardwaj49

This comment has been minimized.

Copy link

@bhardwaj49 bhardwaj49 commented Jun 26, 2020

This gist is over 3 years old and hacking around limitations in the framework at the time. I am not surprised to hear it doesn’t work properly anymore. I believe similar functionality is now available in androidx libs. I should probably just delete this.

For me with this solution, i see flicker on the action bar as well as it some what works as adjustResize, where it's bringing other views with input field above keyboard.

My layout is like this ->
Custom tool bar
Webview (containing the input field at the bottom of view)
Bottom bar(having some tab views as options)

I don't want the bottom bar to come above the keyboard when i click on input field within the webview, but with this solution bottom bar also jumps up the keyboard.

Note: I have used adjustPan with the activity as softInputMode, also if i use only adjustResize it does work very similar to what i see with adjustPan & this solution.

@grennis Can you please guide with the solution which androidx libs provides as you have mentioned?

@grennis

This comment has been minimized.

Copy link
Owner Author

@grennis grennis commented Jun 26, 2020

@bhardwaj49 I think they mentioned it on the android developer backstage podcast, they said it will be a new framework feature but would likely have jetpack backport support.

@many-cat

This comment has been minimized.

Copy link

@many-cat many-cat commented Jul 17, 2020

The Activity can be set to adjustNothing in the AndroidManifest.xml. https://github.com/siebeprojects/samples-keyboardheight

@tsutsuku

This comment has been minimized.

Copy link

@tsutsuku tsutsuku commented Aug 26, 2020

This gist is over 3 years old and hacking around limitations in the framework at the time. I am not surprised to hear it doesn’t work properly anymore. I believe similar functionality is now available in androidx libs. I should probably just delete this.

For me with this solution, i see flicker on the action bar as well as it some what works as adjustResize, where it's bringing other views with input field above keyboard.

My layout is like this ->
Custom tool bar
Webview (containing the input field at the bottom of view)
Bottom bar(having some tab views as options)

I don't want the bottom bar to come above the keyboard when i click on input field within the webview, but with this solution bottom bar also jumps up the keyboard.

Note: I have used adjustPan with the activity as softInputMode, also if i use only adjustResize it does work very similar to what i see with adjustPan & this solution.

@grennis Can you please guide with the solution which androidx libs provides as you have mentioned?

try replace
rootView.layout(contentAreaOfWindowBounds.left, contentAreaOfWindowBounds.top, contentAreaOfWindowBounds.right, contentAreaOfWindowBounds.bottom);
to
rootView.layout(contentAreaOfWindowBounds.left - (int)contentContainer.getX(), contentAreaOfWindowBounds.top - (int)contentContainer.getY(), contentAreaOfWindowBounds.right - (int)contentContainer.getX(), contentAreaOfWindowBounds.bottom - (int)contentContainer.getY());

@pedromarta

This comment has been minimized.

Copy link

@pedromarta pedromarta commented Oct 21, 2020

@mahbub-java @bhardwaj49 Were you able to find a final solution for the status bar flicker? Still experiencing the same issue here.

@CristianMG

This comment has been minimized.

Copy link

@CristianMG CristianMG commented Nov 10, 2020

It's very frustrating have to do tricks because of framework's limitations. Any other solution?

@seanyc4

This comment has been minimized.

Copy link

@seanyc4 seanyc4 commented Dec 29, 2020

How do we remove these window adjustments when moving to a new fragment? It works perfectly in the fragment where I have fullscreen mode but when I navigate back to the previous fragment which isnt full screen the bottom nav view is now hidden behind the device navigation. I guess whatever insets are being done in the full screen mode are carrying over to the prevouis fragment which doesnt require any window/inset modifications

@khleong94

This comment has been minimized.

Copy link

@khleong94 khleong94 commented Mar 16, 2021

I am able to implement this and the status bar is not flickering anymore, need to let it run in Handler when calling "possiblyResizeChildOfContent".
Not sure whether this works on anyone else, but give it a try.
The code below is in Kotlin.

class SoftInputAssist(activity: Activity) {
    private var contentContainer: ViewGroup?
    private var rootView: View?
    private val rootViewLayout: FrameLayout.LayoutParams?

    private val contentAreaOfWindowBounds: Rect = Rect()
    private var viewTreeObserver: ViewTreeObserver? = null
    private var usableHeightPrevious = 0

    private val listener = OnGlobalLayoutListener {
        possiblyResizeChildOfContent()
    }

    init {
        contentContainer = activity.contentRootView as? ViewGroup
        rootView = contentContainer?.getChildAt(0)
        rootViewLayout = rootView?.layoutParams as? FrameLayout.LayoutParams
    }

    fun onPause() {
        if (viewTreeObserver != null && viewTreeObserver!!.isAlive) {
            viewTreeObserver?.removeOnGlobalLayoutListener(listener)
        }
    }

    fun onResume() {
        viewTreeObserver = rootView?.viewTreeObserver

        if (viewTreeObserver != null) {
            viewTreeObserver?.addOnGlobalLayoutListener(listener)
        }
    }

    fun onDestroy() {
        contentContainer = null
        rootView = null
        viewTreeObserver = null
    }

    private fun possiblyResizeChildOfContent() {
        runOnMainThread {
            contentContainer?.getWindowVisibleDisplayFrame(contentAreaOfWindowBounds)
            val usableHeightNow: Int = contentAreaOfWindowBounds.bottom

            if (usableHeightNow != usableHeightPrevious) {
                rootViewLayout?.height = usableHeightNow

                rootView?.layout(
                    contentAreaOfWindowBounds.left - (contentContainer?.x?.toInt() ?: 0),
                    contentAreaOfWindowBounds.top - (contentContainer?.y?.toInt() ?: 0),
                    contentAreaOfWindowBounds.right - (contentContainer?.x?.toInt() ?: 0),
                    contentAreaOfWindowBounds.bottom - (contentContainer?.y?.toInt() ?: 0)
                )

                rootView?.requestLayout()
                usableHeightPrevious = usableHeightNow
            }
        }
    }
}

val Activity.contentRootView: View?
    get() = window?.decorView?.findViewById(android.R.id.content) ?: findViewById(android.R.id.content)

private object ContextHandler {
    val handler = Handler(Looper.getMainLooper())
}

fun runOnMainThread(action: () -> Unit) {
    ContextHandler.handler.post {
        action()
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment