How to capture the “virtual keyboard show/hide” event in Android?

前端 未结 16 1195
暗喜
暗喜 2020-11-22 07:23

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.

相关标签:
16条回答
  • 2020-11-22 07:55

    2020 Update

    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://stackoverflow.com/a/36259261/372076. Alternatively, here's a page giving some different approaches to achieve this pre Android 11:

    https://developer.salesforce.com/docs/atlas.en-us.noversion.service_sdk_android.meta/service_sdk_android/android_detecting_keyboard.htm


    Note

    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.

    0 讨论(0)
  • 2020-11-22 07:55

    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.

    0 讨论(0)
  • 2020-11-22 07:57

    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
                        ...
                    }
                });
    
        }   
    });
    
    0 讨论(0)
  • 2020-11-22 07:57

    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);
    }
    
    0 讨论(0)
  • 2020-11-22 07:58

    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);
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-22 08:00

    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);
            }
        });
    
    0 讨论(0)
提交回复
热议问题