How to check visibility of software keyboard in Android?

前端 未结 30 4491
半阙折子戏
半阙折子戏 2020-11-21 04:43

I need to do a very simple thing - find out if the software keyboard is shown. Is this possible in Android?

相关标签:
30条回答
  • 2020-11-21 05:29

    Wow, We have Good news Android Geeks. And its time to say goodbye to the old way. First I will add official release note to read and know more about these methods/ classes, and then we will see these amazing methods/ classes

    Breaking Note: Do not add these into your release apps, until these classes/ methods are released

    How to check keyboard visibility

    val insets = ViewCompat.getRootWindowInsets(view)
    val isKeyboardVisible = insets.isVisible(Type.ime())
    

    Few other utilities

    How to get the height of Keyboard

    val insets = ViewCompat.getRootWindowInsets(view)
    val keyboardHeight = insets.getInsets(Type.ime()).bottom
    

    How to show/ hide the keyboard

    val controller = view.windowInsetsController
    
    // Show the keyboard
    controller.show(Type.ime())
    
    // Hide the keyboard
    controller.hide(Type.ime())
    

    Note: WindowInsetsController added in API-30, so wait till backward compatible class is not available.

    How to listen to keyboard hide/ show event

    ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets ->
        val isKeyboardVisible = insets.isVisible(Type.ime())
        if (isKeyboardVisible) {
            // Do it when keyboard is being shown
        } else {
            // Do it when keyboard is hidden
        }
    
        // Return the insets to keep going down this event to the view hierarchy
        insets
    }
    
    0 讨论(0)
  • 2020-11-21 05:29

    There's a hidden method can help for this, InputMethodManager.getInputMethodWindowVisibleHeight. But I don't know why it's hidden.

    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()
      }
    }
    
    0 讨论(0)
  • 2020-11-21 05:30

    It has been forever in terms of computer but this question is still unbelievably relevant!

    So I've taken the above answers and have combined and refined them a bit...

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

    Works for me :)

    NOTE: If you notice that the DefaultKeyboardDP does not fit your device play with the value and post a comment for everyone to know what should be the value... eventually we will get the correct value to fit all devices!

    For more details, check out the implementation on Cyborg

    0 讨论(0)
  • 2020-11-21 05:30

    None of these solutions will work for Lollipop as is. In Lollipop activityRootView.getRootView().getHeight() includes the height of the button bar, while measuring the view does not. I've adapted the best/simplest solution above to work with Lollipop.

        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
        }
      }
    });
    
    0 讨论(0)
  • 2020-11-21 05:30

    I was having difficulty maintaining keyboard state when changing orientation of fragments within a viewpager. I'm not sure why, but it just seems to be wonky and acts differently from a standard Activity.

    To maintain keyboard state in this case, first you should add android:windowSoftInputMode = "stateUnchanged" to your AndroidManifest.xml. You may notice, though, that this doesn't actually solve the entire problem -- the keyboard didn't open for me if it was previously opened before orientation change. In all other cases, the behavior seemed to be correct.

    Then, we need to implement one of the solutions mentioned here. The cleanest one I found was George Maisuradze's--use the boolean callback from hideSoftInputFromWindow:

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

    I stored this value in my Fragment's onSaveInstanceState method and retrieved it onCreate. Then, I forcibly showed the keyboard in onCreateView if it had a value of true (it returns true if the keyboard is visible before actually hiding it prior to the Fragment destruction).

    0 讨论(0)
  • 2020-11-21 05:32

    My answer is basically the same as Kachi's answer, but I wrapped it into a nice helper class to clean up the way it's used throughout my app.

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

    You can use this to detect keyboard changes anywhere throughout the app like this:

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

    Note: only use one of the "register" calls. They all work the same and are only there for convenience

    0 讨论(0)
提交回复
热议问题