弹出和关闭软键盘
- 弹出软键盘
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中发送一个延时任务
代码语言: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的点击事件去关闭软键盘和弹窗,这样就避免了点击空白处关闭的问题。