处理输入法可见性

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

对于大多数应用而言,这些默认行为就足够了。但在某些情况下,您可能希望对输入法的可见性以及它对布局的影响有更多控制。本课程解释了如何控制输入法可见性并对其作出响应。

Activity 启动时显示软键盘

虽然 Android 会在 Activity 启动时将焦点置于布局中的第一个文本字段,但它不会显示软键盘。此行为是合适的,因为输入文本可能不是 Activity 中的主要任务。但是,如果输入文本确实是主要任务,例如在登录屏幕中,则您可能希望默认显示软键盘。

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

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

指定您的 UI 应如何响应

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

要在 Activity 中声明您的首选处理方式,请在清单的 <activity> 元素中使用 android:windowSoftInputMode 属性,并将值设置为某个“adjust”值。

例如,为了确保系统将您的布局调整到可用空间大小,从而使所有布局内容都可以访问(即使需要滚动),请使用 "adjustResize"

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

您可以将调整规范与上一部分中的初始软键盘可见性规范结合使用:

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

如果您的 UI 包含用户在文本输入之后或期间可能需要立即访问的控件,则指定 "adjustResize" 非常重要。例如,如果您使用相对布局将按钮栏放置在屏幕底部,使用 "adjustResize" 会调整布局大小,使按钮栏出现在软键盘上方。

按需显示软键盘

如果在 Activity 生命周期中某个方法中您希望确保输入法可见,您可以使用 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);
   }
}

可靠地显示软键盘

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

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

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

  • 可见性还可能受 android:windowSoftInputMode 属性以及 showSoftInput() 使用的标志的影响。

在某些用例(例如 Activity 启动时)中,这些必需条件中的某些尚未满足。系统不认为视图已连接到软件键盘,会忽略 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 启动期间调用 Activity.onCreate() 中的 View.getWindowInsetsController().show(ime()) 时显示软键盘,则应用开发者应注意在初始启动时不设置 SOFT_INPUT_STATE_HIDDENSOFT_INPUT_STATE_ALWAYS_HIDDEN 标志,以防软键盘意外隐藏。

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

在大多数情况下,系统会处理软键盘的隐藏。以下是几种可能的情况:

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

在某些情况下,您的应用必须手动隐藏软键盘,例如在 View.OnFocusChangeListener.onFocusChange 中文本字段失去焦点时。请谨慎使用此技术;意外关闭软键盘会损害用户体验。

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

  • 在调用 showSoftInput() 后,软键盘被视为显式显示。

  • 反之,在以下任一情况下,软键盘被视为隐式显示:

通常,hideSoftInputFromWindow() 会隐藏软键盘,无论它是如何请求显示的,但使用 HIDE_IMPLICIT_ONLY 标志,可以将其限制为仅隐藏隐式请求显示的软键盘。

在软键盘上方显示对话框或叠加视图

在某些情况下,编辑 Activity 可能需要在软键盘上方创建不可编辑的对话框或叠加窗口。

您的应用有几种选项,以下各部分将对此进行说明。

总而言之,请确保正确处理针对窗口的软键盘窗口标志,使其满足以下关于垂直(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();

创建叠加视图

通过软键盘目标 Activity,创建指定 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);

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

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

  • 出现在编辑 Activity 请求显示的软键盘下方,使其不受文本输入的影响。
  • 能感知软键盘的边衬区大小变化,以调整对话框或窗口的布局。

在这种情况下,您的应用有几种选项。以下各部分将说明这些选项。

创建对话框

通过同时设置 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);