创建输入法

输入法 (IME) 是一种可让用户输入文本的用户控件。Android 提供了一个可扩展的输入法框架,让应用可以为用户提供替代输入法,例如屏幕键盘或语音输入。安装 IME 后,用户可以从系统设置中选择一个 IME,并在整个系统中使用。一次只能启用一个 IME。

如需向 Android 系统添加 IME,请创建一个 Android 应用,其中包含一个扩展 InputMethodService 的类。此外,您通常会创建一个“设置”activity,用于将选项传递给 IME 服务。您还可以定义一个设置界面,该界面会作为系统设置的一部分显示。

本页面涵盖以下主题:

如果您之前没有使用过 IME,请先阅读屏幕输入法这篇介绍性文章。

IME 生命周期

下图描述了 IME 的生命周期:

显示 IME 生命周期的图片。
图 1. IME 的生命周期。

以下部分介绍了如何实现与遵循此生命周期的 IME 关联的界面和代码。

在清单中声明 IME 组件

在 Android 系统中,IME 是一个包含特殊 IME 服务的 Android 应用。应用的清单文件必须声明该服务、请求必要的权限、提供与 action.view.InputMethod 操作匹配的 intent 过滤器,并提供定义 IME 特征的元数据。此外,为了提供一个可让用户修改 IME 行为的设置界面,您可以定义一个可从系统设置启动的“设置”activity。

以下代码段声明了一项 IME 服务。它会请求 BIND_INPUT_METHOD 权限,以允许服务将 IME 连接到系统;设置与 android.view.InputMethod 操作匹配的 intent 过滤器;并为 IME 定义元数据:

<!-- Declares the input method service. -->
<service android:name="FastInputIME"
    android:label="@string/fast_input_label"
    android:permission="android.permission.BIND_INPUT_METHOD">
    <intent-filter>
        <action android:name="android.view.InputMethod" />
    </intent-filter>
    <meta-data android:name="android.view.im"
               android:resource="@xml/method" />
</service>

以下代码段声明了 IME 的设置 activity。它有一个适用于 ACTION_MAIN 的 intent 过滤器,指示该 activity 是 IME 应用的主入口点:

<!-- Optional: an activity for controlling the IME settings. -->
<activity android:name="FastInputIMESettings"
    android:label="@string/fast_input_settings">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
    </intent-filter>
</activity>

您还可以让用户能够直接从 IME 界面访问 IME 设置。

输入法 API

IME 专用的类位于 android.inputmethodservice 和 android.view.inputmethod 软件包中。KeyEvent 类对于处理键盘字符非常重要。

IME 的核心部分是一个服务组件,即一个扩展 InputMethodService 的类。除了实现正常的服务生命周期之外,此类还具有用于提供 IME 界面、处理用户输入以及将文本传递到具有焦点的字段的回调。默认情况下,InputMethodService 类提供用于管理 IME 的状态和可见性以及与当前输入字段进行通信的大部分实现。

以下类也很重要:

BaseInputConnection
定义从 InputMethod 返回到正在接收其输入的应用的通信渠道。您可以使用该类读取光标周围的文本,将文本提交至文本框,然后将原始按键事件发送到应用。应用必须扩展此类,而不是实现基本接口 InputConnection。
KeyboardView
View 的扩展,用于呈现键盘并响应用户输入事件。键盘布局由 Keyboard 的实例指定,您可以在 XML 文件中定义该实例。

设计输入法界面

IME 有两个主要视觉元素:输入视图和候选视图。您只需实现与您正在设计的输入法相关的元素。

输入视图

输入视图是用户以点击按键、手写或手势的形式输入文本的界面。首次显示 IME 时,系统会调用 onCreateInputView() 回调。在此方法的实现中,创建您要在 IME 窗口中显示的布局,并将该布局返回给系统。以下代码段展示了实现 onCreateInputView() 方法的示例:

Kotlin

override fun onCreateInputView(): View {
    return layoutInflater.inflate(R.layout.input, null).apply {
        if (this is MyKeyboardView) {
            setOnKeyboardActionListener(this@MyInputMethod)
            keyboard = latinKeyboard
        }
    }
}

Java

@Override
public View onCreateInputView() {
    MyKeyboardView inputView =
        (MyKeyboardView) getLayoutInflater().inflate(R.layout.input, null);

    inputView.setOnKeyboardActionListener(this);
    inputView.setKeyboard(latinKeyboard);

    return inputView;
}

在此示例中,MyKeyboardView 是 KeyboardView 的自定义实现的实例,用于呈现 Keyboard。

候选视图

候选字词视图是 IME 显示潜在字词更正或建议供用户选择的界面。在 IME 生命周期中,系统会在准备好显示候选字词视图时调用 onCreateCandidatesView()。在此方法的实现中,返回显示字词建议的布局,或者如果您不想显示任何内容,则返回 null。默认行为是返回 null 响应,因此,如果您不提供建议,则无需实现此方法。

界面设计注意事项

本部分介绍了 IME 的一些界面设计注意事项。

处理多种屏幕尺寸

IME 的界面必须能够针对不同的屏幕尺寸进行缩放,并能够同时处理横屏和竖屏方向。在非全屏 IME 模式下,为应用留出足够的空间来显示文本字段和任何关联的上下文,以便 IME 占用的屏幕空间不超过一半。在全屏 IME 模式下,这不是问题。

处理不同的输入类型

Android 文本字段可让您设置特定的输入类型,例如自由格式文本、数字、网址、邮箱和搜索字符串。实现新的 IME 时,请检测每个字段的输入类型,并为其提供适当的界面。不过,您无需设置 IME 来检查用户是否针对输入类型输入了有效文本。这是拥有文本字段的应用的责任。

例如,以下是 Latin IME 为 Android 平台文本输入提供的界面:

一张图片,显示拉丁文 IME 上的文本输入
图 2. 拉丁语 IME 文本输入。

以下是 Latin IME 为 Android 平台数字输入提供的界面:

一张图片,显示拉丁语 IME 上的数字输入
图 3. 拉丁语 IME 数字输入。

当输入字段获得焦点且 IME 启动时,系统会调用 onStartInputView(),并传入一个 EditorInfo 对象,其中包含有关输入类型和文本字段的其他属性的详细信息。在此对象中,inputType 字段包含文本字段的输入类型。

inputType 字段是一个 int,其中包含各种输入类型设置的位模式。如要针对文本字段的输入类型测试该字段,请使用常量 TYPE_MASK_CLASS 遮盖它,如下所示:

Kotlin

inputType and InputType.TYPE_MASK_CLASS

Java

inputType & InputType.TYPE_MASK_CLASS

输入类型位模式可以具有多个值之一,包括:

TYPE_CLASS_NUMBER
用于输入数字的文本字段。如图 3 所示,拉丁语 IME 显示了适用于此类字段的数字键盘。
TYPE_CLASS_DATETIME
用于输入日期和时间的文本字段。
TYPE_CLASS_PHONE
用于输入电话号码的文本字段。
TYPE_CLASS_TEXT
用于输入任何受支持字符的文本字段。

InputType 的参考文档更详细地介绍了这些常量。

inputType 字段可以包含其他位,以指示文本字段类型的变体,例如:

TYPE_TEXT_VARIATION_PASSWORD
TYPE_CLASS_TEXT 的一种变体,用于输入密码。输入法会显示装饰符号,而不是实际文本。
TYPE_TEXT_VARIATION_URI
TYPE_CLASS_TEXT 的一种变体,用于输入网址和其他统一资源标识符 (URI)。
TYPE_TEXT_FLAG_AUTO_COMPLETE
TYPE_CLASS_TEXT 的一种变体,用于输入应用从字典、搜索或其他工具中自动补全的文本。

在测试这些变体时,请使用适当的常量遮盖 inputType。InputType 的参考文档中列出了可用的掩码常量。

向应用发送文本

当用户使用您的 IME 输入文本时,您可以通过发送单个按键事件或通过在应用的文本字段中编辑光标周围的文本,将文本发送到应用。在任何一种情况中,您都要使用 InputConnection 的实例来提供文本。如需获取此实例,请调用 InputMethodService.getCurrentInputConnection()。

修改光标周围的文本

在处理现有文本的编辑时,BaseInputConnection 中的一些有用方法如下:

getTextBeforeCursor()
返回 CharSequence,其中包含当前光标位置之前请求数量的字符。
getTextAfterCursor()
返回 CharSequence,其中包含当前光标位置之后请求数量的字符。
deleteSurroundingText()
删除当前光标位置之前和之后的指定数量的字符。
commitText()
将 CharSequence 提交到文本字段,并设置新的光标位置。

例如,以下代码段展示了如何将光标左侧的四个字符替换为文本“Hello!”:

Kotlin

currentInputConnection.also { ic: InputConnection ->
    ic.deleteSurroundingText(4, 0)
    ic.commitText("Hello", 1)
    ic.commitText("!", 1)
}

Java

InputConnection ic = getCurrentInputConnection();
ic.deleteSurroundingText(4, 0);
ic.commitText("Hello", 1);
ic.commitText("!", 1);

支持在提交前撰写文本

如果您的 IME 会预测文本或需要多个步骤才能撰写字形或字词,您可以在文本字段中显示进度,直到用户提交字词,然后您就可以将部分撰写内容替换为完整的文本。您可以在将文本传递给 setComposingText() 时为其添加 span,从而对文本进行特殊处理。

以下代码段演示了如何在文本字段中显示进度:

Kotlin

currentInputConnection.also { ic: InputConnection ->
    ic.setComposingText("Composi", 1)
    ic.setComposingText("Composin", 1)
    ic.commitText("Composing ", 1)
}

Java

InputConnection ic = getCurrentInputConnection();
ic.setComposingText("Composi", 1);
ic.setComposingText("Composin", 1);
ic.commitText("Composing ", 1);

拦截硬件按键事件

即使输入法窗口没有明确的焦点,它也会首先接收硬件按键事件,并且可以消耗这些事件或将其转发给应用。例如,您可能希望使用方向键在界面中导航,以便在撰写期间选择候选字词。 您可能还需要捕获返回键,以关闭源自输入法窗口的所有对话框。

如需拦截硬件按键,请替换 onKeyDown() 和 onKeyUp()。

针对您不想自行处理的按键调用 super() 方法。

创建 IME 子类型

借助子类型,IME 可以提供多种受支持的输入模式和语言。子类型可以表示以下内容:

  • 语言区域,例如 en_US 或 fr_FR
  • 输入模式,例如语音、键盘或手写
  • IME 特有的其他输入样式、形式或属性,例如 10 键或 QWERTY 键盘布局

模式可以是任何文本,例如“keyboard”或“voice”。子类型也可以公开这些类型的组合。

子类型信息用于通知栏中提供的 IME 切换器对话框以及 IME 设置。这些信息还可让框架直接启动 IME 的特定子类型。构建 IME 时,请使用子类型功能,因为该功能可帮助用户识别和切换不同的 IME 语言和模式。

使用 <subtype> 元素在输入法的某个 XML 资源文件中定义子类型。以下代码段定义了一个具有两个子类型的 IME:一个适用于美国英语语言区域的键盘子类型,以及另一个适用于法国法语语言区域的键盘子类型:

<input-method xmlns:android="http://schemas.android.com/apk/res/android"
        android:settingsActivity="com.example.softkeyboard.Settings"
        android:icon="@drawable/ime_icon">
    <subtype android:name="@string/display_name_english_keyboard_ime"
            android:icon="@drawable/subtype_icon_english_keyboard_ime"
            android:languageTag="en-US"
            android:imeSubtypeMode="keyboard"
            android:imeSubtypeExtraValue="somePrivateOption=true" />
    <subtype android:name="@string/display_name_french_keyboard_ime"
            android:icon="@drawable/subtype_icon_french_keyboard_ime"
            android:languageTag="fr-FR"
            android:imeSubtypeMode="keyboard"
            android:imeSubtypeExtraValue="someVariable=30,someInternalOption=false" />
    <subtype android:name="@string/display_name_german_keyboard_ime" ... />
</input-method>

为确保您的子类型在界面中正确标记,请使用“%s”获取与子类型的语言区域标签相同的子类型标签。接下来的两个代码段对此进行了演示。第一个代码段显示了输入法 XML 文件的一部分:

<subtype
    android:label="@string/label_subtype_generic"
    android:imeSubtypeLocale="en_US"
    android:icon="@drawable/icon_en_us"
    android:imeSubtypeMode="keyboard" />

第二个代码段是 IME 的 strings.xml 文件的一部分。字符串资源 label_subtype_generic(由输入法界面定义用于设置子类型的标签)定义如下:

<string name="label_subtype_generic">%s</string>

此设置可使子类型的显示名称与语言区域设置一致。例如,在任何英语语言区域中,显示名称均为“英语(美国)”。

从通知栏中选择 IME 子类型

Android 系统会管理所有 IME 提供的所有子类型。IME 子类型会被视为其所属 IME 的模式。用户可以从通知栏或“设置”应用导航到可用 IME 子类型的菜单,如下图所示:

一张图片,显示“语言和输入法”系统菜单
图 4. 语言和输入法系统菜单。

从系统设置中选择 IME 子类型

用户还可以在系统设置中的语言和输入设置面板中控制子类型的用法:

显示“语言”选择菜单的图片
图 5. 语言系统菜单

在 IME 子类型之间切换

您可以提供一个切换键(例如键盘上的地球形语言图标),让用户轻松切换 IME 子类型。这可以提高键盘的易用性,并为用户提供便利。如需启用此切换,请执行以下步骤:

  1. 在输入法的 XML 资源文件中声明 supportsSwitchingToNextInputMethod = "true"。您的声明必须类似于以下代码段:
    <input-method xmlns:android="http://schemas.android.com/apk/res/android"
            android:settingsActivity="com.example.softkeyboard.Settings"
            android:icon="@drawable/ime_icon"
            android:supportsSwitchingToNextInputMethod="true">
  2. 调用 shouldOfferSwitchingToNextInputMethod() 方法。
  3. 如果该方法返回 true,则显示一个切换键。
  4. 当用户点按开关键时,调用 switchToNextInputMethod(),并传递 false。值为 false 会告知系统一视同仁地对待所有子类型,无论它们属于哪个 IME。指定 true 会要求系统在当前 IME 中循环切换子类型。

IME 一般注意事项

在实现 IME 时,还需要考虑以下事项:

  • 为用户提供一种直接从 IME 界面设置选项的方法。
  • 为用户提供一种直接从输入法界面切换到其他 IME 的方法,因为设备上可能安装了多个 IME。
  • 快速调出 IME 界面。预加载或按需加载任何大型资源,以便用户在点按文本字段后立即看到 IME。缓存资源和视图,以便后续调用输入法。
  • 在输入法窗口隐藏后立即释放大量内存分配,以便应用有足够的内存来运行。如果 IME 隐藏了几秒钟,请使用延迟消息释放资源。
  • 确保用户可以输入尽可能多的字符,以支持与 IME 关联的语言或语言区域。用户可能会在密码或用户名中使用标点符号,因此您的 IME 必须提供许多不同的字符,以便用户输入密码和访问设备。