256

I would like to alter the layout based on whether the virtual keyboard is shown or not. I've searched the API and various blogs but can't seem to find anything useful.

Is it possible?

Thanks!

Premal Khetani
  • 3,175
  • 1
  • 26
  • 58
Sander Versluys
  • 72,737
  • 23
  • 84
  • 91
  • 2
    possible duplicate of [Android EditText, soft keyboard show/hide event?](http://stackoverflow.com/questions/3793093/android-edittext-soft-keyboard-show-hide-event) – Pentium10 Nov 30 '10 at 10:42

21 Answers21

82

2020 Update

This is now possible:

On Android 11, you can do

view.setWindowInsetsAnimationCallback(object : WindowInsetsAnimation.Callback {
    override fun onEnd(animation: WindowInsetsAnimation) {
        super.onEnd(animation)
        val showingKeyboard = view.rootWindowInsets.isVisible(WindowInsets.Type.ime())
        // now use the boolean for something
    }
})

You can also listen to the animation of showing/hiding the keyboard and do a corresponding transition.

I recommend reading Android 11 preview and the corresponding documentation

Before Android 11

However, this work has not been made available in a Compat version, so you need to resort to hacks.

You can get the window insets and if the bottom insets are bigger than some value you find to be reasonably good (by experimentation), you can consider that to be showing the keyboard. This is not great and can fail in some cases, but there is no framework support for that.

This is a good answer on this exact question https://stackoverflow.com/a/36259261/372076. Alternatively, here's a page giving some different approaches to achieve this pre Android 11:

https://developer.salesforce.com/docs/atlas.en-us.noversion.service_sdk_android.meta/service_sdk_android/android_detecting_keyboard.htm


Note

This solution will not work for soft keyboards and onConfigurationChanged will not be called for soft (virtual) keyboards.


You've got to handle configuration changes yourself.

http://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange

Sample:

// from the link above
@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

    
    // Checks whether a hardware keyboard is available
    if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
        Toast.makeText(this, "keyboard visible", Toast.LENGTH_SHORT).show();
    } else if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_YES) {
        Toast.makeText(this, "keyboard hidden", Toast.LENGTH_SHORT).show();
    }
}

Then just change the visibility of some views, update a field, and change your layout file.

Pedro Loureiro
  • 11,436
  • 2
  • 31
  • 37
  • 4
    @shiami try `newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO` ~Chris – cimnine Aug 07 '11 at 23:17
  • 3
    This only works if you've registered the activity to listen to the configChanges you want in the AndroidManifest. – Raul Agrait Aug 18 '11 at 23:58
  • 79
    please update your answer and tell that it doesn't work for soft keyboard. I wasted my half day trying your code. And then saw these comments. – Shirish Herwade Aug 22 '13 at 05:19
  • 2
    does this work in a fragment as well? I have tried this solution but onConfigurationChanged() does not seem to be called at all. – faizal Dec 14 '13 at 15:30
  • yes it works in fragment..just need to declare the configChanges attribute in the activity in the manifest and override onConfigurationChanged() in the fragment class. – faizal Dec 14 '13 at 16:07
  • 1
    I have tried this with `android:configChanges="orientation|keyboardHidden"` in `AndroidManifest.xml` and `onConfigurationChanged()` indeed does not get fired. This is within `android.support.v7.app.ActionBarActivity` – Muxa Jun 11 '14 at 23:55
  • 19
    This is not working for "virtual" keyboards which was the original question. – brummfondel Jun 18 '14 at 08:49
  • 1
    Backing with shiami,Shiri Hrw,brummfondel, this solution doesnot work for soft keyboard. Also, per Muxa,`onConfigurationChanged()` doesnot even be called on softkeyboard launch. – nmxprime Jun 19 '14 at 10:08
  • 19
    Well, the question was about the SOFT KEYBOARD, why is the accepted answer about an hardware keyboard? -1 ! – Denys Vitali Apr 14 '16 at 12:24
  • lets notice that Android sdk21 (lolipop) hasn't method to handle that callback it even if You use WIndowInsectsCompact, works only following `decorView.viewTreeObserver.addOnGlobalLayoutListener(...)` – murt Feb 11 '21 at 13:16
  • 2
    For devices prior to Android 11 `androidx.core:core-ktx:1.5.0` provides compatibity solution: `ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { _, insets -> val isKeyboardVisible = insets.isVisible(WindowInsetsCompat.Type.ime()) insets }` Make sure to remove listener properly – Serhii Petrenko Jul 21 '21 at 09:08
  • `Object is not abstract and does not implement abstract base class member public abstract fun onProgress(p0: WindowInsets, p1: (Mutable)List): WindowInsets defined in android.view.WindowInsetsAnimation.Callback` – user924 Sep 30 '21 at 09:30
60

I did this way:

Add OnKeyboardVisibilityListener interface.

public interface OnKeyboardVisibilityListener {
    void onVisibilityChanged(boolean visible);
}

HomeActivity.java:

public class HomeActivity extends Activity implements OnKeyboardVisibilityListener {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_sign_up);
    // Other stuff...
    setKeyboardVisibilityListener(this);
}

private void setKeyboardVisibilityListener(final OnKeyboardVisibilityListener onKeyboardVisibilityListener) {
    final View parentView = ((ViewGroup) findViewById(android.R.id.content)).getChildAt(0);
    parentView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

        private boolean alreadyOpen;
        private final int defaultKeyboardHeightDP = 100;
        private final int EstimatedKeyboardDP = defaultKeyboardHeightDP + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 48 : 0);
        private final Rect rect = new Rect();

        @Override
        public void onGlobalLayout() {
            int estimatedKeyboardHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, EstimatedKeyboardDP, parentView.getResources().getDisplayMetrics());
            parentView.getWindowVisibleDisplayFrame(rect);
            int heightDiff = parentView.getRootView().getHeight() - (rect.bottom - rect.top);
            boolean isShown = heightDiff >= estimatedKeyboardHeight;

            if (isShown == alreadyOpen) {
                Log.i("Keyboard state", "Ignoring global layout change...");
                return;
            }
            alreadyOpen = isShown;
            onKeyboardVisibilityListener.onVisibilityChanged(isShown);
        }
    });
}


@Override
public void onVisibilityChanged(boolean visible) {
    Toast.makeText(HomeActivity.this, visible ? "Keyboard is active" : "Keyboard is Inactive", Toast.LENGTH_SHORT).show();
  }
}

Hope this would help you.

Hiren Patel
  • 52,124
  • 21
  • 173
  • 151
  • 1
    Thanks, worked for me! If you want just adjust your RecyclerView, see solution here: https://stackoverflow.com/a/43204258/373106 – David Papirov Oct 30 '17 at 13:19
  • 1
    Perfect reusable implementation, worked into Activity or Fragment, thanks – Pelanes Nov 27 '17 at 09:35
  • @DavidPapirov, you pasted a link to a RecyclerView, but not mentioned about it here. – CoolMind Jul 18 '18 at 09:31
  • I tried all other solutions but at the end only this worked. thanks! – aligur Jan 07 '20 at 11:43
  • Note : If in manifest we specify adjustPan or adjustResize then only it works otherwise it doesn't work. – Nigam Patro Sep 25 '20 at 12:32
  • one more improvement could be removing of global layout listener in onDestroy of activity. Something like this: `if (layoutListener != null) { final View parentView = ((ViewGroup)findViewById(android.R.id.content)).getChildAt(0); parentView.getViewTreeObserver().removeOnGlobalLayoutListener(layoutListener); }` – MSerg Feb 02 '21 at 21:24
  • @Hiren Patel This is excellent - thank you! Can I ask you where "100" comes from for defaultKeyboardHeightDP? Is this the minimum keyboard height for any android device? And what about the additional 48 for devices >= Lollipop? Where does this data come from? Thank you! <3 – karlingen Apr 28 '21 at 06:58
59

This may not be the most effective solution. But this worked for me every time... I call this function where ever i need to listen to the softKeyboard.

boolean isOpened = false;

public void setListenerToRootView() {
    final View activityRootView = getWindow().getDecorView().findViewById(android.R.id.content);
    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {

            int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
            if (heightDiff > 100) { // 99% of the time the height diff will be due to a keyboard.
                Toast.makeText(getApplicationContext(), "Gotcha!!! softKeyboardup", 0).show();

                if (isOpened == false) {
                    //Do two things, make the view top visible and the editText smaller
                }
                isOpened = true;
            } else if (isOpened == true) {
                Toast.makeText(getApplicationContext(), "softkeyborad Down!!!", 0).show();
                isOpened = false;
            }
        }
    });
}

Note: This approach will cause issues if the user uses a floating keyboard.

erakitin
  • 11,437
  • 5
  • 44
  • 49
amalBit
  • 12,041
  • 6
  • 77
  • 94
38

If you want to handle show/hide of IMM (virtual) keyboard window from your Activity, you'll need to subclass your layout and override onMesure method(so that you can determine the measured width and the measured height of your layout). After that set subclassed layout as main view for your Activity by setContentView(). Now you'll be able to handle IMM show/hide window events. If this sounds complicated, it's not that really. Here's the code:

main.xml

   <?xml version="1.0" encoding="utf-8"?>
   <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal" >
        <EditText
             android:id="@+id/SearchText" 
             android:text="" 
             android:inputType="text"
             android:layout_width="fill_parent"
             android:layout_height="34dip"
             android:singleLine="True"
             />
        <Button
             android:id="@+id/Search" 
             android:layout_width="60dip"
             android:layout_height="34dip"
             android:gravity = "center"
             />
    </LinearLayout>

Now inside your Activity declare subclass for your layout (main.xml)

    public class MainSearchLayout extends LinearLayout {

    public MainSearchLayout(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.main, this);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.d("Search Layout", "Handling Keyboard Window shown");

        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        final int actualHeight = getHeight();

        if (actualHeight > proposedheight){
            // Keyboard is shown

        } else {
            // Keyboard is hidden
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

You can see from the code that we inflate layout for our Activity in subclass constructor

inflater.inflate(R.layout.main, this);

And now just set content view of subclassed layout for our Activity.

public class MainActivity extends Activity {

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        MainSearchLayout searchLayout = new MainSearchLayout(this, null);

        setContentView(searchLayout);
    }

    // rest of the Activity code and subclassed layout...

}
Nebojsa Tomcic
  • 608
  • 5
  • 7
  • 3
    I need to investigate further but I have my doubts as to whether this would work in my case for small dialog on a large screen device for which the layout measurements would not be affected by presence of a keyboard. – PJL Mar 12 '13 at 12:06
  • 4
    It doesn't work for android:windowSoftInputMode="adjustPan". I wanted that my screen shouldn't get shrinked after soft keyboard appears. Can you please tell any fix so that it works even for adjustPan – Shirish Herwade Aug 22 '13 at 05:25
  • This is not working,it always goes to the else portion here if (actualHeight > proposedheight){ // Keyboard is shown } else { // Keyboard is hidden } – Aamirkhan Mar 20 '14 at 07:07
  • You can also use a Custom View with that same idea, follows an example https://gist.github.com/juliomarcos/8ca307cd7eca607c8547 – Julio Rodrigues Mar 07 '15 at 04:33
  • This solution is working great for me. Tested it on 5" and old 3" devices and it does exactly what it's supposed to do. I only use the "keyboard is shown" part and it fires when the softkeys are done popping up in portrait mode; exactly as expected. That means there are no odd events and normal EditText behaviour in landscape (fullscreen text field) is not handled by this. – easytarget Aug 19 '15 at 21:04
  • 1
    Won't work for Activities set with `android:windowSoftInputMode="adjustPan"`, or `adjustResize` with a fullscreen window, as the layout is never resized. – Ionoclast Brigham Aug 21 '15 at 03:07
29

Like @amalBit's answer, register a listener to global layout and calculate the difference of dectorView's visible bottom and its proposed bottom, if the difference is bigger than some value(guessed IME's height), we think IME is up:

    final EditText edit = (EditText) findViewById(R.id.edittext);
    edit.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (keyboardShown(edit.getRootView())) {
                Log.d("keyboard", "keyboard UP");
            } else {
                Log.d("keyboard", "keyboard Down");
            }
        }
    });

private boolean keyboardShown(View rootView) {

    final int softKeyboardHeight = 100;
    Rect r = new Rect();
    rootView.getWindowVisibleDisplayFrame(r);
    DisplayMetrics dm = rootView.getResources().getDisplayMetrics();
    int heightDiff = rootView.getBottom() - r.bottom;
    return heightDiff > softKeyboardHeight * dm.density;
}

the height threshold 100 is the guessed minimum height of IME.

This works for both adjustPan and adjustResize.

alexhilton
  • 566
  • 7
  • 10
23

Based on the Code from Nebojsa Tomcic I've developed the following RelativeLayout-Subclass:

import java.util.ArrayList;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;

public class KeyboardDetectorRelativeLayout extends RelativeLayout {

    public interface IKeyboardChanged {
        void onKeyboardShown();
        void onKeyboardHidden();
    }

    private ArrayList<IKeyboardChanged> keyboardListener = new ArrayList<IKeyboardChanged>();

    public KeyboardDetectorRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
    }

    public KeyboardDetectorRelativeLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public KeyboardDetectorRelativeLayout(Context context) {
        super(context);
    }

    public void addKeyboardStateChangedListener(IKeyboardChanged listener) {
        keyboardListener.add(listener);
    }

    public void removeKeyboardStateChangedListener(IKeyboardChanged listener) {
        keyboardListener.remove(listener);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        final int actualHeight = getHeight();

        if (actualHeight > proposedheight) {
            notifyKeyboardShown();
        } else if (actualHeight < proposedheight) {
            notifyKeyboardHidden();
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    private void notifyKeyboardHidden() {
        for (IKeyboardChanged listener : keyboardListener) {
            listener.onKeyboardHidden();
        }
    }

    private void notifyKeyboardShown() {
        for (IKeyboardChanged listener : keyboardListener) {
            listener.onKeyboardShown();
        }
    }

}

This works quite fine... Mark, that this solution will just work when Soft Input Mode of your Activity is set to "WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE"

a.ch.
  • 8,285
  • 5
  • 40
  • 53
Stefan
  • 1,007
  • 1
  • 11
  • 32
  • 3
    It doesn't work for android:windowSoftInputMode="adjustPan". I wanted that my screen shouldn't get shrinked after soft keyboard appears. Can you please tell any fix so that it works even for adjustPan – Shirish Herwade Aug 22 '13 at 05:26
  • 1
    This one also won't work for Activities set with `android:windowSoftInputMode="adjustPan"`, or `adjustResize` with a fullscreen window, as the layout is never resized. – Ionoclast Brigham Aug 21 '15 at 03:12
  • it triger quite a few times. – zionpi May 26 '16 at 06:55
15

Pre-android 11 solution:

As androidx.core 1.5.0 is released, this is what i do to listen to keyboard show/hide event in pre-android 11 devices.

gradle:

implementation "androidx.core:core-ktx:1.5.0"

fragment:

   override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val view  = activity?.window?.decorView ?: return
        ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
            val showingKeyboard = insets.isVisible(WindowInsetsCompat.Type.ime())
            if(showingKeyboard){
                //do something
            }
            insets
        }
    }

make sure you remove the listener when the view destroy to avoid memory leak. This solution also only works when the software input mode is adjustResize, setOnApplyWindowInsetsListener won't trigger if it is adjustPan, if anyone has an idea on how to make it work with adjustPan, please share.

Note that according to the doc,

* When running on devices with API Level 29 and before, the returned value is an
* approximation based on the information available. This is especially true for the {@link
* Type#ime IME} type, which currently only works when running on devices with SDK level 23
* and above.
*

insets.isVisible(ime) should only work on devices with SDK level above 23

Jonathan
  • 299
  • 2
  • 5
12

Nebojsa's solution almost worked for me. When I clicked inside a multi-line EditText it knew the keyboard was displayed, but when I started typing inside the EditText, the actualHeight and proposedHeight were still the same so it did not know they keyboard was still displayed. I made a slight modification to store the max height and it works fine. Here is the revised subclass:

public class CheckinLayout extends RelativeLayout {

    private int largestHeight;

    public CheckinLayout(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.checkin, this);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        largestHeight = Math.max(largestHeight, getHeight());

        if (largestHeight > proposedheight)
            // Keyboard is shown
        else
            // Keyboard is hidden

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}
Gary Foster
  • 171
  • 2
  • 3
11

I solve this by overriding onKeyPreIme(int keyCode, KeyEvent event) in my custom EditText.

@Override
public boolean onKeyPreIme(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
        //keyboard will be hidden
    }
}
jakub
  • 3,576
  • 3
  • 29
  • 55
10

Not sure if anyone post this. Found this solution simple to use!. The SoftKeyboard class is on gist.github.com. But while keyboard popup/hide event callback we need a handler to properly do things on UI:

/*
Somewhere else in your code
*/
RelativeLayout mainLayout = findViewById(R.layout.main_layout); // You must use your root layout
InputMethodManager im = (InputMethodManager) getSystemService(Service.INPUT_METHOD_SERVICE);

/*
Instantiate and pass a callback
*/
SoftKeyboard softKeyboard;
softKeyboard = new SoftKeyboard(mainLayout, im);
softKeyboard.setSoftKeyboardCallback(new SoftKeyboard.SoftKeyboardChanged()
{

    @Override
    public void onSoftKeyboardHide() 
    {
        // Code here
        new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    // Code here will run in UI thread
                    ...
                }
            });
    }

    @Override
    public void onSoftKeyboardShow() 
    {
        // Code here
        new Handler(Looper.getMainLooper()).post(new Runnable() {
                @Override
                public void run() {
                    // Code here will run in UI thread
                    ...
                }
            });

    }   
});
Robert
  • 1,660
  • 22
  • 39
  • here is the Git to get SoftkeyBoard "https://gist.github.com/felHR85/6070f643d25f5a0b3674#file-softkeyboard-java" – douarbou May 23 '15 at 15:08
4

I have sort of a hack to do this. Although there doesn't seem to be a way to detect when the soft keyboard has shown or hidden, you can in fact detect when it is about to be shown or hidden by setting an OnFocusChangeListener on the EditText that you're listening to.

EditText et = (EditText) findViewById(R.id.et);
et.setOnFocusChangeListener(new View.OnFocusChangeListener()
    {
        @Override
        public void onFocusChange(View view, boolean hasFocus)
        {
            //hasFocus tells us whether soft keyboard is about to show
        }
    });

NOTE: One thing to be aware of with this hack is that this callback is fired immediately when the EditText gains or loses focus. This will actually fire right before the soft keyboard shows or hides. The best way I've found to do something after the keyboard shows or hides is to use a Handler and delay something ~ 400ms, like so:

EditText et = (EditText) findViewById(R.id.et);
et.setOnFocusChangeListener(new View.OnFocusChangeListener()
    {
        @Override
        public void onFocusChange(View view, boolean hasFocus)
        {
            new Handler().postDelayed(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        //do work here
                    }
                }, 400);
        }
    });
poisondminds
  • 427
  • 4
  • 8
  • 1
    It doesn't work, otherwise. `OnFocusChangeListener` only tells whether `EditText` has focus after the state changed. But the `IME` may be hidden when `EditText` has focus, how to detect this case? – DysaniazzZ Aug 03 '17 at 03:53
  • This is the simplest solution. @DysaniazzZ, to detect when the IME is hidden with the "back" key, override `onKeyPreIme` on the `EditText` and watch for `keyCode==KEYCODE_BACK` – jake n Apr 19 '22 at 22:09
3

Sander ,I believe you are trying to show the view blocked by the soft keyboard. Try this http://android-developers.blogspot.com/2009/04/updating-applications-for-on-screen.html.

100rabh
  • 6,156
  • 5
  • 27
  • 41
  • The first trackback on this URL points at RussenReaktor's weblog which mentions adding android:windowSoftInputMode="adjustPan" to the Activity's manifest. This worked great for me. – JohnnyLambada Oct 31 '11 at 16:32
3

In spite of what the most upvoted solution on this page says, there is a ViewCompat version of setWindowInsetsAnimationCallback that works all the way to Android 21.

So now, the approach in that solution works all the way back to 21.

Source: https://developer.android.com/reference/androidx/core/view/ViewCompat#setWindowInsetsAnimationCallback(android.view.View,androidx.core.view.WindowInsetsAnimationCompat.Callback)

Hisham Hijjawi
  • 1,803
  • 2
  • 17
  • 27
2

I have solve the problem on single line textview back coding.

package com.helpingdoc;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;

public class MainSearchLayout extends LinearLayout {
    int hieght = 0;
    public MainSearchLayout(Context context, AttributeSet attributeSet) {

        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.main, this);


    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.d("Search Layout", "Handling Keyboard Window shown");
       if(getHeight()>hieght){
           hieght = getHeight();
       }
        final int proposedheight = MeasureSpec.getSize(heightMeasureSpec);
        final int actualHeight = getHeight();
        System.out.println("....hieght = "+ hieght);
        System.out.println("....actualhieght = "+ actualHeight);
        System.out.println("....proposedheight = "+ proposedheight);
        if (actualHeight > proposedheight){
            // Keyboard is shown


        } else if(actualHeight<proposedheight){
            // Keyboard is hidden

        }

        if(proposedheight == hieght){
             // Keyboard is hidden
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}
AlexVogel
  • 10,601
  • 10
  • 61
  • 71
user2462737
  • 208
  • 2
  • 11
  • 2
    It doesn't work for android:windowSoftInputMode="adjustPan". I wanted that my screen shouldn't get shrinked after soft keyboard appears. Can you please tell any fix so that it works even for adjustPan – Shirish Herwade Aug 22 '13 at 05:34
  • When function hide/show then this listener method is calling twice or thrice. I don't what is exactly the problem. – Jagveer Singh Aug 14 '14 at 14:01
2

You can also check for first DecorView's child bottom padding. It will be set to non-zero value when keyboard is shown.

@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    View view = getRootView();
    if (view != null && (view = ((ViewGroup) view).getChildAt(0)) != null) {
        setKeyboardVisible(view.getPaddingBottom() > 0);
    }
    super.onLayout(changed, left, top, right, bottom);
}
MatrixDev
  • 1,432
  • 15
  • 20
2

The above answer of @Filipkowicz's works fine in Android API < 30. Since Android API 30 we should use setWindowInsetsAnimationCallback. So below answer combines the both method in order to work API 21 - 30.

private fun isKeyboardVisible(insets: WindowInsets): Boolean {
    val insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets)
    val systemWindow = insetsCompat.systemWindowInsets
    val rootStable = insetsCompat.stableInsets
    if (systemWindow.bottom > rootStable.bottom) {
        // This handles the adjustResize case on < API 30, since
        // systemWindow.bottom is probably going to be the IME
        return true
    }
    return false
}

@JvmStatic
@BindingAdapter("goneWhenKeyboardVisible")
fun View.goneWhenKeyboardVisible() {
    if (isRPlus()) {
        setWindowInsetsAnimationCallback(object :
            WindowInsetsAnimation.Callback(DISPATCH_MODE_STOP) {
            override fun onProgress(
                insets: WindowInsets,
                runningAnimations: MutableList<WindowInsetsAnimation>
            ): WindowInsets {
                return insets
            }

            override fun onStart(
                animation: WindowInsetsAnimation,
                bounds: WindowInsetsAnimation.Bounds
            ): WindowInsetsAnimation.Bounds {
                if (isVisible)
                    isVisible = !rootWindowInsets.isVisible(WindowInsets.Type.ime())
                return super.onStart(animation, bounds)
            }

            override fun onEnd(animation: WindowInsetsAnimation) {
                super.onEnd(animation)
                if (!isVisible)
                    isVisible = !rootWindowInsets.isVisible(WindowInsets.Type.ime())
            }
        })
    } else {
        setOnApplyWindowInsetsListener { _, insets ->
            isVisible = !isKeyboardVisible(insets)
            insets
        }
    }
}
Dilanka Laksiri
  • 408
  • 3
  • 12
1

Hide|Show events for keyboard can be listened through simple hack in OnGlobalLayoutListener :

 final View activityRootView = findViewById(R.id.top_root);
        activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            public void onGlobalLayout() {
                int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();

                if (heightDiff > 100) {
                    // keyboard is up
                } else {
                    // keyboard is down
                }
            }
        });

Here activityRootView is your Activity's root view.

Varun Verma
  • 692
  • 7
  • 13
  • my heightDiff is 160 on the start and 742 with kbd, so I had have to introduce and set initialHeightDiff on the start – djdance Jul 26 '19 at 19:47
0

using viewTreeObserver for easily get the keyboard event.

layout_parent.viewTreeObserver.addOnGlobalLayoutListener {
            val r = Rect()
            layout_parent.getWindowVisibleDisplayFrame(r)
            if (layout_parent.rootView.height - (r.bottom - r.top) > 100) { // if more than 100 pixels, its probably a keyboard...
                Log.e("TAG:", "keyboard open")
            } else {
                Log.e("TAG:", "keyboard close")
            }
        }

** layout_parent is your view like edit_text.parent

Geet Thakur
  • 1,966
  • 1
  • 16
  • 23
0

what I did is created simple binding to hide view when keyboard is visible. Solution is based on current AndroidX implementation for WindowInsetsCompat which is still in beta (androidx core 1.5) - source

private fun isKeyboardVisible(insets: WindowInsets): Boolean {
    val insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets)
    val systemWindow = insetsCompat.systemWindowInsets
    val rootStable = insetsCompat.stableInsets
    if (systemWindow.bottom > rootStable.bottom) {
        // This handles the adjustResize case on < API 30, since
        // systemWindow.bottom is probably going to be the IME
        return true
    }
    return false
}

@BindingAdapter("goneWhenKeyboardVisible")
fun View.goneWhenKeyboardVisible(enabled: Boolean) {
    if (enabled) {
        setOnApplyWindowInsetsListener { view, insets ->
            visibility = if (isKeyboardVisible(insets)) GONE else VISIBLE
            insets
        }
    } else {
        setOnApplyWindowInsetsListener(null)
        visibility = VISIBLE
    }
}

usage:

<FrameLayout
                android:id="@+id/bottom_toolbar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:goneWhenKeyboardVisible="@{true}"
                />
Filipkowicz
  • 639
  • 6
  • 17
0

For Kotlin users, this is inspired from this answer in the comments, you can create an extension:

import android.graphics.Rect
import android.view.View
import android.widget.TextView

const val SOFT_KEYBOARD_HEIGHT = 100

fun TextView.addOnKeyboardVisibilityListener(
    onKeyboardShown: () -> Unit,
    onKeyboardHidden: () -> Unit,
) {
    viewTreeObserver.addOnGlobalLayoutListener {
        if(rootView.isKeyboardShown()) {
            onKeyboardShown()
        } else {
            onKeyboardHidden()
        }
    }
}

fun View.isKeyboardShown(): Boolean =
    Rect().let { rect ->
        rootView.getWindowVisibleDisplayFrame(rect)
        rect
    }.let {
        rootView.bottom - it.bottom
    }.let { heightDiff ->
        heightDiff > SOFT_KEYBOARD_HEIGHT * rootView.resources.displayMetrics.density
    }

and you can use it as:

editText.addOnKeyboardVisibilityListener(
    onKeyboardShown = {
        // TODO
    },
    onKeyboardHidden = {
        // TODO
    }
)
Yasser AKBBACH
  • 539
  • 5
  • 7
-2

Nebojsa Tomcic's answer wasn't helpful for me. I have RelativeLayout with TextView and AutoCompleteTextView inside it. I need to scroll the TextView to the bottom when the keyboard is showed and when it's hidden. To accomplish this I overrode onLayout method and it works fine for me.

public class ExtendedLayout extends RelativeLayout
{
    public ExtendedLayout(Context context, AttributeSet attributeSet)
    {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.main, this);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        super.onLayout(changed, l, t, r, b);

        if (changed)
        {
            int scrollEnd = (textView.getLineCount() - textView.getHeight() /
                textView.getLineHeight()) * textView.getLineHeight();
            textView.scrollTo(0, scrollEnd);
        }
    }
}
Bart
  • 19,692
  • 7
  • 68
  • 77
Mode
  • 11