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

弹出和关闭软键盘

  • 弹出软键盘
    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)
            }
        }
    }
  • 关闭软键盘
    private fun hideSoftInput() {
        imm?.hideSoftInputFromWindow(binding.etChat.windowToken, 0)
    }

在DialogFragment显示时弹出软键盘

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

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

   etChat.postDelayed({ showSoftInput() }, 200)

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

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

    <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方法,在调用之前关闭软键盘。

    override fun dismiss() {
        hideSoftInput()
        super.dismiss()
    }

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

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

重写onCancel(dialog: DialogInterface)

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

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

    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的

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调用有两处地方

	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)

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

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

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试试,代码改为:

    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 关闭软键盘

    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的点击事件去关闭软键盘和弹窗,这样就避免了点击空白处关闭的问题。

当使用dialog来实现loading框,每次dialog手机的状态栏会变成其他颜色,这会影响用户的感官体验。为了解决这个问题,你可以使用以下方法: 1. 首先,设置透明蒙层来解决状态栏变色的问题。你可以使用以下代码: ```java Window dialogWindow = mDialog.getWindow(); dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); dialogWindow.setBackgroundDrawableResource(android.R.color.transparent); WindowManager.LayoutParams lp = dialogWindow.getAttributes(); lp.dimAmount = 0.0f; dialogWindow.setAttributes(lp); ``` 2. 然后,当dialog初始化调用`dialogWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)`方法,这样dialog,状态栏的颜色将和Activity展示的状态栏保持一致,不会随着dialog的弹和销毁而改变。 然而,这种方法会引发另一个问题,就是当设置了透明背景后,状态栏的字体也会变成白色。如果Activity标题背景是白色的话,会造成冲突。为了解决这个问题,你可以尝试以下方法: 1. 在dialog之前,将状态栏字体颜色设置为黑色。你可以使用以下代码: ```java dialogWindow.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); ``` 2. 在dialog关闭后,将状态栏字体颜色恢复为白色。你可以使用以下代码: ```java dialogWindow.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); ``` 这样就可以解决android dialogfragment状态栏变色的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值