创建输入法(Creating an IME)

创建输入法

输入法编辑器(IME)是让用户输入文本的控件。Android提供了一个可扩展的的输入法的框架,它允许应用程序给用户提供另外的输入法,如软键盘或语音输入。这些输入法一旦安装,用户就可以从系统的设置中选择他们想要使用的IME,并且这个设置对整个系统都是有效的,每次只有一种输入法是可用的。

要在Android系统中添加一种输入法,你就要创建一个包含继承了InputMethodService类的类应用程序。另外,你通常还要创建一个“settings”Activity,把选项传递给IME服务。你还可以定义一个用于设置的UI,让它做为系统设置的一部分来显示。

本文包含以下内容:

  • IME的生命周期。

  • 在应用程序的清单中声明IME组件;

  • IME API

  • 设计IME的UI

  • 从IME中把文本发送给应用程序

  • 使用IME子类型

IME的生命周期

下图介绍IME的生命周期:

下列章节介绍如何实现UI以及代码如何跟这个生命周期相关联。

在清单中生命IME组件

在Android系统中,IME是一个包含了特殊IME服务的Android应用程序。应用程序的清单文件必须声明服务、申请必要的权限、提供跟action.view.InputMethod操作相匹配的Intent过滤器、以及定义IME的特性的元数据。另外,还要提供一个设置界面,允许用户用它来编辑IME的行为,你可以定义一个从系统设置中能够启动的“settings”的Activity。

以下清单片段声明了IME服务。它申请了允许把服务连接到系统的IME的BIND_INPUT_METHOD权限,建立了一个跟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>

你还可以在这个设置的UI中提供对IME设置的直接访问。

输入法API

有IME的类可以在Android.inputmethodservice和android.view.inputmethod包中找到。KeyEvent类对于处理键盘字符至关重要。

IME的核心部分是服务组件,它继承InputMethodService类。除了实现了标准的服务生命周期,这个类还给IME的UI提供了回调方法,用于处理用户的输入,并且把文本发送给当前具有输入焦点的文本域。默认情况下,InputMethodService类提供了用于管理IME状态和可见性的大多数实现,并且跟当前的输入域进行通信。

下面的类也很重要:

  • BaseInputConnection

    这个类定义InputMethod类跟接收它输入的应用程序之间的通信通道。使用这个通道来读取光标范围内的文本,把文本提交给文本框,以及把原始的按键事件发送给应用程序。应用程序应该继承这个类,而不是实现基本接口:InputConnection。

  • KeyboardView

    这个类是View类的一个子类,它展现一个软键盘,并响应用户输入的事件。该软键盘 的布局是由一个Keyboard类的实例来指定的,你可以在XML文件中定义它的布局。

设计输入法的UI

对于IME来说,有两个主要的可视元素:input view和candidates view。你只需要实现那些跟你所设计的输入法相关的元素。

Input view

Input view是用户点击键盘、手写或使用手势输入文本的UI。当IME首次显示的时候,系统会调用它的onCreateInputView()回调方法。在这个方法的实现中,你要创建你想要的IME窗口的布局,并把该布局返回给系统。以下是onCreateInputView()方法的实现示例:

@Override 
   public View onCreateInputView() {
       MyKeyboardView inputView =
           (MyKeyboardView) getLayoutInflater().inflate( R.layout.input, null);
       inputView.setOnKeyboardActionListener(this); inputView.setKeyboard(mLatinKeyboard);
       return mInputView;
   }

在这个例子中,MyKeyboardView是一个定制的KeyboardView类实现的实例,它展示了一个软键盘。如果你是创建一个传统的QWERTY软键盘,请看软键盘的示例程序:sample app,看。它是如何继承KeyboardView类的。

Candidates View

Candidates View是用户选择候选字的地方。在IME生命周期中,在准备显示Candidate View的时候,系统会调用它的onCreateCandidatesView()回调方法。在这个方法的实现中,它会返回一个建议候选字的布局,如果没有要显示的内容,它会返回null(如果你提供候选建议,那么你就不必实现这个方法,因为null响应是默认的行为)。

UI设计要考虑的因素

接下来介绍以下设计IME的UI时要特别考虑的一些因素:

  • 处理多种屏幕尺寸

    你的IME的UI必须能够根据不同的屏幕尺寸来进行缩放,并且也必须处理横竖屏的差异。在非全屏的IME模式中,要给你的应用程序留有足够的显示文本域和对应内容的空间,IME所占用的屏幕空间不应该超过半屏。在全屏的IME模式中,这就不是一个问题,

  • 处理不同的输入类型

    Android的文本域允许你给它设置特殊的输入类型,如文本、数字、URL、Email地址和搜索字符串等。当你实现一个新的IME时,你需要检查每个输入域的输入类型,并提供相应的UI界面。这样你就不必检查用户键入的文本是否是有效的输入类型,因为应用程序的文本域自己会负责这件事情。

当一个输入域接收到焦点并且IME启动时,系统会调用onStartInputView()回调方法,系统给这个方法传入了一个EditorInfo对象,它包含了输入类型的细节和其他的文本域属性。在这个对象中,inputType字段包含了文本域的输入类型。

inputType字段整数型字段,把包含了各种输入类型设置的位模式。要检查文本域的输入类型,要用TYPE_MASK_CLASS常量来进行掩码运算,如:

inputType&InputType.TYPE_MASK_CLASS

输入类型的位模式可以是下列值之一:

  • TYPE_CLASS_NUMBER

    接收数字的文本域。

  • 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类型的变体,用于输入应用程序从字典、搜索或其他便利的地方获取的自动完成的文本。

警告:在你自己的IME中,当你把文本发送给密码文本域时,要确保正确的处理这些文本,因为在Input View 和Candidates View的UI中密码都是要隐藏的。还要记住不应该把密码保存在设备上。

把文本发送给应用程序

当用户用你的IME输入文本时,你可以通过发送单独的按键事件或应用程序的文本域中光标附近编辑文本的方式把文本发送给应用程序。在这两种情况中,你要使用一个InputConnection对象来发送文本。调用InputMethodService.getCurrentInputConnection()方法来获得这个实例。

在光标附近编辑文本

当处理文本域中既存的的文本时,在BaseInputConnection类中有一些非常有用的方法:

  • getTextBeforeCursor()

    返回一个CharSequence类型的字符串,它包含了光标当前位置之前所有的要求的字符。

  • getTextAfterCursor()

    返回一个CharSequence类型的字符串,它包含了光标当前位置之后所有的要求的字符。

  • deleteSurroundingText()

    删除当前光标位置所选择的指定数量的字符。

  • commitText()

    把一个CharSequence类型的字符串提交给文本域,并设置新的光标位置。

例如,以下代码演示了如何把文本Fell替换成Hello!:

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

在提交之前编排文本

如果你的IME进行文本预选或需要多个步骤才能完成文字的编排,那么你可以在文本域中显示进度,直到用户提交文字,就可以用完整的文本来代替输入部分的文本。当你把文本传递一个InputConnection类的setComposingText()方法时,你可以通过添加一个“范围”来对文本进行特殊的处理。

以下代码显示了文本域中字符的显示过程:

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

截取硬件的按键事件

即使输入法窗口没有明确的焦点,它也会首先接收到硬件的按键事件,并能够选择是使用还是把它们转发给应用程序。例如,在文本编排期间,你可以使用方向键在输入法UI的候选区进行导航。你也可以捕获回退键来消除从输入法窗口弹出的任何窗口。

重写onKeyDown()和onKeyUp()方法来获取硬件的按键事件。

对于不想自己处理的按键,要记住调用super()方法。

创建IME的子类型

子类型运行IME来展现多种输入模式以及由IME所支持的语言。一个子类型可以表现为:

  • 一种语言环境,如en_US或fr_FR;

  • 一种输入模式,如语音、键盘或手写;

  • 其他的输入样式、格式、或特殊的IME属性,如10个键或qwerty键盘的布局等。

基本上模式是任意文字,如“keyboard”、“voice”等等。

一个子类型也可以是这些类型的一个组合。

子类型信息被用于输入法的切换,在通知栏和IME设置窗口中可以使用。这些信息也允许框架直接携带特定的IME子类型。当你创建一个IME时,可以方便的使用子类型,因为它会帮助用户标识并完成不同IME语言和模式之间的切换。

使用<subtype>元素在输入法的XML资源文件中定义一种子类型。下面的示例中定义了两种子类型:一种是用于美国英语环境的键盘子类型,另一中是用于法语环境的键盘子类型:

<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"
            ...
    />
/>

使用%s来获得与子类型的语言标签相同的子类型标签,以便确保你的子类型在UI中被正确的标记。以下是输入法的部分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被输入法的UI用于设置子类型的标签,定义如下:

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

这样在任何英语的语言环境中,就会把子类型的显示名称设置为“English(United States)”,或者是其他对应的语言。

从通知栏中选IME的子类型

Android系统会管理所有的由IME所暴露的子类型。IME子类型被视为IME的从属模式。在通知栏中,用户能够给当前的IME选择一个有效的子类型,如下图所示:

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

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

用户能够在系统设置区的“Language&input”设置面板中控制如何使用子类型。在软键盘的示例中,InputMethodSettingsFragment.java文件中包含了在IME设置中方便启用子类型的的实现。更多信息请参照Android SDK中的SoftKeyboard示例。

图6.选择IME的语言。

通常IME要考虑的因素

在实现你的IME时,还要考虑以下事情:

  • 在IME的UI中为用户提供直接的设置选项;

  • 因为在设备上可以安装多种IME,因此要在输入法的UI中给用户提供直接切换IME的方法;

  • 要快速的显示IME的UI,因此要按照需要来预装或加载大尺寸的资源,以便用户在进入文本域后就能看到该IME。要缓存资源和视图,以便输入法的后续调用。

  • 相反,在输入窗口被隐藏后,你应该释放大块的内存占用,以便应用程序能够有足够的内存来运行。如果IME的隐藏需要几秒钟,那么要考虑使用延迟消息来释放资源。

  • 确保用户能够尽可能多的输入与IME相匹配的语言的字符。要记住,用户可以在密码或用户名称中使用标点符号,因此你的IME有必要提供不同的字符来让用户输入口令并获得对设备的访问。

Copyright© 2020-2022 li-xyz 冀ICP备2022001112号-1