Catch soft keyboard show/hidden events in Android

Android offers you an overwhelming api loaded with tons of overridable functions representing typical events you may face as a mobile developer. That is the reason because you are not usually worry about catching and handling a new event that you have never meet before, you guess, normally right, those smart kids from Mountain View implemented an event handler sometime ago. Well, this is not the case of soft keyboard. It can be easily show and hide with convenient methods but there is not an implemented way of catching when user choose to hide it. Due to this limitations I coded a simple snippet that I hope it will be useful. It is available here. It is pretty easy to use.

/*
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
	}
 
	@Override
	public void onSoftKeyboardShow() 
	{
		// Code here
	}	
});
	
/*
Open or close the soft keyboard programatically
*/
softKeyboard.openSoftKeyboard();
softKeyboard.closeSoftKeyboard();

/*
SoftKeyboard can catch keyboard events when an EditText gains focus and keyboard appears
*/

/* Prevent memory leaks:
*/
@Override
public void onDestroy()
{
    super.onDestroy();
    softKeyboard.unRegisterSoftKeyboardCallback();
}

Besides this, you must add this line to your activity reference in your manifest.xml.

android:windowSoftInputMode="adjustResize"

While I were coding this snippet I encountered with another snipper which is pretty awesome. It is basically the same, involves subclassing your main layout but I think is a very elegant solution. Use which solution fits better with your needs. Happy coding!

UPDATE (06/15/14): This snippet has been updated with some improvements but It can be used in the same straightforward way and it handles keyboard events from EditText too
This snippet was too specific and it could not catch open keyboard events from EditText, it only worked if you opened and closed keyboard with provided functions and EditText opens keyboard as default behavior. Obviously this was a heavy shortcoming I did not notice before. So here it is my fix: The constructor of SoftKeyboard has been changed from:

public SoftKeyboard(View layout, InputMethodManager im)

to:

public SoftKeyboard(ViewGroup layout, InputMethodManager im)

This means it is necessary to pass a reference of main layout now. Maybe it is more strict now but it is necessary for something we are going to see next and, really, it was not working with whatever view.

SoftKeyboard class, when instantiated, it is going to try to get a reference, if possible, of every EditText available. This will allow it to handle focus changes when a EditText is selected and keyboard appears.

private void initEditTexts()
{
    editTextList = new ArrayList<EditText>();
    int childCount = layout.getChildCount();
    for(int i=0;i<=childCount -1;i++)
    {
        View v = layout.getChildAt(i);
        if(v instanceof EditText)
	    {
	       EditText editText = (EditText) v;
	       editText.setOnFocusChangeListener(this);
	       editText.setCursorVisible(false);
	       editTextList.add(editText);
	    }
    }
}

SoftKeyboard implements the interface View.OnFocusChangeListener to handle properly focus changes on our EditTexts.

@Override
public void onFocusChange(View v, boolean hasFocus) 
{   
    if(hasFocus && !isKeyboardShow)
    {
        layoutBottom = getLayoutCoordinates();
        softKeyboardThread.keyboardOpened();
        isKeyboardShow = true;
        tempView = v;
    }
}

I noticed the resize process when keyboard appears through an EditText gaining focus has some differences and it was breaking the logic of the thread which is checking the bottom position of the layout. So i added this silly loop to handle it.

// When keyboard is opened from EditText, initial bottom location is greater than layoutBottom
// and at some moment equals layoutBottom.
// That broke the previous logic, so I added this new loop to handle this.
while(currentBottomLocation >= layoutBottom)
{
    currentBottomLocation = getLayoutCoordinates();
}

There are some minor changes too but I think these are the most important changes. I have tested it but nothing is 100% bugs-free. I would love read your feedback about this modification.

UPDATE (10/07/14): Thanks to Francesco Verheye(verheye.francesco@gmail.com) this snippet can handle EditTexts attached to nested subViews. initEditText modifications:

private void initEditTexts(ViewGroup viewgroup) 
{
    if(editTextList == null)
        editTextList = new ArrayList<EditText>();
		
    int childCount = viewgroup.getChildCount();
    for(int i=0; i<= childCount-1;i++) 
    {
        View v = viewgroup.getChildAt(i);

        if(v instanceof ViewGroup) 
        {
	    initEditTexts((ViewGroup) v);
	}

        if(v instanceof EditText) 
        {
            EditText editText = (EditText) v;
            editText.setOnFocusChangeListener(this);
            //editText.setCursorVisible(false);
            editTextList.add(editText);
        }
    }
}