DialogFragment自动弹出软键盘,消失时关闭软键盘

2021-11-05 14:55:16 浏览数 (1)

弹出和关闭软键盘

  • 弹出软键盘
代码语言:javascript复制
    private val imm: InputMethodManager? by lazy { activity?.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager? }

    private fun showSoftInput() {
        imm?.let {
            binding.apply {
                etChat.requestFocus()
                it.showSoftInput(etChat, InputMethodManager.SHOW_FORCED)
            }
        }
    }
复制代码
  • 关闭软键盘
代码语言:javascript复制
    private fun hideSoftInput() {
        imm?.hideSoftInputFromWindow(binding.etChat.windowToken, 0)
    }
复制代码

在DialogFragment显示时弹出软键盘

在DialogFragment显示时有两种方式弹出软键盘:

1、在onViewCreated中发送一个延时任务

代码语言:javascript复制
   etChat.postDelayed({ showSoftInput() }, 200)
复制代码

注意:如果直接调用显示键盘不会起作用,因为这个时候view没有显示出来

2、设置dialog的style属性 android:windowSoftInputMode

代码语言:javascript复制
    <style name="live_editTextDialogStyle" parent="@android:style/Theme.Dialog">
        <!-- 背景透明 -->
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:windowContentOverlay">@null</item>
        <!-- 浮于Activity之上 -->
        <item name="android:windowIsFloating">true</item>
        <!-- 边框 -->
        <item name="android:windowFrame">@null</item>
        <!-- Dialog以外的区域模糊效果 -->
        <item name="android:backgroundDimEnabled">false</item>
        <!-- 无标题 -->
        <item name="android:windowNoTitle">true</item>
        <!-- 半透明 -->
        <item name="android:windowIsTranslucent">true</item>
        <!-- 显示软键盘 -->
        <item name="android:windowSoftInputMode">stateAlwaysVisible</item>
    </style>
复制代码

在DialogFragment消失时关闭软键盘

dialog关闭分为几种情况,处理方式不一样:

1.用户手动调用DialogFragment.dismiss()

这时可以重写dismiss方法,在调用之前关闭软键盘。

代码语言:javascript复制
    override fun dismiss() {
        hideSoftInput()
        super.dismiss()
    }
复制代码

2.用户点击空白处关闭dialog

DialogFragment本身没有监听关闭之前的方法,只有两个相关方法onCancel(dialog: DialogInterface)和onDismiss(dialog: DialogInterface)

重写onCancel(dialog: DialogInterface)

代码语言:javascript复制
    override fun onCancel(dialog: DialogInterface) {
        hideSoftInput()
        super.onCancel(dialog)
    }
复制代码

当这样处理时发现软键盘没有关闭,可以看下流程: 看看InputMethodManager的hideSoftInputFromWindow方法

代码语言:javascript复制
    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) {
                throw e.rethrowFromSystemServer();
            }
        }
    }
复制代码

可以看到关闭软键盘的代码为mService.hideSoftInput 断点发现当在onCancel中关闭软键盘时 mServedView为null,所以走不到关闭代码,看一下mServedView在哪赋值为null的

代码语言:javascript复制
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();
        }
    }
复制代码

finishInputLocked调用有两处地方

代码语言:javascript复制
	private boolean checkFocusNoStartInput(boolean forceNewFocus) {
		// 已省略其余代码
        if (mNextServedView == null) {
                finishInputLocked();
                // 此方法会调用 mService.hideSoftInput,所以可以排除
                closeCurrentInput();
                return false;
            }
         return true;
    }

    public void windowDismissed(IBinder appWindowToken) {
        checkFocus();
        synchronized (mH) {
            if (mServedView != null &&
                    mServedView.getWindowToken() == appWindowToken) {
                finishInputLocked();
            }
        }
    }
复制代码

所以可以判断当回调到onCancel的时候,windowDismissed方法已经调用,所以无法关闭软键盘。此方式排除,再来看看onDismiss方法

重写onDismiss(dialog: DialogInterface)

代码语言:javascript复制
    override fun onDismiss(dialog: DialogInterface) {
        hideSoftInput()
        super.onDismiss(dialog)
    }
复制代码

在onDismiss调用时发现 hideSoftInputFromWindow()中的 mServedView不为null,但是 windowToken == null,看一下这个参数的获取

代码语言:javascript复制
imm?.hideSoftInputFromWindow(binding.etChat.windowToken, 0)

// View.java ->
    public IBinder getWindowToken() {
        return mAttachInfo != null ? mAttachInfo.mWindowToken : null;
    }
复制代码

mAttachInfo会在 dispatchDetachedFromWindow()中置为null。因为我们传入的etchat所在窗口已经关闭,所以获取的 windowToken为null。

再来看 mServedView不为null的值,发现是我 DialogFragment 依附的 activity的布局控件,可以理解为当前获取焦点的控件,因此可以传入activity中当前焦点所在的view试试,代码改为:

代码语言:javascript复制
    override fun onDismiss(dialog: DialogInterface) {
        val view = activity?.window?.currentFocus
        view?.let { imm?.hideSoftInputFromWindow(it.windowToken, 0) }
        super.onDismiss(dialog)
    }
复制代码

运行后软键盘正常关闭,OK,问题解决。

本以为问题已解决,但是在操作的时候发现会有偶发的关闭失效,发现又是 mServedView == null,原因未知,没办法,这种方式不够保险。

自定义Dialog在dismiss之前通知 DialogFragment 关闭软键盘

转变思路,既然在 DialogFragment 中无法提前监听dialog关闭,那就自定义Dialog重写dismiss方法,在Dialog关闭之前告知 DialogFragment 关闭软键盘

代码语言:javascript复制
    class EditDialog(context: Context?, theme: Int) : Dialog(context, theme) {
        override fun dismiss() {
            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
    }
复制代码

在DialogFragment的onCreateDialog中创建自定义的Dialog,设置关闭回调。

最后,还有一种方式就是使用全屏dialog,在原先空白区域加一个透明的View,设置view的点击事件去关闭软键盘和弹窗,这样就避免了点击空白处关闭的问题。

0 人点赞