处理输入法可见性

当输入焦点移入或移出可编辑文本字段时,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()时软键盘的可见性依赖于以下条件

在某些用例中,例如活动启动时,某些必需的条件未满足。系统不认为视图已连接到软件键盘,忽略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);