处理输入法可见性

当输入焦点移入或移出可编辑文本字段时,Android 会根据需要显示或隐藏输入,例如屏幕键盘。系统还会决定你的 UI 和文本字段如何在输入法之上显示。例如,当屏幕上的垂直空间有限时,文本字段可能会填充输入法之上的所有空间。

对于大多数应用来说,这些默认行为已经足够了。但是,在某些情况下,你可能需要对输入法的可见性及其对布局的影响进行更多控制。本课介绍如何控制和响应输入法的可见性。

在活动启动时显示软键盘

虽然 Android 在活动启动时会将焦点赋予布局中的第一个文本字段,但它不会显示软键盘。这种行为是合适的,因为输入文本可能不是活动中的主要任务。但是,如果输入文本确实是主要任务,例如在登录屏幕中,那么你可能希望默认情况下显示软键盘。

要在活动启动时显示输入法,请将 android:windowSoftInputMode 属性添加到 <activity> 元素中,并将值设置为 "stateVisible"。例如

<application ... >
    <activity
        android:windowSoftInputMode="stateVisible" ... >
        ...
    </activity>
   ...
</application>

指定 UI 应如何响应

当软键盘出现在屏幕上时,它会减少应用 UI 可用的空间量。系统会决定如何调整 UI 的可见部分,但它可能无法正确调整。为了确保应用获得最佳行为,请指定你希望系统如何在剩余空间中显示 UI。

要在活动中声明你偏好的处理方式,请在清单的 <activity> 元素中使用 android:windowSoftInputMode 属性,并使用其中一个“adjust”值。

例如,要确保系统调整布局以适应可用空间(即使需要滚动,也能使所有布局内容都可访问),请使用 "adjustResize"

<application ... >
   <activity
       android:windowSoftInputMode="adjustResize" ... >
       ...
   </activity>
   ...
</application>

你可以将调整规范与上一节中的 初始软键盘可见性 规范组合起来

<activity
    android:windowSoftInputMode="stateVisible|adjustResize" ... >
    ...
</activity>

如果你的 UI 包含用户可能需要在执行文本输入之前或期间访问的控件,那么指定 "adjustResize" 非常重要。例如,如果你使用相对布局将按钮栏放置在屏幕底部,则使用 "adjustResize" 会调整布局,使按钮栏显示在软键盘之上。

按需显示软键盘

如果在活动生命周期的某个方法中,你希望确保输入法可见,则可以使用 InputMethodManager 来显示它。

例如,以下方法接受一个 View,用户预计会在其中输入内容,调用 requestFocus() 为其赋予焦点,然后调用 showSoftInput() 打开输入法

Kotlin

fun showSoftKeyboard(view: View) {
   if (view.requestFocus()) {
       val imm = getSystemService(InputMethodManager::class.java)
       imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
   }
}

Java

public void showSoftKeyboard(View view) {
   if (view.requestFocus()) {
       InputMethodManager imm = getSystemService(InputMethodManager.class);
       imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT);
   }
}

可靠地显示软键盘

在某些情况下,例如活动启动时,使用 InputMethodManager.showSoftInput() 显示软键盘可能会导致软件键盘对用户不可见。

使用 showSoftInput() 时,软键盘的可见性取决于以下条件

  • 该视图必须已连接到软件键盘。(这反过来又要求 窗口获得焦点 且编辑器视图使用 View.requestFocus() 请求视图焦点)。

  • 可见性还会受到 android:windowSoftInputMode 属性和 showSoftInput() 使用的标志的影响。

在某些情况下,例如活动启动时,这些必要条件中的一部分未满足。系统不会将该视图视为已连接到软件键盘,会忽略 showSoftInput() 调用,并且软件键盘对用户不可见。

要确保软件键盘可靠地显示,你可以使用以下替代方案

Kotlin

editText.requestFocus()
WindowCompat.getInsetsController(window, editText)!!.show(WindowInsetsCompat.Type.ime())

Java

editText.requestFocus();
WindowCompat.getInsetsController(getWindow(), editText).show(WindowInsetsCompat.Type.ime());

Kotlin

class MyEditText : EditText() {
  ...
  override fun onWindowFocusChanged(hasWindowFocus: Boolean) {
    if (hasWindowFocus) {
      requestFocus()
      post {
        val imm: InputMethodManager = getSystemService(InputMethodManager::class.java)
        imm.showSoftInput(this, 0)
      }
    }
  }
}

Java

public class MyEditText extends EditText {
  ...
  @Override
  public void onWindowFocusChanged(boolean hasWindowFocus) {
    if (hasWindowFocus) {
      requestFocus();
      post(() -> {
        InputMethodManager imm = getSystemService(InputMethodManager.class);
        imm.showSoftInput(this, 0);
      });
    }
  }
}

谨慎处理运行时可见性标志

在运行时切换软键盘可见性时,注意不要将某些标志值传递到这些方法中。例如,如果应用期望在活动启动期间的 Activity.onCreate() 中调用 View.getWindowInsetsController().show(ime()) 时显示软键盘,应用开发人员应注意,不要在初始启动期间设置 SOFT_INPUT_STATE_HIDDENSOFT_INPUT_STATE_ALWAYS_HIDDEN 标志,以防软键盘意外隐藏。

系统通常会自动隐藏软键盘

在大多数情况下,系统会处理隐藏软键盘。这可能是以下情况之一

根据之前的系统行为手动隐藏软键盘

在某些情况下,你的应用必须手动隐藏软键盘,例如,当文本字段在 View.OnFocusChangeListener.onFocusChange 中失去焦点时。谨慎使用这种方法;意外关闭软键盘会影响用户体验。

如果你的应用手动隐藏软键盘,你需要知道软键盘是显式显示还是隐式显示的

  • 调用 showSoftInput() 后,软键盘被认为是显式显示的。

  • 相反,在以下两种情况下,软键盘被认为是隐式显示的

通常,hideSoftInputFromWindow() 会隐藏软键盘,无论它是如何被请求的,但使用 HIDE_IMPLICIT_ONLY 可以限制它只关闭隐式请求的软键盘。

在软键盘上方显示一个对话框或覆盖视图

在某些情况下,编辑器活动可能需要在软键盘上方创建一个不可编辑的对话框或覆盖窗口。

您的应用有几个选项,以下部分将进行描述。

总之,确保正确处理针对窗口的软键盘的窗口标志,以便它满足以下关于垂直(z 层)排序的预期

  • 无标志(正常情况):位于软键盘层后面,可以接收文本。
  • FLAG_NOT_FOCUSABLE:位于软键盘层之上,但不能接收文本。
  • FLAG_ALT_FOCUSABLE_IM:位于软键盘层之上,可以获得焦点,但不与软键盘连接。它还会阻止其下方的所有视图连接到软键盘。这对于在软键盘层上方显示不使用文本输入的应用对话框很有用。
  • FLAG_NOT_FOCUSABLEFLAG_ALT_FOCUSABLE_IM:位于软键盘层后面,但不能接收文本。
  • FLAG_NOT_FOCUSABLEFLAG_NOT_TOUCH_MODAL:位于软键盘之上,并允许触控事件“穿过”窗口到达软键盘。

创建对话框

使用 FLAG_ALT_FOCUSABLE_IM 对话框窗口标志使对话框位于软键盘上方,并防止软键盘获得焦点

Kotlin

val content = TextView(this)
content.text = "Non-editable dialog on top of soft keyboard"
content.gravity = Gravity.CENTER
val builder = AlertDialog.Builder(this)
  .setTitle("Soft keyboard layering demo")
  .setView(content)
mDialog = builder.create()
mDialog!!.window!!
  .addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM)
mDialog!!.show()

Java

TextView content = new TextView(this);
content.setText("Non-editable dialog on top of soft keyboard");
content.setGravity(Gravity.CENTER);
final AlertDialog.Builder builder = new AlertDialog.Builder(this)
    .setTitle("Soft keyboard layering demo")
    .setView(content);
mDialog = builder.create();
mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
mDialog.show();

创建覆盖视图

通过软键盘目标活动创建覆盖视图,指定 TYPE_APPLICATION_OVERLAY 窗口类型和 FLAG_ALT_FOCUSABLE_IM 窗口标志。

Kotlin

val params = WindowManager.LayoutParams(
  width,  /* Overlay window width */
  height,  /* Overlay window height */
  WindowManager.LayoutParams.TYPE_APPLICATION, /* Overlay window type */
  WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM /* No need to allow for text input on top of the soft keyboard */
    or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,  /* Allow touch event send to soft keyboard behind the overlay */
  PixelFormat.TRANSLUCENT
)
params.title = "Overlay window"
mOverlayView!!.layoutParams = params
windowManager.addView(mOverlayView, params)

Java

WindowManager.LayoutParams params = new WindowManager.LayoutParams(
    width, /* Overlay window width */
    height, /* Overlay window height */
    TYPE_APPLICATION, /* Overlay window type */
    FLAG_ALT_FOCUSABLE_IM /* No need to allow for text input on top of the soft keyboard */
        | FLAG_NOT_TOUCH_MODAL, /* Allow touch event send to soft keyboard behind the overlay */
    PixelFormat.TRANSLUCENT);
params.setTitle("Overlay window");
mOverlayView.setLayoutParams(params);
getWindowManager().addView(mOverlayView, params);

在软键盘下方显示对话框或视图

您的应用可能需要创建一个具有以下属性的对话框或窗口

  • 出现在编辑器活动请求的软键盘下方,这样它就不会受到文本输入的影响。
  • 保持对软键盘内嵌大小变化的了解,以调整对话框或窗口的布局。

在这种情况下,您的应用有几个选项。以下部分将描述这些选项。

创建对话框

通过同时设置 FLAG_NOT_FOCUSABLE 窗口标志和 FLAG_ALT_FOCUSABLE_IM 窗口标志来创建对话框

Kotlin

val content = TextView(this)
content.text = "Non-editable dialog behind soft keyboard"
content.gravity = Gravity.CENTER
val builder = AlertDialog.Builder(this)
  .setTitle("Soft keyboard layering demo")
  .setView(content)
mDialog = builder.create()
mDialog!!.window!!
  .addFlags(FLAG_NOT_FOCUSABLE or FLAG_ALT_FOCUSABLE_IM)
mDialog!!.show()

Java

TextView content = new TextView(this);
content.setText("Non-editable dialog behind soft keyboard");
content.setGravity(Gravity.CENTER);
final AlertDialog.Builder builder = new AlertDialog.Builder(this)
    .setTitle("Soft keyboard layering demo")
    .setView(content);

mDialog = builder.create();
mDialog.getWindow()
    .addFlags(FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM);
mDialog.show();

创建覆盖视图

通过同时设置 FLAG_NOT_FOCUSABLE 窗口标志和 FLAG_ALT_FOCUSABLE_IM 窗口标志来创建覆盖视图

Kotlin

val params = WindowManager.LayoutParams(
  width,  /* Overlay window width */
  height,  /* Overlay window height */
  WindowManager.LayoutParams.TYPE_APPLICATION,  /* Overlay window type */
  WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
      or WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
  PixelFormat.TRANSLUCENT
)
params.title = "Overlay window"
mOverlayView!!.layoutParams = params
windowManager.addView(mOverlayView, params)

Java

WindowManager.LayoutParams params = new WindowManager.LayoutParams(
    width, /* Overlay window width */
    height, /* Overlay window height */
    TYPE_APPLICATION, /* Overlay window type */
    FLAG_NOT_FOCUSABLE | FLAG_ALT_FOCUSABLE_IM,
    PixelFormat.TRANSLUCENT);
params.setTitle("Overlay window");
mOverlayView.setLayoutParams(params);
getWindowManager().addView(mOverlayView, params);