欢迎参加我们将于 6 月 3 日举行的 #Android11:Beta 版发布会

创建输入法

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

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

本指南涵盖以下内容:

  • IME 生命周期
  • 在应用清单中声明 IME 组件
  • IME API
  • 设计 IME 界面
  • 将文本从 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

android.inputmethodserviceandroid.view.inputmethod 软件包中提供了 IME 专用的类。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;
    }
    

在此示例中,MyKeyboardViewKeyboardView(会呈现 Keyboard)的自定义实现的一个实例。

候选视图

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

界面设计注意事项

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

处理多种屏幕尺寸

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

处理不同的输入类型

通过 Android 文本字段,您可以设置特定的输入类型,例如自由格式的文本、数字、网址、电子邮件地址以及搜索字符串。当实现新的 IME 时,您需要检测每个字段的输入类型,并为其提供相应的界面。不过,您无需设置 IME 来检查用户针对输入类型输入的文本是否有效;这项工作由拥有相应文本字段的应用负责。

例如,以下是随 Android 平台提供的拉丁语 IME 针对文本和电话号码输入提供的界面的屏幕截图:

图 2.拉丁语 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
用于输入数字的文本字段。如前面的屏幕截图所示,拉丁语 IME 显示了适用于此类型的字段的数字键盘。
TYPE_CLASS_DATETIME
用于输入日期和时间的文本字段。
TYPE_CLASS_PHONE
用于输入电话号码的文本字段。
TYPE_CLASS_TEXT
用于输入所有受支持字符的文本字段。

InputType 的参考文档中对这些常量进行了更详细的介绍。

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

TYPE_TEXT_VARIATION_PASSWORD
用于输入密码的 TYPE_CLASS_TEXT 的变体。输入法将显示装饰标志,而不是实际文本。
TYPE_TEXT_VARIATION_URI
用于输入网址以及其他统一资源标识符 (URI) 的 TYPE_CLASS_TEXT 的变体。
TYPE_TEXT_FLAG_AUTO_COMPLETE
TYPE_CLASS_TEXT 的变体,用于输入应用从字典、搜索或其他工具“自动填充”的文本。

在测试这些变体时,请务必使用相应的常量遮盖 inputTypeInputType 的参考文档中列出了可用的遮盖常量。

注意:在您自己的 IME 中,确保在将文本发送到密码字段时对其进行正确处理。在界面中的输入视图和候选视图中隐藏密码。另请注意,您不应将密码存储到设备上。要了解详情,请参阅安全设计指南。

向应用发送文本

当用户使用您的 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);
    

以下屏幕截图显示了这一过程向用户显示的方式:

图 3.在提交之前构成文本。

拦截硬件按键事件

即使输入法窗口未获得明确的焦点,它也会首先接收硬件按键事件,并且可以选择是使用这些事件,还是将其转发到应用。例如,您可能需要使用方向键在界面中导航,以便在构成文本期间选择候选项。您可能还需要点按返回键,以便关闭来自输入法窗口的任何弹出式窗口。

要拦截硬件按键,请替换 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:imeSubtypeLanguage="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:imeSubtypeLanguage="fr_FR"
                android:imeSubtypeMode="keyboard"
                android:imeSubtypeExtraValue="foobar=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>
    

此设置可使子类型的显示名与语言区域设置一致。 例如,在任何英语语言区域中,显示名都是“English (United States)”。

从通知栏中选择 IME 子类型

Android 系统会管理所有 IME 公开的所有子类型。IME 子类型被视为它们所属 IME 的模式。在通知栏中,用户可以为当前设置的 IME 选择一个可用的子类型,如以下屏幕截图所示:

图 4.从通知栏中选择 IME 子类型。

图 5.在系统设置中设置子类型偏好设置。

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

用户可以在“系统设置”区域的“语言和输入法”设置面板中控制子类型的使用方式。

图 6.为 IME 选择语言。

在 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 中的子类型。

注意:在 Android 5.0(API 级别 21)之前的版本中,switchToNextInputMethod() 无法感知 supportsSwitchingToNextInputMethod 属性。如果用户在没有切换键的情况下切换到某个 IME,可能会卡在该 IME 中,无法轻松从中切换出来。

一般 IME 注意事项

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

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