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!
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!
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://mcmap.net/q/109497/-how-to-capture-the-quot-virtual-keyboard-show-hide-quot-event-in-android. Alternatively, here's a page giving some different approaches to achieve this pre Android 11:
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.
newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO
~Chris –
Econah android:configChanges="orientation|keyboardHidden"
in AndroidManifest.xml
and onConfigurationChanged()
indeed does not get fired. This is within android.support.v7.app.ActionBarActivity
–
Exterminatory onConfigurationChanged()
doesnot even be called on softkeyboard launch. –
Biron decorView.viewTreeObserver.addOnGlobalLayoutListener(...)
–
Wallflower 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 –
Deglutinate Object is not abstract and does not implement abstract base class member public abstract fun onProgress(p0: WindowInsets, p1: (Mutable)List<WindowInsetsAnimation!>): WindowInsets defined in android.view.WindowInsetsAnimation.Callback
–
Asyndeton 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.
if (layoutListener != null) { final View parentView = ((ViewGroup)findViewById(android.R.id.content)).getChildAt(0); parentView.getViewTreeObserver().removeOnGlobalLayoutListener(layoutListener); }
–
Geochronology 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.
android:windowSoftInputMode="adjustPan"
, or adjustResize
with a fullscreen window, as the layout is never resized. –
Grovergroves 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...
}
android:windowSoftInputMode="adjustPan"
, or adjustResize
with a fullscreen window, as the layout is never resized. –
Grovergroves 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.
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"
android:windowSoftInputMode="adjustPan"
, or adjustResize
with a fullscreen window, as the layout is never resized. –
Grovergroves 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
ViewCompat.setOnApplyWindowInsetsListener
changes color of system navigation controls! –
Asyndeton view = activity?.window?.decorView
! –
Ebonize 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);
}
}
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
}
}
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
...
}
});
}
});
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);
}
});
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? –
Steib onKeyPreIme
on the EditText
and watch for keyCode==KEYCODE_BACK
–
Aft 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.
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.
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);
}
}
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);
}
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
}
}
}
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.
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
}
)
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
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}"
/>
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);
}
}
}
© 2022 - 2024 — McMap. All rights reserved.