处理输入法可见性

当输入焦点移入或移出可编辑的文本字段时,Android 会视情况显示或隐藏输入项(例如屏幕键盘)。系统还会决定界面和文本字段在输入法上方的显示方式。例如,当屏幕上的垂直空间受到限制时,文本字段可能会填充输入法上方的所有空间。

对于大多数应用来说,只需要这些默认行为。不过,在某些情况下,您可能希望更好地控制输入法的可见性以及它对布局的影响。本课将介绍如何控制和响应输入法可见性。

在 activity 启动时显示软键盘

尽管 Android 会在 activity 启动时将焦点放在布局中的第一个文本字段上,但不会显示软键盘。此行为是适当的,因为输入文本可能不是 activity 中的主要任务。但是,如果输入文本确实是主要任务(例如在登录屏幕中),那么您可能希望默认显示软键盘。

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

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

指定界面的响应方式

当软键盘出现在屏幕上时,它会减少可供应用界面使用的空间。系统会决定如何调整界面的可见部分,但可能会不正确。为确保应用实现最佳行为,请指定您希望系统如何在剩余空间中显示界面。

如需在 activity 中声明您的首选处理方式,请在清单的 <activity> 元素中使用 android:windowSoftInputMode 属性,该属性为其中一个“adjust”值。

例如,若要确保系统将布局大小调整为可用空间(即使需要滚动,所有布局内容也可访问),请使用 "adjustResize"

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

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

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

如果界面包含用户在执行文本输入之后或执行期间可能需要立即访问的控件,则指定 "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() 时软键盘的可见性取决于以下条件:

在某些用例(例如 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);