如何检查Android中软件键盘的可见性?

How to check visibility of software keyboard in Android?

我需要做一个非常简单的事情 - 找出是否显示了软件键盘。 这在Android中可行吗?


NEW ANSWER于2012年1月25日补充说

自从写下面的答案后,有人告诉我ViewTreeObserver和朋友的存在,这些API自版本1以来一直潜伏在SDK中。

不需要自定义布局类型,更简单的解决方案是为活动的根视图提供已知ID,比如说@+id/activityRoot,将GlobalLayoutListener挂钩到ViewTreeObserver,然后计算活动视图根和窗口之间的大小差异尺寸:

1
2
3
4
5
6
7
8
9
10
final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();
        if (heightDiff > dpToPx(this, 200)) { // if more than 200 dp, it's probably a keyboard...
            // ... do something here
        }
     }
});

使用如下的实用程序:

1
2
3
4
public static float dpToPx(Context context, float valueInDp) {
    DisplayMetrics metrics = context.getResources().getDisplayMetrics();
    return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, valueInDp, metrics);
}

简单!

注意:
您的应用程序必须在Android Manifest android:windowSoftInputMode="adjustResize"中设置此标志,否则上述解决方案将无效。

原始答案

是的,这是可能的,但它比它应该的要困难得多。

如果我需要关心键盘何时出现并消失(这是经常),那么我所做的是将我的顶级布局类自定义为覆盖onMeasure()的类。基本逻辑是,如果布局发现自己填充的内容明显少于窗口的总面积,则可能会显示软键盘。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import android.app.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.widget.LinearLayout;

/*
 * LinearLayoutThatDetectsSoftKeyboard - a variant of LinearLayout that can detect when
 * the soft keyboard is shown and hidden (something Android can't tell you, weirdly).
 */

public class LinearLayoutThatDetectsSoftKeyboard extends LinearLayout {

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

    public interface Listener {
        public void onSoftKeyboardShown(boolean isShowing);
    }
    private Listener listener;
    public void setListener(Listener listener) {
        this.listener = listener;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height = MeasureSpec.getSize(heightMeasureSpec);
        Activity activity = (Activity)getContext();
        Rect rect = new Rect();
        activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(rect);
        int statusBarHeight = rect.top;
        int screenHeight = activity.getWindowManager().getDefaultDisplay().getHeight();
        int diff = (screenHeight - statusBarHeight) - height;
        if (listener != null) {
            listener.onSoftKeyboardShown(diff>128); // assume all soft keyboards are at least 128 pixels high
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);      
    }

    }

然后在你的Activity类中......

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyActivity extends Activity implements LinearLayoutThatDetectsSoftKeyboard.Listener {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ...
        LinearLayoutThatDetectsSoftKeyboard mainLayout = (LinearLayoutThatDetectsSoftKeyboard)findViewById(R.id.main);
        mainLayout.setListener(this);
        ...
    }


    @Override
    public void onSoftKeyboardShown(boolean isShowing) {
        // do whatever you need to do here
    }

    ...
}


所以希望这可以帮助某人。

Reuben Scratton给出的新答案非常棒且效率很高,但它确实只有在将windowSoftInputMode设置为adjustResize时才有效。如果将其设置为adjustPan,则仍无法使用其代码片段检测键盘是否可见。为了解决这个问题,我对上面的代码做了一些微小的修改。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
final View activityRootView = findViewById(R.id.activityRoot);
activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new 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.bottom - r.top);
    if (heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
        ... do something here
    }
 }
});


它一直是计算机方面的问题,但这个问题仍然令人难以置信!

所以我已经采取了上述答案,并将它们结合起来并进行了一些改进......

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public interface OnKeyboardVisibilityListener {


    void onVisibilityChanged(boolean visible);
}

public final void setKeyboardListener(final OnKeyboardVisibilityListener listener) {
    final View activityRootView = ((ViewGroup) getActivity().findViewById(android.R.id.content)).getChildAt(0);

    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

        private boolean wasOpened;

        private final int DefaultKeyboardDP = 100;

        // From @nathanielwolf answer...  Lollipop includes button bar in the root. Add height of button bar (48dp) to maxDiff
        private final int EstimatedKeyboardDP = DefaultKeyboardDP + (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? 48 : 0);

        private final Rect r = new Rect();

        @Override
        public void onGlobalLayout() {
            // Convert the dp to pixels.
            int estimatedKeyboardHeight = (int) TypedValue
                    .applyDimension(TypedValue.COMPLEX_UNIT_DIP, EstimatedKeyboardDP, activityRootView.getResources().getDisplayMetrics());

            // Conclude whether the keyboard is shown or not.
            activityRootView.getWindowVisibleDisplayFrame(r);
            int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
            boolean isShown = heightDiff >= estimatedKeyboardHeight;

            if (isShown == wasOpened) {
                Log.d("Keyboard state","Ignoring global layout change...");
                return;
            }

            wasOpened = isShown;
            listener.onVisibilityChanged(isShown);
        }
    });
}

适合我:)

注意:
如果您发现DefaultKeyboardDP不适合您的设备,请使用该值并发布评论以便每个人知道该值应该是什么...最终我们将获得适合所有设备的正确值!

有关更多详细信息,请查看Cyborg上的实施


对于迟到的答案很抱歉,但是我创建了一个小助手类来处理打开/关闭事件,通知听众和其他有用的东西,可能有人会发现它有用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
import android.graphics.Rect;
import android.view.View;
import android.view.ViewTreeObserver;

import java.util.LinkedList;
import java.util.List;

public class SoftKeyboardStateWatcher implements ViewTreeObserver.OnGlobalLayoutListener {

    public interface SoftKeyboardStateListener {
        void onSoftKeyboardOpened(int keyboardHeightInPx);
        void onSoftKeyboardClosed();
    }

    private final List<SoftKeyboardStateListener> listeners = new LinkedList<SoftKeyboardStateListener>();
    private final View activityRootView;
    private int        lastSoftKeyboardHeightInPx;
    private boolean    isSoftKeyboardOpened;

    public SoftKeyboardStateWatcher(View activityRootView) {
        this(activityRootView, false);
    }

    public SoftKeyboardStateWatcher(View activityRootView, boolean isSoftKeyboardOpened) {
        this.activityRootView     = activityRootView;
        this.isSoftKeyboardOpened = isSoftKeyboardOpened;
        activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    @Override
    public void onGlobalLayout() {
        final Rect r = new Rect();
        //r will be populated with the coordinates of your view that area still visible.
        activityRootView.getWindowVisibleDisplayFrame(r);

        final int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
        if (!isSoftKeyboardOpened && heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
            isSoftKeyboardOpened = true;
            notifyOnSoftKeyboardOpened(heightDiff);
        } else if (isSoftKeyboardOpened && heightDiff < 100) {
            isSoftKeyboardOpened = false;
            notifyOnSoftKeyboardClosed();
        }
    }

    public void setIsSoftKeyboardOpened(boolean isSoftKeyboardOpened) {
        this.isSoftKeyboardOpened = isSoftKeyboardOpened;
    }

    public boolean isSoftKeyboardOpened() {
        return isSoftKeyboardOpened;
    }

    /**
     * Default value is zero {@code 0}.
     *
     * @return last saved keyboard height in px
     */
    public int getLastSoftKeyboardHeightInPx() {
        return lastSoftKeyboardHeightInPx;
    }

    public void addSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
        listeners.add(listener);
    }

    public void removeSoftKeyboardStateListener(SoftKeyboardStateListener listener) {
        listeners.remove(listener);
    }

    private void notifyOnSoftKeyboardOpened(int keyboardHeightInPx) {
        this.lastSoftKeyboardHeightInPx = keyboardHeightInPx;

        for (SoftKeyboardStateListener listener : listeners) {
            if (listener != null) {
                listener.onSoftKeyboardOpened(keyboardHeightInPx);
            }
        }
    }

    private void notifyOnSoftKeyboardClosed() {
        for (SoftKeyboardStateListener listener : listeners) {
            if (listener != null) {
                listener.onSoftKeyboardClosed();
            }
        }
    }
}

用法示例:

1
2
3
4
5
6
final SoftKeyboardStateWatcher softKeyboardStateWatcher
    = new SoftKeyboardStateWatcher(findViewById(R.id.activity_main_layout);

// Add listener
softKeyboardStateWatcher.addSoftKeyboardStateListener(...);
// then just handle callbacks


一些改进,以避免错误地检测高密度设备上软键盘的可见性:

  • 高度差的阈值应定义为128 dp,而不是128像素。
    请参阅有关指标和网格的Google设计文档,48 dp是触摸对象的舒适尺寸,32 dp是按钮的最小尺寸。通用软键盘应包括4行按键,因此最小键盘高度应为:32 dp * 4 = 128 dp,这意味着阈值大小应通过乘以设备密度传输到像素。对于xxxhdpi设备(密度4),软键盘高度阈值应为128 * 4 = 512像素。

  • 根视图与其可见区域之间的高度差异:
    根视图高度 - 状态栏高度 - 可见框架高度=根视图底部 - 可见框架底部,因为状态栏高度等于根视图可见框架的顶部。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    private final String TAG ="TextEditor";
    private TextView mTextEditor;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_editor);
        mTextEditor = (TextView) findViewById(R.id.text_editor);
        mTextEditor.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                isKeyboardShown(mTextEditor.getRootView());
            }
        });
    }

    private boolean isKeyboardShown(View rootView) {
        /* 128dp = 32dp * 4, minimum button height 32dp and generic 4 rows soft keyboard */
        final int SOFT_KEYBOARD_HEIGHT_DP_THRESHOLD = 128;

        Rect r = new Rect();
        rootView.getWindowVisibleDisplayFrame(r);
        DisplayMetrics dm = rootView.getResources().getDisplayMetrics();
        /* heightDiff = rootView height - status bar height (r.top) - visible frame height (r.bottom - r.top) */
        int heightDiff = rootView.getBottom() - r.bottom;
        /* Threshold size: dp to pixels, multiply with display density */
        boolean isKeyboardShown = heightDiff > SOFT_KEYBOARD_HEIGHT_DP_THRESHOLD * dm.density;

        Log.d(TAG,"isKeyboardShown ?" + isKeyboardShown +", heightDiff:" + heightDiff +", density:" + dm.density
                +"root view height:" + rootView.getHeight() +", rect:" + r);

        return isKeyboardShown;
    }

  • 我花了一点时间来弄明白这一点...我运行了一些CastExceptions,但想通了你可以在layout.xml中用类的名称替换LinearLayout。

    像这样:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?xml version="1.0" encoding="UTF-8"?>
    <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent"
        xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/llMaster">

    <com.ourshoppingnote.RelativeLayoutThatDetectsSoftKeyboard android:background="@drawable/metal_background"
        android:layout_width="fill_parent" android:layout_height="fill_parent"
        android:id="@+id/rlMaster">
        <LinearLayout android:layout_width="fill_parent"
            android:layout_height="1dip" android:background="@drawable/line"></LinearLayout>

              ....

    </com.ourshoppingnote.RelativeLayoutThatDetectsSoftKeyboard>    


    </LinearLayout>

    这样你就不会遇到任何演员问题。

    ...如果您不想在每个页面上执行此操作,我建议您使用"Android中的MasterPage"。请看这里的链接:
    http://jnastase.alner.net/archive/2011/01/08/ldquomaster-pagesrdquo-in-android.aspx


    检查元素的高度是不可靠的,因为像WifiKeyboard这样的键盘高度为零。

    相反,您可以使用showSoftInput()和hideSoftInput()的回调结果来检查键盘的状态。完整的详细信息和示例代码

    https://rogerkeays.com/how-to-check-if-the-software-keyboard-is-shown-in-android


    我们的想法是,如果您需要隐藏键盘并同时检查软输入状态,请使用以下解决方案:

    1
    2
    3
    4
    public boolean hideSoftInput() {
        InputMethodManager imm = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
        return imm.hideSoftInputFromWindow(mViewPager.getWindowToken(), 0);
    }

    如果在隐藏之前显示键盘,则此方法返回true。


    而不是假设差异编码我做了类似的事情,因为我在我的应用程序中有菜单选项。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    final View root= findViewById(R.id.myrootview);
    root.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
    @Override
        public void onGlobalLayout() {
            int heightDiff = root.getRootView().getHeight() - root.getHeight();

            Rect rectgle= new Rect();
            Window window= getWindow();
            window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
            int contentViewTop=                    
              window.findViewById(Window.ID_ANDROID_CONTENT).getTop();
            if(heightDiff <= contentViewTop){
                //Soft KeyBoard Hidden
            }else{
                //Soft KeyBoard Shown
            }
         }
    });


    有一个隐藏的方法可以帮助这个,InputMethodManager.getInputMethodWindowVisibleHeight。但我不知道它隐藏的原因。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    import android.content.Context
    import android.os.Handler
    import android.view.inputmethod.InputMethodManager

    class SoftKeyboardStateWatcher(private val ctx: Context) {
      companion object {
        private const val DELAY = 10L
      }

      private val handler = Handler()
      private var isSoftKeyboardOpened: Boolean = false

      private val height: Int
        get() {
          val imm = ctx.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
          val method = imm.javaClass.getMethod("getInputMethodWindowVisibleHeight")
          method.isAccessible = true
          return method.invoke(imm) as Int
        }

      private val task: Runnable by lazy {
        Runnable {
          start()
          if (!isSoftKeyboardOpened && height > 0) {
            isSoftKeyboardOpened = true
            notifyOnSoftKeyboardOpened(height)
          } else if (isSoftKeyboardOpened && height == 0) {
            isSoftKeyboardOpened = false
            notifyOnSoftKeyboardClosed()
          }
        }
      }

      var listener: SoftKeyboardStateListener? = null

      interface SoftKeyboardStateListener {
        fun onSoftKeyboardOpened(keyboardHeightInPx: Int)
        fun onSoftKeyboardClosed()
      }

      fun start() {
        handler.postDelayed(task, DELAY)
      }

      fun stop() {
        handler.postDelayed({
          if (!isSoftKeyboardOpened) handler.removeCallbacks(task)
        }, DELAY * 10)
      }

      private fun notifyOnSoftKeyboardOpened(keyboardHeightInPx: Int) {
        listener?.onSoftKeyboardOpened(keyboardHeightInPx)
      }

      private fun notifyOnSoftKeyboardClosed() {
        listener?.onSoftKeyboardClosed()
      }
    }


    您可以使用activity的decorView观察软键盘的隐藏。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public final class SoftKeyboardUtil {
        public static final String TAG ="SoftKeyboardUtil";
        public static void observeSoftKeyBoard(Activity activity , final OnSoftKeyBoardHideListener listener){
            final View decorView = activity.getWindow().getDecorView();
            decorView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    Rect rect = new Rect();
                    decorView.getWindowVisibleDisplayFrame(rect);
                    int displayHight = rect.bottom - rect.top;
                    int hight = decorView.getHeight();
                    boolean hide = (double)displayHight / hight > 0.8 ;
                    if(Log.isLoggable(TAG, Log.DEBUG)){
                        Log.d(TAG ,"DecorView display hight ="+displayHight);
                        Log.d(TAG ,"DecorView hight ="+ hight);
                        Log.d(TAG,"softkeyboard visible =" + !hide);
                    }

                    listener.onSoftKeyBoardVisible(!hide);

                }
            });
        }



        public interface OnSoftKeyBoardHideListener{
            void onSoftKeyBoardVisible(boolean visible);
        }
    }

    还有系统插入的解决方案,但它只适用于API >= 21(Android L)。假设您有BottomNavigationView,它是LinearLayout的子级,您需要在显示键盘时隐藏它:

    1
    2
    3
    > LinearLayout
      > ContentView
      > BottomNavigationView

    您需要做的就是以这种方式扩展LinearLayout

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    public class KeyboardAwareLinearLayout extends LinearLayout {
        public KeyboardAwareLinearLayout(Context context) {
            super(context);
        }

        public KeyboardAwareLinearLayout(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
        }

        public KeyboardAwareLinearLayout(Context context,
                                         @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }

        public KeyboardAwareLinearLayout(Context context, AttributeSet attrs,
                                         int defStyleAttr, int defStyleRes) {
            super(context, attrs, defStyleAttr, defStyleRes);
        }

        @Override
        public WindowInsets onApplyWindowInsets(WindowInsets insets) {
            int childCount = getChildCount();
            for (int index = 0; index < childCount; index++) {
                View view = getChildAt(index);
                if (view instanceof BottomNavigationView) {
                    int bottom = insets.getSystemWindowInsetBottom();
                    if (bottom >= ViewUtils.dpToPx(200)) {
                        // keyboard is shown
                        view.setVisibility(GONE);
                    } else {
                        // keyboard is hidden
                        view.setVisibility(VISIBLE);
                    }
                }
            }
            return insets;
        }
    }

    这个想法是,当显示键盘时,系统插入更改为非常大的.bottom值。


    我发现@ Reuben_Scratton的方法和@ Yogesh的方法的组合似乎效果最好。结合他们的方法会得到这样的结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    final View activityRootView = findViewById(R.id.activityRoot);
    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {
        if (getResources().getConfiguration().keyboardHidden == Configuration.KEYBOARDHIDDEN_NO) { // Check if keyboard is not hidden
           // ... do something here
        }
      }
    });


    它一直是计算机方面的问题,但这个问题仍然令人难以置信!
    所以我已经采取了上述答案,并将它们结合起来并进行了一些改进......

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    public interface OnKeyboardVisibilityListener {
        void onVisibilityChanged(boolean visible);
    }

    public final void setKeyboardListener(final OnKeyboardVisibilityListener listener) {
        final View activityRootView = ((ViewGroup) getActivity().findViewById(android.R.id.content)).getChildAt(0);
        activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

            private boolean wasOpened;

        private final Rect r = new Rect();

            @Override
            public void onGlobalLayout() {
                activityRootView.getWindowVisibleDisplayFrame(r);

                int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
                boolean isOpen = heightDiff > 100;
                if (isOpen == wasOpened) {
                    logDebug("Ignoring global layout change...");
                    return;
                }

                wasOpened = isOpen;
                listener.onVisibilityChanged(isOpen);
            }
        });
    }

    这个对我有用。


    我使用了Reuban答案的略微变体,在某些情况下证明它更有用,特别是对于高分辨率设备。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    final View activityRootView = findViewById(android.R.id.content);
    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(
            new OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    int heightView = activityRootView.getHeight();
                    int widthView = activityRootView.getWidth();
                    if (1.0 * widthView / heightView > 3) {
                        //Make changes for Keyboard not visible
                    } else {
                        //Make changes for keyboard visible
                    }
                }
            });


    这些解决方案都不适用于Lollipop。在Lollipop中,activityRootView.getRootView().getHeight()包括按钮栏的高度,而测量视图则不包括。我已经采用了上面最好/最简单的解决方案来与Lollipop合作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
        final View activityRootView = findViewById(R.id.activityRoot);
    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new 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.bottom - r.top);
        Resources res = getResources();
        // The status bar is 25dp, use 50dp for assurance
        float maxDiff =
            TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, res.getDisplayMetrics());

        //Lollipop includes button bar in the root. Add height of button bar (48dp) to maxDiff
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
          float buttonBarHeight =
              TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, res.getDisplayMetrics());
          maxDiff += buttonBarHeight;
        }
        if (heightDiff > maxDiff) { // if more than 100 pixels, its probably a keyboard...
          ...do something here
        }
      }
    });


    我的答案与Kachi的答案基本相同,但我把它包装成一个很好的帮助类来清理它在整个应用程序中的使用方式。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    import android.app.Activity;
    import android.app.Fragment;
    import android.graphics.Rect;
    import android.view.View;
    import android.view.ViewTreeObserver.OnGlobalLayoutListener;

    /**
     * Detects Keyboard Status changes and fires events only once for each change
     */
    public class KeyboardStatusDetector {
        KeyboardVisibilityListener visibilityListener;

        boolean keyboardVisible = false;

        public void registerFragment(Fragment f) {
            registerView(f.getView());
        }

        public void registerActivity(Activity a) {
            registerView(a.getWindow().getDecorView().findViewById(android.R.id.content));
        }

        public KeyboardStatusDetector registerView(final View v) {
            v.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    Rect r = new Rect();
                    v.getWindowVisibleDisplayFrame(r);

                    int heightDiff = v.getRootView().getHeight() - (r.bottom - r.top);
                    if (heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
                        /** Check this variable to debounce layout events */
                        if(!keyboardVisible) {
                            keyboardVisible = true;
                            if(visibilityListener != null) visibilityListener.onVisibilityChanged(true);
                        }
                    } else {
                        if(keyboardVisible) {
                            keyboardVisible = false;
                            if(visibilityListener != null) visibilityListener.onVisibilityChanged(false);
                        }
                    }
                }
            });

            return this;
        }

        public KeyboardStatusDetector setVisibilityListener(KeyboardVisibilityListener listener) {
            visibilityListener = listener;
            return this;
        }

        public static interface KeyboardVisibilityListener {
            public void onVisibilityChanged(boolean keyboardVisible);
        }
    }

    您可以使用它来检测整个应用程序中的键盘更改,如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
        new KeyboardStatusDetector()
                .registerFragment(fragment)  //register to a fragment
                .registerActivity(activity)  //or register to an activity
                .registerView(view)          //or register to a view
                .setVisibilityListener(new KeyboardVisibilityListener() {
                    @Override
                    public void onVisibilityChanged(boolean keyboardVisible) {
                        if(keyboardVisible) {
                           //Do stuff for keyboard visible
                        }else {
                           //Do stuff for keyboard hidden
                        }
                    }
                });

    注意:只使用其中一个"注册"调用。它们都是相同的,只是为了方便起见


    试试这个:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    final View activityRootView = getWindow().getDecorView().getRootView();
    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.bottom - r.top);
            if (heightDiff < activityRootView.getRootView().getHeight() / 4 ) { // if more than 100 pixels, its probably a keyboard...
                 // ... do something here ... \\
            }
        }
    });

    在更改viewpager中片段的方向时,我很难保持键盘状态。我不确定为什么,但它似乎很不稳定,并且与标准活动的行为不同。

    要在这种情况下保持键盘状态,首先应将android:windowSoftInputMode ="stateUnchanged"添加到AndroidManifest.xml。但是,您可能会注意到,这实际上并没有解决整个问题 - 如果之前在方向更改之前打开了键盘,则键盘不会打开。在所有其他情况下,行为似乎是正确的。

    然后,我们需要实现这里提到的解决方案之一。
    我找到的最干净的是George Maisuradze的 - 使用hideSoftInputFromWindow的布尔回调:

    1
    2
    InputMethodManager imm = (InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
    return imm.hideSoftInputFromWindow(mViewPager.getWindowToken(), 0);

    我将此值存储在Fragment的onSaveInstanceState方法中并将其检索到onCreate。然后,我强行在onCreateView中显示键盘,如果它的值为true(如果键盘在碎片破坏之前实际隐藏之前可见,则返回true)。


    你可以尝试这个,对我很有用:

    1
    2
    3
    4
    5
    6
    7
    InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);

    if (imm.isAcceptingText()) {
        //Software Keyboard was shown..
    } else {
        //Software Keyboard was not shown..
    }


    我刚刚使用上面的大多数解决方案时遇到了一个错误,建议添加一个固定的数字。

    S4具有高dpi,导致导航栏的高度为100px,因此我的应用程序认为键盘一直打开。

    因此,随着所有新的高分辨率手机的发布,我相信使用硬编码值并不是一个长期的好主意。

    我在各种屏幕和设备上进行一些测试后发现的更好的方法是使用百分比。
    获取decorView和ur app内容之间的区别,然后检查该差异的百分比。
    根据我得到的统计数据,大多数导航条(无论大小,分辨率等)都占据屏幕的3%到5%。在键盘打开的情况下,它占据了屏幕的47%到55%之间。

    作为一个结论,我的解决方案是检查差异是否超过10%然后我假设它的键盘打开。


    如果设置半透明状态栏模式,则Reuben Scratton的新答案(计算HeightDiff int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight();)将无法在活动中起作用。

    如果您使用半透明状态栏,activityRootView.getHeight()将永远不会改变天气,软键盘是可见的。它将始终返回活动和状态栏的高度。

    例如,Nexus 4,Android 5.0.1,将android:windowTranslucentStatus设置为true,它将永远返回1184,即使ime已经打开。如果你将android:windowTranslucentStatus设置为false,它将正确返回Height,如果ime不可见,则返回1134(不包括状态栏)。关闭ime,它将返回5xx(取决于ime的高度)

    我不知道天气这是一个bug,我试过4.4.4和5.0.1,结果是一样的。

    因此,到目前为止,第二个最常见的答案是,Kachi的解决方案将是计算ime高度最安全的方法。这是一份副本:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    final View activityRootView = findViewById(R.id.activityRoot);
    activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new        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.bottom - r.top);
    if (heightDiff > 100) { // if more than 100 pixels, its probably a keyboard...
        ... do something here
        }
     }
    });

    这是我的解决方案,它的工作原理。不要查找像素大小,只需检查内容视图的高度是否已更改:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // Scroll to the latest comment whenever the keyboard is shown
    commentsContent.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {

            private int oldHeight;

            @Override
            public void onGlobalLayout() {
                int newHeight = commentsContent.getMeasuredHeight();
                if (newHeight < oldHeight) {
                    // Check for the keyboard showing in case the height difference
                    // is a result of orientation change
                    if (isSoftKeyboardShowing(CommentsActivity.this)) {
                        // Keyboard is showing so scroll to the latest comment
                        scrollToLatestComment();
                    }
                }
                oldHeight = newHeight;
            }

        });


    public static boolean isSoftKeyboardShowing(Activity activity) {
        InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
        return inputMethodManager.isActive();
    }


    不要做任何硬代码。最好的方法是在使用KeyBord Show获取焦点在EditText上时调整视图大小。
    您可以使用下面的代码将活动的resize属性添加到Manifest文件中。

    android:windowSoftInputMode="adjustResize"


    我认为这种方法可以帮助你找出keybord是否可见。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     public Boolean isSoftKeyBoardVisible(){
        InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);

        if (imm.isAcceptingText()) {
            Log.d(TAG,"Software Keyboard was shown");
            return true;
        } else {
            Log.d(TAG,"Software Keyboard was not shown");
            return false;
        }

    }


    也许这会对你有所帮助:

    1
    2
    InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);


    一种不需要LayoutListener的方法

    在我的情况下,我想在更换我的片段之前保存键盘的状态。我从onSaveInstanceState调用hideSoftInputFromWindow方法,关闭键盘并返回键盘是否可见。

    此方法很简单,但可能会更改键盘的状态。


    我知道这是一个老帖子,但我认为这是我所知道的最简单的方法,我的测试设备是Nexus 5.我没有在其他设备上尝试过。如果他们发现我的代码不好,希望其他人分享他们的方法:)

    1
    2
    3
    4
    5
    6
    7
    8
    public static boolean isKeyboardShown(Context context, View view) {
            if (context == null || view == null) {
                return false;
            }
            InputMethodManager imm = (InputMethodManager) context
                    .getSystemService(Context.INPUT_METHOD_SERVICE);
            return imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
    }

    imm.hideSoftInputFromWindow返回boolean。

    谢谢,


    1
    2
    3
    4
    5
    if (keyopen())
    {
                    InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY,0);            
    }

    以上功能是我用来检查键盘是否可见的功能。如果是,那么我关闭它。

    下面显示了所需的两种方法。

    首先,在onCreate中定义可行的窗口高度。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

    //  add to onCreate method
        Rect rectgle= new Rect();
        Window window= getWindow();
        window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
        sheight= rectgle.bottom;
    //

    }

    然后,添加一个布尔方法,该方法获取该实例的Window高度。如果它与原始版本不匹配(假设您没有沿途改变它......)那么键盘是打开的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public boolean keyopen()
    {
        Rect rectgle= new Rect();
        Window window= getWindow();
        window.getDecorView().getWindowVisibleDisplayFrame(rectgle);
        int curheight= rectgle.bottom;

        if (curheight!=sheight)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    了frotz!


    我知道你能确定键盘是否隐藏的确切程度。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public int getStatusBarHeight() {
        int result = 0;
        int resourceId = getResources().getIdentifier("status_bar_height","dimen","android");
        if (resourceId > 0) {
            result = getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }

    public int getNavigationBarHeight() {
        int result = 0;
        int resourceId = getResources().getIdentifier("navigation_bar_height","dimen","android");
        if (resourceId > 0) {
            result = getResources().getDimensionPixelSize(resourceId);
        }
        return result;
    }

    public boolean isKeyboardHidden() {
        int delta = mRootView.getRootView().getHeight() - mRootView.getHeight() - getNavigationBarHeight() - getStatusBarHeight()
                - getSupportActionBar().getHeight();
        return delta <= 0;
    }

    这适用于平板电脑。导航栏水平显示时。


    思考有一个简单的方法,像这样:

    1
    2
    InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
    imm.isActive();

    您还可以查看他是否在特定视图中处于活动状态:

    1
    imm.isActive(View v);

    Reuben Scratton和Kachi提供的解决方案似乎依赖于设备的像素密度,如果你有高密度设备,即使键盘向下,高度差也可能大于100。稍微解决一下是看初始高度差(键盘向下),然后与当前差异进行比较。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    boolean isOpened = false;
    int firstHeightDiff = -1;

    public void setListenerToRootView(){
        final View activityRootView = getActivity().getWindow().getDecorView().findViewById(android.R.id.content);
        Rect r = new Rect();
        activityRootView.getWindowVisibleDisplayFrame(r);
        firstHeightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
        activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                if (isAdded()) {
                    Rect r = new Rect();
                    activityRootView.getWindowVisibleDisplayFrame(r);
                    int heightDiff = activityRootView.getRootView().getHeight() - (r.bottom - r.top);
                    isOpened = heightDiff>firstHeightDiff+100;
                    if (isAdded())
                        if(isOpened) {
                            //TODO stuff for when it is up
                        } else {
                            //TODO stuf for when it is down
                        }
                }
            }
        });
    }

    这是一个解决方法,以了解软键盘是否可见。

  • 使用检查系统上的运行服务
    ActivityManager.getRunningServices(max_count_of_services);
  • 从返回的ActivityManager.RunningServiceInfo实例中,检查软键盘服务的clientCount值。
  • 每次递增上述clientCount,显示软键盘。例如,如果clientCount是
    最初1,键盘显示时为2。
  • 在键盘解雇时,clientCount递减。在这种情况下,它重置为1。
  • 一些流行的键盘在其classNames中有一些关键字:

    1
    2
    3
    4
    5
    6
    Google AOSP = IME
    Swype = IME
    Swiftkey = KeyboardService
    Fleksy = keyboard
    Adaptxt = IME (KPTAdaptxtIME)
    Smart = Keyboard (SmartKeyboard)

    从ActivityManager.RunningServiceInfo中,检查ClassNames中的上述模式。另外,ActivityManager.RunningServiceInfo的clientPackage = android,表示键盘绑定到系统。

    可以结合上述信息以严格的方式来确定软键盘是否可见。


    在理解了不同分辨率的一些问题后,我决定使用相对大小。我注意到可见和隐藏状态之间的差异大约是30%。所以我决定用0.3替换128 PX。

    我添加了这个类监听器来通知任何变化。

    这是我的版本

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    import android.app.*;
    import android.graphics.*;
    import android.view.*;

    public class SoftKeyboardState {
      public static final int HIDDEN = 0, VISIBLE = 1;
      private OnKeyboardStateChangedListener listener;
      private View decorView;

      public SoftKeyboardState(Activity activity) {
        this.decorView = activity.findViewById(android.R.id.content);
        initKeyboardListener();
      }

      private void initKeyboardListener() {
        decorView.getViewTreeObserver().addOnGlobalLayoutListener(
          new ViewTreeObserver.OnGlobalLayoutListener(){
            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) && (lastVisibleDecorViewHeight / visibleDecorViewHeight >= 0.3f)) {
                  // visible
                  if (listener != null)listener.onKeyboardStateChanged(VISIBLE);
                } else if ((lastVisibleDecorViewHeight < visibleDecorViewHeight) && (visibleDecorViewHeight / lastVisibleDecorViewHeight >= 0.3f)) {
                  // hidden
                  if (listener != null)listener.onKeyboardStateChanged(HIDDEN);
                }
              }
              lastVisibleDecorViewHeight = visibleDecorViewHeight;
            }
          });
      }

      public void setOnKeyboardStateChangedListener(OnKeyboardStateChangedListener listener) {
        this.listener = listener;
      }

      public interface OnKeyboardStateChangedListener {
        public void onKeyboardStateChanged(int state);
      }
    }


    这段代码非常好用

    将此类用于根视图:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    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);
    }

    public KeyboardConstraintLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        minKeyboardHeight = getResources().getDimensionPixelSize(R.dimen.keyboard_min_height);
    }

    public KeyboardConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        minKeyboardHeight = getResources().getDimensionPixelSize(R.dimen.keyboard_min_height);
    }

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

    }

    并在activity或fragment中设置键盘侦听器:

    1
    2
    3
    4
    5
    6
        rootLayout.setKeyboardListener(targetEditText, new KeyboardConstraintLayout.KeyboardListener() {
        @Override
        public void onKeyboardVisibility(boolean isVisible) {

        }
    });

    有一种直接的方法可以找到它。并且,它不需要任何布局更改。
    因此,它也适用于沉浸式全屏模式。
    诀窍是你试图隐藏或显示软键盘并捕获该尝试的结果。
    没有恐慌,这并没有真正显示或隐藏键盘。我们只是要求国家。
    为了保持最新,您可以简单地重复操作,例如,每200毫秒,使用一个处理程序。
    您可以在此处找到一个实现:https://stackoverflow.com/a/27567074/2525452


    此解决方案可能会重新打开键盘,但它可以工作。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    InputMethodManager inputManager = ( (InputMethodManager) this.getSystemService(Context.INPUT_METHOD_SERVICE) );

    private boolean isKeyboardShowing() {

        boolean isKeyboardShowing = inputManager.hideSoftInputFromWindow(irrelevantView.getWindowToken(), 0);
        if (isKeyboardShowing) {
            inputManager.showSoftInput(this.getCurrentFocus(), 0);
        }
        return isKeyboardShowing;
    }

    InputMethodManager包含有关软键盘的信息。您可以通过以下方式从活动中获取:

    1
    ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE))

    你可以玩弄它,看它能告诉你什么。您可以使用它来显示或隐藏软输入...