I have an Activity
where there are 5 EditText
s. When the user clicks on the first EditText
, the soft keyboard opens to enter some value in it. I want to set some other View
's visibility to Gone
when the soft keyboard opens and also when the user clicks on the first EditText
and also when the soft keyboard closes from the same EditText
on the back button press. Then I want to set some other View
's visibility to visible.
Is there any listener or callback or any hack for when the soft keyboard opens from a click on the first EditText
in Android?
This question is related to
android
android-activity
android-edittext
android-softkeyboard
Use this class,
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
public class SoftKeyboard implements View.OnFocusChangeListener
{
private static final int CLEAR_FOCUS = 0;
private ViewGroup layout;
private int layoutBottom;
private InputMethodManager im;
private int[] coords;
private boolean isKeyboardShow;
private SoftKeyboardChangesThread softKeyboardThread;
private List<EditText> editTextList;
private View tempView; // reference to a focused EditText
public SoftKeyboard(ViewGroup layout, InputMethodManager im)
{
this.layout = layout;
keyboardHideByDefault();
initEditTexts(layout);
this.im = im;
this.coords = new int[2];
this.isKeyboardShow = false;
this.softKeyboardThread = new SoftKeyboardChangesThread();
this.softKeyboardThread.start();
}
public void openSoftKeyboard()
{
if(!isKeyboardShow)
{
layoutBottom = getLayoutCoordinates();
im.toggleSoftInput(0, InputMethodManager.SHOW_IMPLICIT);
softKeyboardThread.keyboardOpened();
isKeyboardShow = true;
}
}
public void closeSoftKeyboard()
{
if(isKeyboardShow)
{
im.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
isKeyboardShow = false;
}
}
public void setSoftKeyboardCallback(SoftKeyboardChanged mCallback)
{
softKeyboardThread.setCallback(mCallback);
}
public void unRegisterSoftKeyboardCallback()
{
softKeyboardThread.stopThread();
}
public interface SoftKeyboardChanged
{
public void onSoftKeyboardHide();
public void onSoftKeyboardShow();
}
private int getLayoutCoordinates()
{
layout.getLocationOnScreen(coords);
return coords[1] + layout.getHeight();
}
private void keyboardHideByDefault()
{
layout.setFocusable(true);
layout.setFocusableInTouchMode(true);
}
/*
* InitEditTexts now handles EditTexts in nested views
* Thanks to Francesco Verheye ([email protected])
*/
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(true);
editTextList.add(editText);
}
}
}
/*
* OnFocusChange does update tempView correctly now when keyboard is still shown
* Thanks to Israel Dominguez ([email protected])
*/
@Override
public void onFocusChange(View v, boolean hasFocus)
{
if(hasFocus)
{
tempView = v;
if(!isKeyboardShow)
{
layoutBottom = getLayoutCoordinates();
softKeyboardThread.keyboardOpened();
isKeyboardShow = true;
}
}
}
// This handler will clear focus of selected EditText
private final Handler mHandler = new Handler()
{
@Override
public void handleMessage(Message m)
{
switch(m.what)
{
case CLEAR_FOCUS:
if(tempView != null)
{
tempView.clearFocus();
tempView = null;
}
break;
}
}
};
private class SoftKeyboardChangesThread extends Thread
{
private AtomicBoolean started;
private SoftKeyboardChanged mCallback;
public SoftKeyboardChangesThread()
{
started = new AtomicBoolean(true);
}
public void setCallback(SoftKeyboardChanged mCallback)
{
this.mCallback = mCallback;
}
@Override
public void run()
{
while(started.get())
{
// Wait until keyboard is requested to open
synchronized(this)
{
try
{
wait();
} catch (InterruptedException e)
{
e.printStackTrace();
}
}
int currentBottomLocation = getLayoutCoordinates();
// There is some lag between open soft-keyboard function and when it really appears.
while(currentBottomLocation == layoutBottom && started.get())
{
currentBottomLocation = getLayoutCoordinates();
}
if(started.get())
mCallback.onSoftKeyboardShow();
// 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 && started.get())
{
currentBottomLocation = getLayoutCoordinates();
}
// Now Keyboard is shown, keep checking layout dimensions until keyboard is gone
while(currentBottomLocation != layoutBottom && started.get())
{
synchronized(this)
{
try
{
wait(500);
} catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
currentBottomLocation = getLayoutCoordinates();
}
if(started.get())
mCallback.onSoftKeyboardHide();
// if keyboard has been opened clicking and EditText.
if(isKeyboardShow && started.get())
isKeyboardShow = false;
// if an EditText is focused, remove its focus (on UI thread)
if(started.get())
mHandler.obtainMessage(CLEAR_FOCUS).sendToTarget();
}
}
public void keyboardOpened()
{
synchronized(this)
{
notify();
}
}
public void stopThread()
{
synchronized(this)
{
started.set(false);
notify();
}
}
}
}
In Android Manifest
, android:windowSoftInputMode="adjustResize"
is necessary.
/*
Somewhere else in your code
*/
RelativeLayout mainLayout = findViewById(R.layout.main_layout); // You must use the layout root
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 easily
*/
softKeyboard.openSoftKeyboard();
softKeyboard.closeSoftKeyboard();
/* Prevent memory leaks:*/
@Override
public void onDestroy() {
super.onDestroy();
softKeyboard.unRegisterSoftKeyboardCallback();
}
P.S - Completely taken from here.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/tools"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/addresses_confirm_root_view"
android:orientation="vertical">
<---In the xml root use the id--->
final LinearLayout activityRootView = view.findViewById(R.id.addresses_confirm_root_view); activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { Rect r = new Rect(); //r will be populated with the coordinates of your view that area still visible. activityRootView.getWindowVisibleDisplayFrame(r);
int heightDiff = activityRootView.getRootView().getHeight() - r.height();
if (heightDiff > 0.25 * activityRootView.getRootView().getHeight()) {
// if more than 25% of the screen, its probably a keyboard...
onkeyboard();
} else {
//Keyboard not visible
offkeyboard();
}
}
});
Solution with extra property in Activity\Fragment, but without any hypothetical hardcoded heights (like 100 etc) . Just add OnGlobalLayoutListener to your root view and save its initial height before keyboard will be shown:
var firstLoad = true
var contentFullWeight = 0
override fun onViewCreated(layoutView: View, savedInstanceState: Bundle?) {
super.onViewCreated(layoutView, savedInstanceState)
view?.viewTreeObserver?.addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener {
if(firstLoad){
contentFullWeight = view?.height!!
firstLoad = false
}
if (view?.height!! < contentFullWeight) {
Log.d("TEZT_KEYBOARD", ">> KBD OPENED")
} else {
Log.d("TEZT_KEYBOARD", ">> KBD closed")
}
})
}
You can use my Rx extension function (Kotlin).
/**
* @return [Observable] to subscribe of keyboard visibility changes.
*/
fun AppCompatActivity.keyboardVisibilityChanges(): Observable<Boolean> {
// flag indicates whether keyboard is open
var isKeyboardOpen = false
val notifier: BehaviorSubject<Boolean> = BehaviorSubject.create()
// approximate keyboard height
val approximateKeyboardHeight = dip(100)
// device screen height
val screenHeight: Int = getScreenHeight()
val visibleDisplayFrame = Rect()
val viewTreeObserver = window.decorView.viewTreeObserver
val onDrawListener = ViewTreeObserver.OnDrawListener {
window.decorView.getWindowVisibleDisplayFrame(visibleDisplayFrame)
val keyboardHeight = screenHeight - (visibleDisplayFrame.bottom - visibleDisplayFrame.top)
val keyboardOpen = keyboardHeight >= approximateKeyboardHeight
val hasChanged = isKeyboardOpen xor keyboardOpen
if (hasChanged) {
isKeyboardOpen = keyboardOpen
notifier.onNext(keyboardOpen)
}
}
val lifeCycleObserver = object : GenericLifecycleObserver {
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event?) {
if (source.lifecycle.currentState == Lifecycle.State.DESTROYED) {
viewTreeObserver.removeOnDrawListener(onDrawListener)
source.lifecycle.removeObserver(this)
notifier.onComplete()
}
}
}
viewTreeObserver.addOnDrawListener(onDrawListener)
lifecycle.addObserver(lifeCycleObserver)
return notifier
.doOnDispose {
viewTreeObserver.removeOnDrawListener(onDrawListener)
lifecycle.removeObserver(lifeCycleObserver)
}
.onTerminateDetach()
.hide()
}
Example:
(context as AppCompatActivity)
.keyboardVisibilityChanges()
.subscribeBy { isKeyboardOpen ->
// your logic
}
Unfortunately I do not have a sufficiently high reputation to comment on Jaap van Hengstum's answer. But I read a few comments of people, having the problem that contentViewTop
is always 0
and that onShowKeyboard(...)
is always called.
I had the same issue and figured out the problem I had. I used an AppCompatActivity
instead of a 'normal' Activity
. In this case Window.ID_ANDROID_CONTENT
refers to an ContentFrameLayout
and not to the FrameLayout
with the right top-value. In my case it was fine to use the 'normal' Activity
, if you have to use another activity-type (I just tested the AppCompatActivity
, maybe it's also an issue with other acitivy-types like the FragmentActivity
), you have to access the FrameLayout
, which is an ancestor of the ContentFrameLayout
.
Piece of cake with the awesome KeyboardVisibilityEvent library
KeyboardVisibilityEvent.setEventListener(
getActivity(),
new KeyboardVisibilityEventListener() {
@Override
public void onVisibilityChanged(boolean isOpen) {
// Ah... at last. do your thing :)
}
});
Credits for Yasuhiro SHIMIZU
This will work without any need to change your activity's android:windowSoftInputMode
step 1: extend EditText class and override these two:
@Override
public void setOnEditorActionListener(final OnEditorActionListener listener) {
mEditorListener = listener;
super.setOnEditorActionListener(listener);
}
@Override
public boolean onKeyPreIme(final int keyCode, final KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
if (mEditorListener != null) {
mEditorListener.onEditorAction(this, android.R.id.closeButton, event);
}
}
return super.onKeyPreIme(keyCode, event);
}
step 2: create these two in your activity:
private void initKeyboard() {
final AppEditText editText = findViewById(R.id.some_id);
editText.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
setKeyboard(hasFocus);
}
});
editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (event == null || event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
editText.clearFocus();
}
return false;
}
});
}
public void setKeyboard(boolean isShowing) {
// do something
}
*** remember in order to make clearFocus
work, you have to make parent or first child in the parent hierarchy focusable.
setFocusableInTouchMode(true);
setFocusable(true);
Jaap's answer won't work for AppCompatActivity. Instead get the height of the Status Bar and Navigation bar etc and compare to your app's window size.
Like so:
private ViewTreeObserver.OnGlobalLayoutListener keyboardLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// navigation bar height
int navigationBarHeight = 0;
int resourceId = getResources().getIdentifier("navigation_bar_height", "dimen", "android");
if (resourceId > 0) {
navigationBarHeight = getResources().getDimensionPixelSize(resourceId);
}
// status bar height
int statusBarHeight = 0;
resourceId = getResources().getIdentifier("status_bar_height", "dimen", "android");
if (resourceId > 0) {
statusBarHeight = getResources().getDimensionPixelSize(resourceId);
}
// display window size for the app layout
Rect rect = new Rect();
getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
// screen height - (user app height + status + nav) ..... if non-zero, then there is a soft keyboard
int keyboardHeight = rootLayout.getRootView().getHeight() - (statusBarHeight + navigationBarHeight + rect.height());
if (keyboardHeight <= 0) {
onHideKeyboard();
} else {
onShowKeyboard(keyboardHeight);
}
}
};
If you can, try to extend EditText and override 'onKeyPreIme' method.
@Override
public void setOnEditorActionListener(final OnEditorActionListener listener) {
mEditorListener = listener; //keep it for later usage
super.setOnEditorActionListener(listener);
}
@Override
public boolean onKeyPreIme(final int keyCode, final KeyEvent event) {
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
if (mEditorListener != null) {
//you can define and use custom listener,
//OR define custom R.id.<imeId>
//OR check event.keyCode in listener impl
//* I used editor action because of ButterKnife @
mEditorListener.onEditorAction(this, android.R.id.closeButton, event);
}
}
return super.onKeyPreIme(keyCode, event);
}
How can you extend it:
I think, that recalculating of screen height is not 100% successfully as mentioned before. To be clear, overriding of 'onKeyPreIme' is not called on 'hide soft keyboard programatically' methods, BUT if you are doing it anywhere, you should do 'onKeyboardHidden' logic there and do not create a comprehensive solutions.
Found an accurate way of telling whether or not a keyboard when using the 'adjustResize' Soft input mode (Kotlin code)
Define a couple of activity scope variables
private var activityHeight = 0
private var keyboardOpen = false
Write the following code in onCreate
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
...
/* Grab initial screen value */
[email protected] {
val displayFrame : Rect = Rect()
[email protected](displayFrame)
activityHeight = displayFrame.height()
}
/* Check for keyboard open/close */
[email protected] { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
val drawFrame : Rect = Rect()
[email protected](drawFrame)
val currentSize = drawFrame.height()
keyboardOpen = currentSize < activityHeight
Log.v("keyboard1","$keyboardOpen $currentSize - $activityHeight")
}
}
You now have a boolean which accurately tracks whether or not the keyboard is open, do what you will
I am late but I just found a very convenient dependency out there. Using it you can check the visibility of the keyboard as well as make the keyboard "Hide" and Show Whenever you want with a single Line of Code.
implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:3.0.0-RC2'
And then you simply use this code segment to check the keyboard visibility.
KeyboardVisibilityEvent.setEventListener(this, new KeyboardVisibilityEventListener() {
@Override
public void onVisibilityChanged(boolean isOpen) {
if (isOpen)
Toast.makeText(MainActivity.this, "keyboard opened",Toast.LENGTH_SHORT).show();
else
Toast.makeText(MainActivity.this, "keyboard hidden", Toast.LENGTH_SHORT).show();
}
});
Then if you want to Hide/Show keyboard at any point of time then you can just write one of these single lines to achieve it.
UIUtil.showKeyboard(this,edittext_to_be_focused);
UIUtil.hideKeyboard(this);
at the first create a kotlin file and add these methods:
fun Activity.getRootView(): View {
return findViewById<View>(android.R.id.content)
}
fun Context.convertDpToPx(dp: Float): Float {
return TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
dp,
this.resources.displayMetrics
)
}
fun Activity.isKeyboardOpen(): Boolean {
val visibleBounds = Rect()
this.getRootView().getWindowVisibleDisplayFrame(visibleBounds)
val heightDiff = getRootView().height - visibleBounds.height()
val marginOfError = Math.round(this.convertDpToPx(50F))
return heightDiff > marginOfError
}
fun Activity.isKeyboardClosed(): Boolean {
return !this.isKeyboardOpen()
}
then create a listener class for checking the keyboard is open or not :
class KeyboardEventListener(
private val activity: AppCompatActivity,
private val callback: (isOpen: Boolean) -> Unit
) : LifecycleObserver {
private val listener = object : ViewTreeObserver.OnGlobalLayoutListener {
private var lastState: Boolean = activity.isKeyboardOpen()
override fun onGlobalLayout() {
val isOpen = activity.isKeyboardOpen()
if (isOpen == lastState) {
return
} else {
dispatchKeyboardEvent(isOpen)
lastState = isOpen
}
}
}
init {
// Dispatch the current state of the keyboard
dispatchKeyboardEvent(activity.isKeyboardOpen())
// Make the component lifecycle aware
activity.lifecycle.addObserver(this)
registerKeyboardListener()
}
private fun registerKeyboardListener() {
activity.getRootView().viewTreeObserver.addOnGlobalLayoutListener(listener)
}
private fun dispatchKeyboardEvent(isOpen: Boolean) {
when {
isOpen -> callback(true)
!isOpen -> callback(false)
}
}
@OnLifecycleEvent(value = Lifecycle.Event.ON_PAUSE)
@CallSuper
fun onLifecyclePause() {
unregisterKeyboardListener()
}
private fun unregisterKeyboardListener() {
activity.getRootView().viewTreeObserver.removeOnGlobalLayoutListener(listener)
}
}
and use it like this :
override fun onResume() {
super.onResume()
KeyboardEventListener(this) { isOpen -> // handle event }
}
I hope you find it useful.
"Jaap van Hengstum"'s answer is working for me, but there is no need to set "android:windowSoftInputMode" as he just said!
I've made it smaller(it now just detects what I want, actually an event on showing and hiding of keyboard):
private ViewTreeObserver.OnGlobalLayoutListener keyboardLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int heightDiff = rootLayout.getRootView().getHeight() - rootLayout.getHeight();
int contentViewTop = getWindow().findViewById(Window.ID_ANDROID_CONTENT).getTop();
if(heightDiff <= contentViewTop){
onHideKeyboard();
} else {
onShowKeyboard();
}
}
};
private boolean keyboardListenersAttached = false;
private ViewGroup rootLayout;
protected void onShowKeyboard() {}
protected void onHideKeyboard() {}
protected void attachKeyboardListeners() {
if (keyboardListenersAttached) {
return;
}
rootLayout = (ViewGroup) findViewById(R.id.CommentsActivity);
rootLayout.getViewTreeObserver().addOnGlobalLayoutListener(keyboardLayoutListener);
keyboardListenersAttached = true;
}
@Override
protected void onDestroy() {
super.onDestroy();
if (keyboardListenersAttached) {
rootLayout.getViewTreeObserver().removeGlobalOnLayoutListener(keyboardLayoutListener);
}
}
and just don't forget to add this
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_comments);
attachKeyboardListeners();}
For use in Kotlin inside fragment, which is a common use case it is very easy with KeyboardVisibilityEvent
library.
In build.gradle:
implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:3.0.0-RC2'
In Fragment:
activity?.let {
KeyboardVisibilityEvent.setEventListener(it,object: KeyboardVisibilityEventListener {
override fun onVisibilityChanged(isOpen: Boolean) {
if (isOpen) Toast.makeText(context,"Keyboard is opened",Toast.LENGTH_SHORT).show()
else Toast.makeText(context,"Keyboard is closed",Toast.LENGTH_SHORT).show()
}
})
}
There is a keyboard dismissed listener.
Class SearchEditText
is derived from android.widget.EditText
class. There is an interface SearchEditText.OnKeyboardDismissListener
in this class.
You can look docmentation:
https://developer.android.com/reference/androidx/leanback/widget/SearchEditText
Note. Before use SearchEditText you need to set up Gradle dependencies in the build.gradle (:app):
implementation 'androidx.leanback:leanback:1.1.0-alpha05'
Maybe someone will come in handy.
Detailed response:
import androidx.appcompat.app.AppCompatActivity;
import androidx.leanback.widget.SearchEditText;
import android.os.Bundle;
import android.widget.Toast;
public class MainActivity extends AppCompatActivity
implements SearchEditText.OnKeyboardDismissListener {
SearchEditText searchEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
searchEditText = findViewById(R.id.search_edit_text);
searchEditText.setOnKeyboardDismissListener(this);
}
/**
* Method invoked when the keyboard is dismissed.
*/
@Override
public void onKeyboardDismiss() {
Toast.makeText(this, "The listener worked", Toast.LENGTH_LONG).show();
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.leanback.widget.SearchEditText
android:id="@+id/search_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:textSize="20sp"
android:focusableInTouchMode="true"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Note: listener works with:
android:windowSoftInputMode="adjustPan"
android:windowSoftInputMode="adjustResize"
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mainactivity);
attachKeyboardListeners();
....
yourEditText1.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
yourEditText2.setVisibility(View.GONE);
yourEditText3.setVisibility(View.GONE);
yourEditText4.setVisibility(View.GONE);
yourEditText5.setVisibility(View.GONE);
} else {
yourEditText2.setVisibility(View.VISIBLE);
yourEditText3.setVisibility(View.VISIBLE);
yourEditText4.setVisibility(View.VISIBLE);
yourEditText5.setVisibility(VISIBLE);
}
}
});
}
}
This code works great nice
use this class for root view:
public class KeyboardConstraintLayout extends ConstraintLayout {
private KeyboardListener keyboardListener;
private EditText targetEditText;
private int minKeyboardHeight;
private boolean isShow;
public KeyboardConstraintLayout(Context context) {
super(context);
minKeyboardHeight = getResources().getDimensionPixelSize(R.dimen.keyboard_min_height); //128dp
}
public KeyboardConstraintLayout(Context context, AttributeSet attrs) {
super(context, attrs);
minKeyboardHeight = getResources().getDimensionPixelSize(R.dimen.keyboard_min_height); // 128dp
}
public KeyboardConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
minKeyboardHeight = getResources().getDimensionPixelSize(R.dimen.keyboard_min_height); // 128dp
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
if (!isInEditMode()) {
Activity activity = (Activity) getContext();
@SuppressLint("DrawAllocation")
Rect rect = new Rect();
getWindowVisibleDisplayFrame(rect);
int statusBarHeight = rect.top;
int keyboardHeight = activity.getWindowManager().getDefaultDisplay().getHeight() - (rect.bottom - rect.top) - statusBarHeight;
if (keyboardListener != null && targetEditText != null && targetEditText.isFocused()) {
if (keyboardHeight > minKeyboardHeight) {
if (!isShow) {
isShow = true;
keyboardListener.onKeyboardVisibility(true);
}
}else {
if (isShow) {
isShow = false;
keyboardListener.onKeyboardVisibility(false);
}
}
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
public boolean isShowKeyboard() {
return isShow;
}
public void setKeyboardListener(EditText targetEditText, KeyboardListener keyboardListener) {
this.targetEditText = targetEditText;
this.keyboardListener = keyboardListener;
}
public interface KeyboardListener {
void onKeyboardVisibility (boolean isVisible);
}
}
and set keyboard listener in activity or fragment:
rootLayout.setKeyboardListener(targetEditText, new KeyboardConstraintLayout.KeyboardListener() {
@Override
public void onKeyboardVisibility(boolean isVisible) {
}
});
As Vikram pointed out in the comments, detecting whether the softkeyboard is shown or has disappeared is only possible with some ugly hacks.
Maybe it is enough to set a focus listener on the edittext:
yourEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
//got focus
} else {
//lost focus
}
}
});
You can handle keyboard visibility by overriding two methods in your Activity: onKeyUp()
and onKeyDown()
more information in this link: https://developer.android.com/training/keyboard-input/commands
check with the below code :
XML CODE :
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinatorParent"
style="@style/parentLayoutPaddingStyle"
android:layout_width="match_parent"
android:layout_height="match_parent">
.................
</android.support.constraint.ConstraintLayout>
JAVA CODE :
//Global Variable
android.support.constraint.ConstraintLayout activityRootView;
boolean isKeyboardShowing = false;
private ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener;
android.support.constraint.ConstraintLayout.LayoutParams layoutParams;
//onCreate or onViewAttached
activityRootView = view.findViewById(R.id.coordinatorParent);
onGlobalLayoutListener = onGlobalLayoutListener();
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayoutListener);
//outside oncreate
ViewTreeObserver.OnGlobalLayoutListener onGlobalLayoutListener() {
return new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect r = new Rect();
activityRootView.getWindowVisibleDisplayFrame(r);
int screenHeight = activityRootView.getRootView().getHeight();
int keypadHeight = screenHeight - r.bottom;
if (keypadHeight > screenHeight * 0.15) { // 0.15 ratio is perhaps enough to determine keypad height.
if (!isKeyboardShowing) { // keyboard is opened
isKeyboardShowing = true;
onKeyboardVisibilityChanged(true);
}
}
else {
if (isKeyboardShowing) { // keyboard is closed
isKeyboardShowing = false;
onKeyboardVisibilityChanged(false);
}
}
}//ends here
};
}
void onKeyboardVisibilityChanged(boolean value) {
layoutParams = (android.support.constraint.ConstraintLayout.LayoutParams)topImg.getLayoutParams();
if(value){
int length = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 90, getResources().getDisplayMetrics());
layoutParams.height= length;
layoutParams.width = length;
topImg.setLayoutParams(layoutParams);
Log.i("keyboard " ,""+ value);
}else{
int length1 = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 175, getResources().getDisplayMetrics());
layoutParams.height= length1;
layoutParams.width = length1;
topImg.setLayoutParams(layoutParams);
Log.i("keyboard " ,""+ value);
}
}
@Override
public void onDetach() {
super.onDetach();
if(onGlobalLayoutListener != null) {
activityRootView.getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayoutListener);
}
}
A different approach would be to check when the user stopped typing...
When a TextEdit is in focus (user is/was typing) you could hide the views (focus listener)
and use a Handler + Runnable and a text change listener to close the keyboard (regardless of its visibility) and show the views after some delay.
The main thing to look out for would be the delay you use, which would depend on the content of these TextEdits.
Handler timeoutHandler = new Handler();
Runnable typingRunnable = new Runnable() {
public void run() {
// current TextEdit
View view = getCurrentFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
// reset focus
view.clearFocus();
// close keyboard (whether its open or not)
imm.hideSoftInputFromWindow(view.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN);
// SET VIEWS VISIBLE
}
};
editText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
// SET VIEWS GONE
// reset handler
timeoutHandler.removeCallbacks(typingRunnable);
timeoutHandler.postDelayed(typingRunnable, TYPING_TIMEOUT);
}
}
});
editText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// Reset Handler...
timeoutHandler.removeCallbacks(typingRunnable);
}
@Override
public void afterTextChanged(Editable s) {
// Reset Handler Cont.
if (editText.getText().toString().trim().length() > 0) {
timeoutHandler.postDelayed(typingRunnable, TYPING_TIMEOUT);
}
}
});
You can try it:
private void initKeyBoardListener() {
// ??????????? ???????? ??????????.
// Threshold for minimal keyboard height.
final int MIN_KEYBOARD_HEIGHT_PX = 150;
// ???? ???????? ?????? view.
// Top-level window decor view.
final View decorView = getWindow().getDecorView();
// ???????????? ?????????? ?????????. Register global layout listener.
decorView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
// ??????? ????????????? ?????? ????.
// Retrieve visible rectangle inside window.
private final Rect windowVisibleDisplayFrame = new Rect();
private int lastVisibleDecorViewHeight;
@Override
public void onGlobalLayout() {
decorView.getWindowVisibleDisplayFrame(windowVisibleDisplayFrame);
final int visibleDecorViewHeight = windowVisibleDisplayFrame.height();
if (lastVisibleDecorViewHeight != 0) {
if (lastVisibleDecorViewHeight > visibleDecorViewHeight + MIN_KEYBOARD_HEIGHT_PX) {
Log.d("Pasha", "SHOW");
} else if (lastVisibleDecorViewHeight + MIN_KEYBOARD_HEIGHT_PX < visibleDecorViewHeight) {
Log.d("Pasha", "HIDE");
}
}
// ????????? ??????? ?????? view ?? ?????????? ??????.
// Save current decor view height for the next call.
lastVisibleDecorViewHeight = visibleDecorViewHeight;
}
});
}
For Activity:
final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect r = new Rect();
activityRootView.getWindowVisibleDisplayFrame(r);
int heightDiff = view.getRootView().getHeight() - (r.bottom - r.top);
if (heightDiff > 100) {
//enter your code here
}else{
//enter code for hid
}
}
});
For Fragment:
view = inflater.inflate(R.layout.live_chat_fragment, null);
view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
Rect r = new Rect();
//r will be populated with the coordinates of your view that area still visible.
view.getWindowVisibleDisplayFrame(r);
int heightDiff = view.getRootView().getHeight() - (r.bottom - r.top);
if (heightDiff > 500) { // if more than 100 pixels, its probably a keyboard...
}
}
});
The below code is working for me,
mainLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
if (mainLayout != null) {
int heightDiff = mainLayout.getRootView().getHeight() - mainLayout.getHeight();
if (heightDiff > dpToPx(getActivity(), 200)) {
//keyboard is open
} else {
//keyboard is hide
}
}
}
});
This is not working as desired...
... have seen many use size calculations to check ...
I wanted to determine if it was open or not and I found isAcceptingText()
so this really does not answer the question as it does not address opening or closing rather more like is open or closed so it is related code that may help others in various scenarios...
in an activity
if (((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).isAcceptingText()) {
Log.d(TAG,"Software Keyboard was shown");
} else {
Log.d(TAG,"Software Keyboard was not shown");
}
in a fragment
if (((InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE)).isAcceptingText()) {
Log.d(TAG,"Software Keyboard was shown");
} else {
Log.d(TAG,"Software Keyboard was not shown");
}
For the case of adjustResize
and FragmentActivity accepted solution from @Jaap doesn't work for me.
Here is my solution:
private ViewTreeObserver.OnGlobalLayoutListener keyboardLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
private int contentDiff;
private int rootHeight;
@Override
public void onGlobalLayout() {
View contentView = getWindow().findViewById(Window.ID_ANDROID_CONTENT);
if (rootHeight != mDrawerLayout.getRootView().getHeight()) {
rootHeight = mDrawerLayout.getRootView().getHeight();
contentDiff = rootHeight - contentView.getHeight();
return;
}
int newContentDiff = rootHeight - contentView.getHeight();
if (contentDiff != newContentDiff) {
if (contentDiff < newContentDiff) {
onShowKeyboard(newContentDiff - contentDiff);
} else {
onHideKeyboard();
}
contentDiff = newContentDiff;
}
}
};
when keyboard show
rootLayout.getHeight() < rootLayout.getRootView().getHeight() - getStatusBarHeight()
is true,else hide
private boolean isKeyboardShown = false;
private int prevContentHeight = 0;
private ViewGroup contentLayout;
private ViewTreeObserver.OnGlobalLayoutListener keyboardLayoutListener =
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int contentHeight = contentLayout.getHeight();
int rootViewHeight = contentLayout.getRootView().getHeight();
if (contentHeight > 0) {
if (!isKeyboardShown) {
if (contentHeight < prevContentHeight) {
isKeyboardShown = true;
onShowKeyboard(rootViewHeight - contentHeight);
}
} else {
if (contentHeight > prevContentHeight) {
isKeyboardShown = false;
onHideKeyboard();
}
}
prevContentHeight = contentHeight;
}
}
};
I've modified the Jaap's accepted answer a bit. But in my case, there are few assumptions such as android:windowSoftInputMode=adjustResize
and the keyboard does not show up at the beginning when the app starts. And also, I assume that the screen in regard matches the parent's height.
contentHeight > 0
this check provides me to know if the regarding screen is hidden or shown to apply keyboard event listening for this specific screen. Also I pass the layout view of the regarding screen in attachKeyboardListeners(<your layout view here>)
in my main activity's onCreate()
method. Every time when the height of the regarding screen changes, I save it to prevContentHeight
variable to check later whether the keyboard is shown or hidden.
For me, so far it's been worked pretty well. I hope that it works for others too.
Source: Stackoverflow.com