Pop up and close the soft keyboard

  • Eject soft keyboard
    private val imm: InputMethodManager? bylazy { activity? .getSystemService(Context.INPUT_METHOD_SERVICE)as InputMethodManager? }

    private fun showSoftInput(a){ imm? .let { binding.apply { etChat.requestFocus() it.showSoftInput(etChat, InputMethodManager.SHOW_FORCED) } } }Copy the code
  • Close the soft keyboard
    private fun hideSoftInput(a){ imm? .hideSoftInputFromWindow(binding.etChat.windowToken,0)}Copy the code

The soft keyboard is displayed when DialogFragment is displayed

There are two ways to pop up the soft keyboard in DialogFragment display:

1. Send a delayed task in onViewCreated

   etChat.postDelayed({ showSoftInput() }, 200)
Copy the code

Note: This will not work if you call the display keyboard directly, because the view is not displayed at this time

2, Settings dialog style attribute android: windowSoftInputMode

    <style name="live_editTextDialogStyle" parent="@android:style/Theme.Dialog"> <! -- Background transparency --> <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowContentOverlay">@null</item> <! Float above the Activity --> <item name="android:windowIsFloating">true</item> <! -- border --> <item name="android:windowFrame">@null</item> <! -- Region blur outside Dialog --> <item name="android:backgroundDimEnabled">false</item> <! -- no title --> <item name="android:windowNoTitle">true</item> <! --> <item name="android:windowIsTranslucent">true</item> <! -- Display soft keyboard --> <item name="android:windowSoftInputMode">stateAlwaysVisible</item>
    </style>
Copy the code

Close the soft keyboard when DialogFragment disappears

Dialog closing is divided into several cases, which are handled differently:

1. The user manually calls dialogFragment.dismiss ()

You can override the dismiss method to close the soft keyboard before calling it.

    override fun dismiss(a) {
        hideSoftInput()
        super.dismiss()
    }
Copy the code

2. The user clicks on the blank space to close the dialog

DialogFragment itself does not listen for the method before closing, only two related methods onCancel(Dialog: DialogInterface) and onDismiss(Dialog: DialogInterface).

Rewrite onCancel (dialog: DialogInterface)

    override fun onCancel(dialog: DialogInterface) {
        hideSoftInput()
        super.onCancel(dialog)
    }
Copy the code

When you do this and find that the soft keyboard is not closed, take a look at the flow: look at the hideSoftInputFromWindow method of InputMethodManager

    public boolean hideSoftInputFromWindow(IBinder windowToken, int flags,
            ResultReceiver resultReceiver) {
        checkFocus();
        synchronized (mH) {
            if (mServedView == null|| mServedView.getWindowToken() ! = windowToken) {return false;
            }

            try {
                return mService.hideSoftInput(mClient, flags, resultReceiver);
            } catch (RemoteException e) {
                throwe.rethrowFromSystemServer(); }}}Copy the code

HideSoftInput breakpoint: mServedView is null when the softkeyboard is closed in onCancel, so no close code is available. Where is mServedView assigned to null

void finishInputLocked() {
        mNextServedView = null;
        if(mServedView ! =null) {
            if (DEBUG) Log.v(TAG, "FINISH INPUT: mServedView=" + dumpViewInfo(mServedView));
            if(mCurrentTextBoxAttribute ! =null) {
                try {
                    mService.finishInput(mClient);
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            mServedView = null;
            mCompletions = null;
            mServedConnecting = false; clearConnectionLocked(); }}Copy the code

FinishInputLocked calls have two places

	private boolean checkFocusNoStartInput(boolean forceNewFocus) {
		// The rest of the code was omitted
        if (mNextServedView == null) {
                finishInputLocked();
                // This method calls mservice.hidesoftinput, so it can be ruled out
                closeCurrentInput();
                return false;
            }
         return true;
    }

    public void windowDismissed(IBinder appWindowToken) {
        checkFocus();
        synchronized (mH) {
            if(mServedView ! =null&& mServedView.getWindowToken() == appWindowToken) { finishInputLocked(); }}}Copy the code

So you can tell that when the callback goes to onCancel, the windowDismissed method is already called, so the soft keyboard cannot be closed. This method is eliminated, so let’s look at the onDismiss method

Rewrite onDismiss (dialog: DialogInterface)

    override fun onDismiss(dialog: DialogInterface) {
        hideSoftInput()
        super.onDismiss(dialog)
    }
Copy the code

In onDismiss call, mServedView in hideSoftInputFromWindow() is not null, but windowToken == NULL

imm? .hideSoftInputFromWindow(binding.etChat.windowToken,0)

// View.java ->
    public IBinder getWindowToken() {
        returnmAttachInfo ! =null ? mAttachInfo.mWindowToken : null;
    }
Copy the code

In mAttachInfo dispatchDetachedFromWindow () set to null. Because the window we passed in for etchat is closed, the windowToken we get is NULL.

If mServedView is not null, it is the layout control of the activity attached to my DialogFragment. It can be regarded as the control that currently obtains the focus. Therefore, we can pass in the view of the current focus in the activity.

    override fun onDismiss(dialog: DialogInterface) {
        valview = activity? .window? .currentFocus view? .let { imm? .hideSoftInputFromWindow(it.windowToken,0)}super.onDismiss(dialog)
    }
Copy the code

After running, the soft keyboard closes normally, OK, problem solved.

I thought the problem had been solved, but during operation, it was found that there would be occasional shutdown failure, and it was found that mServedView == NULL again. The reason is unknown, there is no way, this method is not safe enough.

A custom Dialog notifies the DialogFragment to close the soft keyboard before dismiss

In order to change the way of thinking, since DialogFragment cannot listen for dialog to close in advance, we need to customize dialog overriding method to tell DialogFragment to close the soft keyboard before dialog closes

    class EditDialog(context: Context? , theme:Int) : Dialog(context, theme) {
        override fun dismiss(a){ onDismissListener? .invoke()super.dismiss()
        }

        var onDismissListener: (() -> Unit)? = null
    }

    override fun onCreateDialog(savedInstanceState: Bundle?).: Dialog {
        val dialog = EditDialog(context, R.style.live_editTextDialogStyle)
        dialog.onDismissListener = { hideSoftInput() }
        return dialog
    }
Copy the code

Create a custom Dialog in DialogFragment onCreateDialog and set it to close the callback.

Finally, there is another way to use a full-screen dialog. Add a transparent View to the previously empty area and set the View’s click event to close the soft keyboard and popover, thus avoiding the problem of clicking on the blank area to close.