Android 中一个 activity 的软键盘打开和关闭监听器

173

我有一个Activity,其中有5个EditText。当用户单击第一个EditText时,软键盘会打开以在其中输入一些值。当软键盘打开并且用户单击第一个EditText,以及当软键盘从同一个EditText通过返回按钮关闭时,我想将其他一些View的可见性设置为Gone。然后,我希望将另一些View的可见性设置为可见。

在Android中,是否有任何侦听器、回调或任何技巧可以在从单击第一个EditText打开软键盘时使用?


2
不,不存在这样的监听器。有一些技巧可以实现您正在尝试的内容。以下是一个可能的方法:如何在Android中发送指针事件 - Vikram
尝试为您的第一个编辑文本添加监听器。 - Tejas
@Tejas,我不确定你在谈论哪个监听器。 - N Sharma
@Williams - 我在说的是addTextChangedListener。Manu也指定了一种使用Focus Listener的方法。 - Tejas
看这个链接,它会完美地工作。https://dev59.com/WHI95IYBdhLWcg3wyRM0 - Chandramouli
显示剩余10条评论
32个回答

123

使用强大的KeyboardVisibilityEvent库,轻松搞定。

KeyboardVisibilityEvent.setEventListener(
    getActivity(),
    new KeyboardVisibilityEventListener() {
        @Override
        public void onVisibilityChanged(boolean isOpen) {
            // Ah... at last. do your thing :)
        }
    });

Yasuhiro SHIMIZU的制作人员名单


1
这样做不行,因为键盘的高度是不固定的,而且这个库中的高度设置为100dp。 - milosmns
18
仍然是硬编码的。多窗口?三星分屏视图?画中画模式?还有一个最小的一行键盘,将在100dp以下。这里没有万能解决方案... - milosmns
3
因为没有一个万能的解决方案来解决这个问题,所以这似乎是最容易实现的方法,只需返回您实际想要工作的代码即可 :) - Machine Tribe
1
@Pelanes 完全不是这样。只需要看一些未解决的问题就可以了。 - Tim
3
很令人沮丧的是,Android没有本地解决方案来实现这样基本的功能!Google似乎在新版本的Android上塞入了许多不必要的垃圾,但却没有解决这样重要的缺陷... - doctorram
显示剩余6条评论

99

只有当您的活动在清单中设置为adjustResize时,此方法才有效。
您可以使用布局侦听器来查看活动的根布局是否因键盘而调整大小。

我为自己的活动使用以下基类:

public class BaseActivity extends Activity {
    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();

            LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(BaseActivity.this);

            if(heightDiff <= contentViewTop){
                onHideKeyboard();

                Intent intent = new Intent("KeyboardWillHide");
                broadcastManager.sendBroadcast(intent);
            } else {
                int keyboardHeight = heightDiff - contentViewTop;
                onShowKeyboard(keyboardHeight);

                Intent intent = new Intent("KeyboardWillShow");
                intent.putExtra("KeyboardHeight", keyboardHeight);
                broadcastManager.sendBroadcast(intent);
            }
        }
    };

    private boolean keyboardListenersAttached = false;
    private ViewGroup rootLayout;

    protected void onShowKeyboard(int keyboardHeight) {}
    protected void onHideKeyboard() {}

    protected void attachKeyboardListeners() {
        if (keyboardListenersAttached) {
            return;
        }

        rootLayout = (ViewGroup) findViewById(R.id.rootLayout);
        rootLayout.getViewTreeObserver().addOnGlobalLayoutListener(keyboardLayoutListener);

        keyboardListenersAttached = true;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();

        if (keyboardListenersAttached) {
            rootLayout.getViewTreeObserver().removeGlobalOnLayoutListener(keyboardLayoutListener);
        }
    }
}
以下示例演示如何在键盘显示时隐藏视图,并在键盘隐藏时再次显示它。
XML布局:
<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/rootLayout"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical">              

    <ScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        >

        <!-- omitted for brevity -->

    </ScrollView>

    <LinearLayout android:id="@+id/bottomContainer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >

        <!-- omitted for brevity -->

    </LinearLayout>

</LinearLayout>

并且这个活动:

public class TestActivity extends BaseActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test_activity);

        attachKeyboardListeners();
    }

    @Override
    protected void onShowKeyboard(int keyboardHeight) {
        // do things when keyboard is shown
        bottomContainer.setVisibility(View.GONE);
    }

    @Override
    protected void onHideKeyboard() {
        // do things when keyboard is hidden
        bottomContainer.setVisibility(View.VISIBLE);
    }        
}

4
+1 是我问题的完美解决方案。 - N Sharma
22
嗨,你在 Window.ID_ANDROID_CONTENT 上使用了 getTop()。对我来说 getTop() 不起作用。这里它总是0,在这种情况下使用 getHeight() 才能正常工作。 - Daniele Segato
1
你从哪里得到 rootLayout = (ViewGroup) findViewById(R.id.rootLayout); 这段代码的? - CommonSenseCode
1
无论我是打开还是关闭键盘,它总是调用onShowKeyboard方法,对我来说不起作用。我正在使用findViewById(android.R.id.content),也许这就是问题所在? - McSullivan
5
@tsig您的+100解决方案取决于特定的屏幕。在平板电脑和高密度手机上失败了。我使用的是设备高度的百分之十作为校正值。这意味着如果视图高度低于屏幕高度的10%,则键盘会弹出;否则键盘将关闭。这是我的onGlobalLayout中的contentViewTop值:contentViewTop =(getWindow()。getDecorView()。getBottom()/ 10) - ilker
显示剩余10条评论

83

正如Vikram在评论中指出的那样,检测软键盘是否已显示或已消失只能通过一些丑陋的技巧来实现。

也许只需在编辑文本上设置焦点监听器:

yourEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
        if (hasFocus) {
            //got focus
        } else {
            //lost focus
        }
   }
});

47
假设我点击EditText,然后将调用setOnFocusChangeListener监听器,然后我按返回键,键盘会关闭,但没有点击其他视图,现在我再次点击已经获得焦点的相同EditText,那么会发生什么? - N Sharma
3
我不完全确定,但我怀疑 onFocusChange() 不会被调用。 - Manuel Allenspach
5
不要看这个答案,因为它说的是不同的东西,即使我也不明白。 - N Sharma
9
无论如何,这对我没用...当软键盘隐藏时,EditText上没有任何焦点变化...所以我无法从这个监听器中获得通知。 - Chinese Cat
3
如果用户只是关闭了键盘(向下箭头),焦点仍然停留在文本编辑框上,但键盘已关闭。 - Choletski
显示剩余3条评论

59

对于活动:

    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
                }
            }
        });

对于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...

                }
            }
        });

4
使用它进行活动时,我将其与屏幕大小进行比较,而不是与视图进行比较。效果很好。 - Roee
最好将heightDiff与dp中的高度进行比较,而不是像素。它可能会有很大的变化。 - Leo DroidCoder
在清单文件中需要添加 android:windowSoftInputMode="adjustResize" 吗? - LiuWenbin_NO.
android:windowSoftInputMode="adjustResize" android:configChanges="orientation|keyboard|keyboardHidden" - M Singh Karnawat
它在我这里可以运行,但我有一个问题。它会消耗很多资源吗? - Chinese Cat

36
Jaap的答案不适用于AppCompatActivity。相反,获取状态栏、导航栏等的高度并与应用程序窗口大小进行比较。

像这样:


    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);
        }
    }
};

看起来运行得相当不错,只有一个例外:在分屏模式下会出现故障。否则它很棒。 - MCLLC

23

你可以尝试一下:

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;
        }
    });
}

13

我迟到了,但我刚刚发现了一个非常方便的依赖项。使用它,您可以检查键盘的可见性,并使用一行代码随时隐藏或显示键盘。

implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:3.0.0-RC2'

然后,您只需使用此代码段来检查键盘的可见性。

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();
}
});

如果您想在任何时候隐藏/显示键盘,那么您只需编写以下其中一行代码即可实现。

        UIUtil.showKeyboard(this,edittext_to_be_focused);
        UIUtil.hideKeyboard(this);

这些天我一直遇到这个错误...之前它是可以工作的。原因是:java.lang.ClassNotFoundException: 在路径上没有找到类"net.yslibrary.android.keyboardvisibilityevent.KeyboardVisibilityEventListener":DexPathList[[zip文件"/data/app/iquO8KrlQd-nWuV82FYhTA==/com.vzw.sampleappforgyde-rMZn0uD205pWrw8PzNOQ0Q==/base.apk"],nativeLibraryDirectories=[/data/app/iquO8KrlQd-nWuV82FYhTA==/com.vzw.sampleappforgyde-rMZn0uD205pWrw8PzNOQ0Q==/lib/arm64, /system/lib64, /system/system_ext/lib64]]。 - Aditya Patil

6

在 Kotlin 的 fragment 中使用是一种常见的用例,使用 KeyboardVisibilityEvent 库非常简单。

在 build.gradle 中:

implementation 'net.yslibrary.keyboardvisibilityevent:keyboardvisibilityevent:3.0.0-RC2'

在 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()
        }
    })
}

源代码和致谢


检测到键盘打开,但无法检测到键盘关闭。 - Johann
你试过了吗?对我来说有效。尝试使用 if (!isOpen) {} - F.Mysir

6
以下代码对我有效:
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
                }
            }
        }
    });

1
什么是mainLayout?当文本字段位于视图顶部时,heightDiff始终为0。 - Md Imran Choudhury

5
你可以使用我的 Rx 扩展函数(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()
}

例子:

(context as AppCompatActivity)
                    .keyboardVisibilityChanges()
                    .subscribeBy { isKeyboardOpen ->
                        // your logic
                    }

对我不起作用。找不到 dip()getScreenHeight() 方法。 - Marcin Kunert
@MarcinKunert 这只是一个扩展函数,可以帮助您将像素转换为 dp,并获取屏幕高度。如果您想要,我可以给您提供这样的函数示例。 - Vlad
GenericLifecycleObserver已被弃用?有什么解决方案吗? - Zainal Fahrudin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接