软键盘的显示和隐藏的正确姿势

一、前言

如果有需要用到输入的地方,通常会有需要自动弹出或者收起软键盘的需求。开篇明义,本文会讲讲弹出和收起软键盘的一些细节,最终还会从源码进行分析。

想要操作软键盘,需要使用到 InputMethodManager ,它是一个系统服务,可以使用 Context.getSystemService() 获取到它。而很多关键的逻辑代码,都是在 InputMethodManagerService 中实现的。

特别说明:本文的所有分析的源码,都是基于 Android 26 的源码。

二、操作软键盘

2.1 InputMethodManager

前面提到,想要操作软键盘,需要使用 InputMethodManager ,它是一个系统服务,想要获取它,可以使用 getSystemService() ,获取到它。

get-server

毕竟是系统服务,使用的时候为了安全,还是要判空,避免空指针。

2.2 显示软键盘

在 InputMethodManager 中,有两个方法 showSoftInput()showSoftInputFromInputMethod() ,而实际上,只有 showSoftInput() 是有效的。

它有两个重载方法,而通常我们会使用它的两个参数的方法。

showsoftinput2

这里我们只需要传递两个参数。它首先需要一个 View ,使用软键盘就是为了输入,而输入就需要有接收输入内容的 View ,这里接收输入的 View ,最好是一个 EditText(但这不是必须的)。

而第二个参数 flags 就是个标志位,从上面截图的方法签名上的文档上可以看到,它接收 0 或者 SHOW_INPYT_IMPLICIT 两个参数,但是实际上,它有第三个参数,另外一个是 SHOW_FORCED。

show-flag

可以看到 1、2 都是有特殊含义的,实际上它们并不影响显示,只是在隐藏的时候,会有一些限制,这些后面看源码的时候再说,一般没有特别需要的话,我们直接传递 0 就好了。

现在,简单总结一下调用 showSoftInput() 会生效的关键点:

1、第一个参数,最好是 EditText 或者它的子类。

考虑到软键盘就是为了输入,EditText 就是一个接收输入的控件。而这不是绝对的,如果不是一个 EditText ,就必须要求这个 View 有两个属性,分别是:android:focusable="true"android:focusableInTouchMode="true"

2、第一个参数,必须是可获取焦点的,并且当前已经获取到焦点。

EditText 默认是允许获取焦点的,但是假如布局中,存在多个可获取焦点的控件,就需要提前让我们传递进去的 View 获取到焦点。获取焦点可以使用 requestFocus() 方法。

3、布局必须加载完成。

onCreate() 中,如果立即调用 showSoftInput() 是不会生效的。想要在页面一启动的时候就弹出键盘,可以在 Activity 上,设置 android:windowSoftInputMode 属性来完成,或者做一个延迟加载,View.postDelayed() 也是一个解决方案。

所以最终,完整的显示软键盘的代码就如下所示了。

static-show

2.3 隐藏软键盘

虽然 showSoftInput() 方法是有效的,但是想要隐藏软键盘,就没有提供对应的 hideSoftInput() 方法,但是却有一个 hideSoftInputFromWindow() 方法,可以用来隐藏软键盘。

先来看看这个方法的签名,它同样有两个方法可以调用。

hide-softmethod

它接收两个参数,第一个参数是一个 IBinder ,可以直接传递一个 View.getWindowToken() 的 windowToken 对象就可以了。而第二个参数,就是隐藏软键盘的标志位,如果没有特殊要求的话,直接传递 0 就好了。

注意这里虽然原则上需要传递一个之前弹出键盘传递的时候,传递的 View 的 windowToken ,但是实际情况是你只需要传递一个存在于当前布局 ViewTree 中,随意一个 View 的 windowToken 就可以了。

最终隐藏软件的代码就是这样的。

sttaic-hide

2.4 切换键盘的弹出和隐藏

在 InputMethodManager 中,还提供了一个 toggleSoftInput() 方法,如同它的名字一样,它可以让软键盘在显示和隐藏之间切换。

toggle-method

该方法,接收两个 flags ,分别是控制 show 和 hide 时候的标识,它们的含义和前面介绍的 showSoftInput()hideSoftInputFromWindow() 一致,所以没有特殊要求,直接传递 0 就好了。

toggleSoftInput() 方法不要求传递一个 View 或者 windowToken,所以它并没有 showSoftInput() 中的一些限制,但是依然还有需要在布局绘制完成之后调用才会有效果。

static-toggle

虽然这个方法,限制很少,但是我们基本上不会使用它。主要原因在于,它是一个开关的方法,会根据当前的状态做相反的操作。这就导致很多时候,我们在代码中,无法直接根据 InputMethodManager 提供的方法判断当前软键盘的显示状态,这样也就无法确定调用它的时候的效果了。

三、源码分析

3.1 flag 的细节

前面的一些方法,都需要传递一个 flag 值,文档中描述的并不详细,我们就从源码的角度,来分析一下这些 flag 的含义。

先来看看 showSoftInput() 方法。

showSoftInput

它最终会调用 mService.showSoftInput() 方法,最终的源码,就需要查看 InputMethodManagerService 中的代码了。而 showSoftInput() 方法,最终会调用 showCurrentInputLocked()

这个方法的代码很长,我们只关心和 flag 相关的代码。

code-show-current

可以看到,flag 会影响两个字段,mShowExplicitlyRequested 和 mShowForced,而 SHOW_FORCED 会更强势一点。

hideSoftInputFromWindow() 方法,最终也会调用 InputMethodManagerService 中的 hideCurrentInputLocked() 方法。

code-hide

DEBUG == true 会输出的 Log 中,已经可以看到含义了。这里会根据显示和隐藏传递的两个 flag 来进行比对,也就是说,如果 flag 使用不正确,可能导致这里直接返回 false ,从而无法隐藏软键盘,这些细节对照代码就清晰了,就不在文章里屡这些细节了。

所以这就是为什么前面提到,如果没有特殊要求,直接传递 0 就好了,可以规避这个限制。

3.2 如何判断软键盘是否弹出

既然 toggleSoftInput() 可以根据当前软键盘的状态,进行不同的操作,那么肯定是有办法确定当前软键盘的状态的。

那我们继续追踪 toggleSoftInput() 的方法源码。

该方法,最终会调用到 InputMethodService 的 onToggleSoftInput() 方法。

toggle-soft

在这个方法中,是根据 isInputViewShow() 方法来判定当前软键盘是否处于显示弹出的状态。但是我们并没有办法,直接和 InputMethodService 进行交互,我们也就没办法直接拿到当前键盘是否显示。

如果想要监听键盘的弹出和收起,可以使用 ViewTreeObserver.OnGlobalLayoutListener 这个监听,来监听布局的调整,从而判断出键盘的弹出和隐藏。这些细节有时间再聊。

四、KeyboardUtils

既然已经清楚了软键盘的收起和弹出的方法细节,那我们来写一个帮助类,来解决这个问题。让你们拿到就可用。

这里提供一下 Java 版和 Kotlin 版。

4.1 Java 版

public class KeyboardUtils {

    public static void showKeyboard(View view) {
        InputMethodManager imm = (InputMethodManager) view.getContext()
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm != null) {
            view.requestFocus();
            imm.showSoftInput(view, 0);
        }
    }

    public static void hideKeyboard(View view){
        InputMethodManager imm = (InputMethodManager) view.getContext()
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm != null) {
            imm.hideSoftInputFromWindow(view.getWindowToken(),0);
        }
    }
    public static void  toggleSoftInput(View view){
        InputMethodManager imm = (InputMethodManager) view.getContext()
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm != null) {
            imm.toggleSoftInput(0,0);
        }
    }
}

4.2 Kotlin 版

object KeyboardktUtils{
    fun showKeyboard(view: View) {
        val imm = view.context
                .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        if (imm != null) {
            view.requestFocus()
            imm.showSoftInput(view, 0)
        }
    }

    fun hideKeyboard(view: View) {
        val imm = view.context
                .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        imm?.hideSoftInputFromWindow(view.windowToken, 0)
    }

    fun toggleSoftInput(view: View) {
        val imm = view.context
                .getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        imm?.toggleSoftInput(0, 0)
    }
}
### 回答1: 你好,对于你的问题,可以通过以下步骤来实现在点击editview外的区域时,收回键盘: 1. 在XML布局文件中,将外层布局设置为可点击,并设置其焦点。 例如: ``` <RelativeLayout android:id="@+id/mainLayout" android:layout_width="match_parent" android:layout_height="match_parent" android:focusable="true" android:focusableInTouchMode="true"> <!-- EditView --> </RelativeLayout> ``` 2. 在Activity中获取外层布局,并设置监听器,当点击外层布局时,收回键盘。 例如: ``` RelativeLayout mainLayout = findViewById(R.id.mainLayout); mainLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(view.getWindowToken(), 0); } }); ``` 这样,当用户点击外层布局时,键盘就会被收回。 ### 回答2: 可以通过以下步骤实现在点击EditText外的区域时收回键盘的功能。 1. 首先,在方法类中创建一个公共方法,在其中监听整个布局的触摸事件。代码如下: ```java public class KeyboardUtils { public static void hideKeyboardOnOutsideClick(Activity activity, View rootView) { if (!(rootView instanceof EditText)) { rootView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { hideKeyboard(activity); return false; } }); } if (rootView instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) rootView; for (int i = 0; i < viewGroup.getChildCount(); i++) { View innerView = viewGroup.getChildAt(i); hideKeyboardOnOutsideClick(activity, innerView); } } } private static void hideKeyboard(Activity activity) { InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE); View focusedView = activity.getCurrentFocus(); if (focusedView != null) { inputMethodManager.hideSoftInputFromWindow(focusedView.getWindowToken(), 0); focusedView.clearFocus(); } } } ``` 2. 然后,在需要使用该功能的Activity中,例如MainActivity,在合适的地方调用该方法,并传入根布局的视图对象。例如,可以在onCreate方法的末尾调用,代码如下: ```java public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); View rootView = findViewById(android.R.id.content); KeyboardUtils.hideKeyboardOnOutsideClick(MainActivity.this, rootView); } // 其他代码... } ``` 这样,在点击EditText以外的区域时,键盘将会被收回。方法类会遍历整个布局,捕获所有非EditText视图的触摸事件,并在触摸事件发生时调用hideKeyboard方法隐藏键盘。 ### 回答3: 要在点击editview外的区域时收回键盘,可以使用以下方法: 首先,创建一个方法类,命名为KeyboardUtil。 在KeyboardUtil类中,定义一个静态方法hideKeyboard,传入一个Context对象和一个View对象作为参数。 在hideKeyboard方法中,使用InputMethodManager类来控制键盘的显示隐藏。 首先,通过Context.getSystemService(Context.INPUT_METHOD_SERVICE)获取InputMethodManager对象。 然后,使用InputMethodManager的hideSoftInputFromWindow方法,将View对象和InputMethodManager.HIDE_IMPLICIT_ONLY作为参数传入,实现隐藏键盘的功能。 在需要使用隐藏键盘功能的地方,调用KeyboardUtil.hideKeyboard方法即可。比如,在Activity的onCreate方法中,可以通过findViewById方法获取editview对象,然后使用KeyboardUtil.hideKeyboard(this, editview)来实现在点击editview外的区域时收回键盘。 在点击editview外的区域时,可以通过设置一个监听器来实现隐藏键盘的功能。比如,可以在Activity的布局文件中的最外层布局中设置一个OnClickListener监听器。当点击该布局时,即可触发点击editview外的区域的事件,在事件中调用KeyboardUtil.hideKeyboard方法隐藏键盘。 以上就是使用方法类实现在点击editview外的区域时收回键盘的步骤。这样可以提供用户更好的使用体验,避免键盘挡住视图的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值