Cómo crear un método de entrada

Un editor de método de entrada (IME) es un control para el usuario que le permite ingresar texto. Android proporciona un framework de método de entrada extensible que permite que las aplicaciones ofrezcan a los usuarios métodos de entrada alternativos, como teclados en pantalla o incluso entrada de voz. Después de instalar los IME deseados, un usuario puede seleccionar cuál usar desde la configuración y utilizarlo en todo el sistema; solo se puede habilitar un IME a la vez.

A fin de agregar un IME al sistema Android, creas una aplicación para Android que contiene una clase que extiende InputMethodService. Además, por lo general, creas una actividad de "configuración" que pasa opciones al servicio del IME. También puedes definir una IU de configuración que se muestra como parte de la configuración del sistema.

En esta guía, se tratan los siguientes temas:

  • El ciclo de vida del IME
  • La declaración de los componentes del IME en el manifiesto de la aplicación
  • La API del IME
  • Cómo diseñar una IU de IME
  • Cómo enviar texto de un IME a una aplicación
  • Cómo trabajar con subtipos de IME

Si no has trabajado anteriormente con IME, primero debes leer el artículo de introducción Métodos de entrada en pantalla.

El ciclo de vida del IME

En el siguiente diagrama, se describe el ciclo de vida de un IME:

Figura 1: Ciclo de vida de un IME

En las siguientes secciones, se describe cómo implementar la IU y el código asociado con un IME que sigue este ciclo de vida.

Cómo declarar componentes de IME en el manifiesto

En el sistema Android, un IME es una aplicación que contiene un servicio especial de IME. El archivo de manifiesto de la aplicación debe declarar el servicio, solicitar los permisos necesarios, proporcionar un filtro de intents que coincida con el método action.view.InputMethod de la acción y ofrecer metadatos que definan características del IME. Además, para proporcionar una interfaz de configuración que permita que el usuario modifique el comportamiento del IME, puedes definir una actividad de "configuración" que se pueda iniciar desde Configuración del sistema.

El siguiente fragmento declara un servicio del IME. Solicita el permiso BIND_INPUT_METHOD para permitir que el servicio conecte el IME al sistema, configura un filtro de intents que coincida con el método android.view.InputMethod de la acción y define metadatos para el 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>

El siguiente fragmento declara la actividad de configuración para el IME. Tiene un filtro de intents para ACTION_MAIN que indica que esta actividad es el punto de entrada principal para la aplicación del 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>

También puedes permitir el acceso a la configuración del IME directamente desde su IU.

La API del método de entrada

Las clases específicas de IME se encuentran en los paquetes android.inputmethodservice y android.view.inputmethod. La clase KeyEvent es importante para procesar los caracteres del teclado.

La parte central de un IME es un componente de servicio, una clase que extiende InputMethodService. Además de implementar el ciclo de vida de servicio normal, esta clase tiene devoluciones de llamada que permiten proporcionar la IU del IME, procesar las entradas del usuario y enviar texto al campo enfocado actualmente. De acuerdo con la configuración predeterminada, la clase InputMethodService proporciona la mayor parte de la implementación para administrar el estado y la visibilidad del IME, además de comunicarse con el campo de entrada actual.

Las siguientes clases también son importantes:

BaseInputConnection
Define el canal de comunicación de un método InputMethod para la aplicación que recibe su entrada. Lo usas para leer texto alrededor del cursor, confirmar el mensaje en el cuadro de texto y enviar eventos clave sin procesar a la aplicación. Las aplicaciones deberían extender esta clase en lugar de implementar la interfaz base InputConnection.
KeyboardView
Es una extensión de View que procesa un teclado y responde a los eventos de entrada del usuario. El diseño del teclado se especifica mediante una instancia de Keyboard, que puedes definir en un archivo en formato XML.

Cómo diseñar la IU del método de entrada

Un IME tiene dos elementos visuales principales: una vista de entrada y otra de candidatos. Solo debes implementar los elementos que sean relevantes para el método de entrada que estés diseñando.

Vista Entrada

La vista Entrada es la IU en la que el usuario ingresa texto en forma de teclas, escritura a mano o gestos. Cuando se muestra el IME por primera vez, el sistema llama a la devolución de llamada de onCreateInputView(). En la implementación de este método, crea el diseño que quieras mostrar en la ventana del IME y muestra el diseño al sistema. Este fragmento es un ejemplo de la implementación del método 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;
}

En este ejemplo, MyKeyboardView es una instancia de una implementación personalizada de KeyboardView que procesa un objeto Keyboard.

Vista Candidatos

La vista Candidatos es la IU en la que el IME muestra posibles correcciones o sugerencias de palabras para que seleccione el usuario. En el ciclo de vida del IME, el sistema llama a onCreateCandidatesView() cuando está listo para mostrar la vista Candidatos. En la implementación de este método, muestra un diseño que incluya sugerencias de palabras o, si no quieres incluir nada, muestra un valor nulo. El comportamiento predeterminado es una respuesta nula, por lo que no tienes que implementarla si no proporcionas sugerencias.

Consideraciones de diseño de la IU

En esta sección, se describen algunas consideraciones de diseño de IU específicas para los IME.

Cómo procesar varios tamaños de pantalla

La IU de tu IME se debe poder ajustar a diferentes tamaños de pantalla y también debe procesar las orientaciones horizontal y vertical. En el modo IME de pantalla no completa, deja espacio suficiente para que la aplicación muestre el campo de texto y cualquier contexto asociado, de modo que el IME no ocupe más de la mitad de la pantalla. En el modo IME de pantalla completa, esto no es un problema.

Cómo procesar diferentes tipos de entrada

Los campos de texto de Android te permiten configurar un tipo de entrada específico, como strings de búsquedas, texto de formato libre, números, URL y direcciones de correo electrónico. Cuando implementas un nuevo IME, debes detectar el tipo de entrada de cada campo y proporcionar la interfaz adecuada para él. Sin embargo, no es necesario que configures tu IME para comprobar que el usuario haya ingresado texto válido en el tipo de entrada, ya que esa es la responsabilidad de la aplicación que posee el campo de texto.

Por ejemplo, aquí hay capturas de pantalla de las interfaces que proporciona el IME latino con la plataforma de Android correspondiente a entradas de texto y números de teléfono:

Figura 2: Tipos de entrada del IME latino

Cuando un campo de entrada recibe el enfoque y se inicia el IME, el sistema llama a onStartInputView() y pasa un objeto EditorInfo que contiene detalles sobre el tipo de entrada y otros atributos del campo de texto. En este objeto, el campo inputType contiene el tipo de entrada del campo de texto.

El campo inputType es un int que contiene patrones de bits para varias configuraciones de tipo de entrada. A fin de probarlo para el tipo de entrada del campo de texto, enmascáralo con la constante TYPE_MASK_CLASS, de la siguiente manera:

Kotlin

inputType and InputType.TYPE_MASK_CLASS

Java

inputType & InputType.TYPE_MASK_CLASS

El patrón de bits del tipo de entrada puede tener uno de varios valores, entre los que se incluyen los siguientes:

TYPE_CLASS_NUMBER
Es un campo de texto para ingresar números. Como se ilustra en la captura de pantalla anterior, el IME latino muestra un teclado numérico para los campos de este tipo.
TYPE_CLASS_DATETIME
Es un campo de texto para ingresar una fecha y una hora.
TYPE_CLASS_PHONE
Es un campo de texto para ingresar números de teléfono.
TYPE_CLASS_TEXT
Es un campo de texto para ingresar todos los caracteres compatibles.

Estas constantes se describen con más detalle en la documentación de referencia de InputType.

El campo inputType puede contener otros bits que indiquen una variante del tipo de campo de texto, por ejemplo:

TYPE_TEXT_VARIATION_PASSWORD
Es una variante de TYPE_CLASS_TEXT para ingresar contraseñas. El método de entrada mostrará íconos al azar en lugar del texto real.
TYPE_TEXT_VARIATION_URI
Es una variante de TYPE_CLASS_TEXT para ingresar URL web y otros identificadores uniformes de recursos (URI).
TYPE_TEXT_FLAG_AUTO_COMPLETE
Es una variante de TYPE_CLASS_TEXT para ingresar texto que la aplicación "completa automáticamente" desde un diccionario, una búsqueda u otro elemento.

Cuando pruebes estas variantes, recuerda enmascarar inputType con la constante apropiada. Las constantes de enmascaramiento disponibles se indican en la documentación de referencia de InputType.

Precaución: En tu propio IME, asegúrate de procesar correctamente el texto cuando lo envíes a un campo de contraseña. Oculta la contraseña en la IU tanto en la vista de entrada como en la de candidatos. También recuerda que no debes almacenar contraseñas en un dispositivo. Para obtener más información, consulta la guía Cómo diseñar de forma segura.

Cómo enviar texto a la aplicación

A medida que el usuario ingresa texto con tu IME, puedes enviarlo a la aplicación mediante el envío de eventos clave individuales o la edición del texto alrededor del cursor en el campo de texto de la aplicación. En cualquier caso, usas una instancia de InputConnection para entregar el texto. Para obtener esa instancia, llama a InputMethodService.getCurrentInputConnection().

Cómo editar el texto que aparece alrededor del cursor

Cuando procesas la edición de texto existente en un campo, algunos de los métodos más útiles de BaseInputConnection son los siguientes:

getTextBeforeCursor()
Muestra un objeto CharSequence que contiene la cantidad de caracteres solicitados antes de la posición actual del cursor.
getTextAfterCursor()
Muestra un objeto CharSequence que contiene la cantidad de caracteres solicitados que siguen a la posición actual del cursor.
deleteSurroundingText()
Borra la cantidad especificada de caracteres antes y después de la posición actual del cursor.
commitText()
Confirma un objeto CharSequence para el campo de texto y configura una nueva posición del cursor.

Por ejemplo, en el siguiente fragmento, se muestra cómo reemplazar los cuatro caracteres a la izquierda del cursor con el texto "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);

Cómo redactar texto antes de confirmar

Si tu IME hace predicciones de texto o requiere varios pasos para redactar un glifo o una palabra, puedes mostrar el progreso en el campo de texto hasta que el usuario confirme la palabra y, luego, puedes reemplazar la redacción parcial con el texto completado. Puedes darle un tratamiento especial al texto agregando un "intervalo" cuando lo pases a setComposingText().

En el siguiente fragmento, se indica cómo mostrar el progreso en un campo de texto:

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);

En las siguientes capturas de pantalla, se muestra cómo ve esto el usuario:

Figura 3: Cómo redactar un texto antes de confirmar

Cómo interceptar eventos clave de hardware

Aunque la ventana del método de entrada no tiene un enfoque explícito, primero recibe eventos clave de hardware y puede elegir entre usarlos o reenviarlos a la aplicación. Por ejemplo, es posible que quieras utilizar las teclas direccionales a fin de navegar dentro de la IU de la selección de candidatos durante la redacción. También es posible que desees capturar la tecla de retroceso para descartar cualquier ventana emergente que se origine en la ventana del método de entrada.

Para interceptar claves de hardware, anula onKeyDown() y onKeyUp().

Recuerda llamar al método super() para las claves que no quieras procesar.

Cómo crear un subtipo de IME

Los subtipos permiten que el IME exponga varios idiomas y modos de entrada compatibles con él. Un subtipo puede representar lo siguiente:

  • Una configuración regional, como en_US o fr_FR
  • Un modo de entrada, como voz, teclado o escritura a mano
  • Otros estilos, formas o propiedades de entrada específicos del IME, como diseños de 10 teclas o teclado QWERTY

En líneas generales, el modo puede ser cualquier texto, como "teclado", "voz", etc. Un subtipo también puede exponer una combinación de ellos.

La información de subtipo se usa para un diálogo de selector de IME que está disponible en la barra de notificaciones y también para la configuración del IME. La información también permite que el framework muestre directamente un subtipo específico de un IME. Cuando creas un IME, usas la facilidad de subtipo, ya que ayuda al usuario a identificar y alternar entre diferentes modos e idiomas de IME.

Defines los subtipos en uno de los archivos de recursos XML del método de entrada mediante el elemento <subtype>. En el siguiente fragmento, se define un IME con dos subtipos: un subtipo de teclado para la configuración regional de inglés de EE.UU. y otro para la configuración regional de idioma francés para Francia:

<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>

Para asegurarte de que tus subtipos estén bien etiquetados en la IU, usa %s a fin de obtener una etiqueta de subtipo que sea igual a la etiqueta de configuración regional del subtipo. Esto se demuestra en los siguientes dos fragmentos. En el primero, se muestra parte del archivo XML del método de entrada:

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

El siguiente fragmento es parte del archivo strings.xml del IME. El objeto label_subtype_generic del recurso de strings, utilizado por la definición de UI del método de entrada para configurar la etiqueta del subtipo, se define de la siguiente manera:

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

Esta configuración hace que el nombre visible del subtipo coincida con la configuración regional. Por ejemplo, en cualquier configuración regional en inglés, el nombre que se muestra es "inglés (Estados Unidos)".

Cómo seleccionar subtipos de IME de la barra de notificaciones

El sistema Android administra todos los subtipos expuestos por todos los IME. Los subtipos de IME se tratan como modos del IME al que pertenecen. En la barra de notificaciones, un usuario puede seleccionar un subtipo disponible para el IME actualmente configurado, como se muestra en la siguiente captura de pantalla:

Figura 4: Elección de un subtipo de IME desde la barra de notificaciones

Figura 5: Configuración de las preferencias de subtipo en Configuración del sistema

Cómo seleccionar los subtipos de IME desde Configuración del sistema

Un usuario puede controlar el uso de los subtipos en el panel de configuración "Idioma y entrada" del área de Configuración del sistema.

Figura 6: Cómo seleccionar un idioma para el IME

Cómo alternar entre subtipos de IME

Puedes permitir que los usuarios alternen fácilmente entre varios subtipos de IME proporcionando una tecla de cambio (por ejemplo, el ícono de idioma en forma de globo terráqueo) como parte del teclado. Esto mejora la usabilidad del teclado y ayuda a evitar que los usuarios se frustren. Para habilitar ese cambio, sigue estos pasos:

  1. Declara supportsSwitchingToNextInputMethod = "true" en los archivos de recursos XML del método de entrada. La declaración debería ser similar al siguiente fragmento:
    <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. Llama al método shouldOfferSwitchingToNextInputMethod().
  3. Si arroja un valor verdadero, muestra una tecla de cambio.
  4. Cuando el usuario presione la tecla de cambio, llama a switchToNextInputMethod() y pasa el valor falso al segundo parámetro. Un valor falso le indica al sistema que trate todos los subtipos por igual, independientemente de a qué IME pertenezcan. Para especificar un valor verdadero, se requiere que el sistema recorra los subtipos en el IME actual.

Precaución: Antes de Android 5.0 (nivel de API 21), switchToNextInputMethod() no reconoce el atributo supportsSwitchingToNextInputMethod. Si el usuario cambia a un IME sin una tecla de cambio, es posible que quede atascado en ese IME y no pueda salir fácilmente.

Consideraciones generales de IME

A continuación, se incluyen algunos aspectos que se deben tener en cuenta cuando implementas tu IME:

  • Ofrece a los usuarios una forma de configurar opciones desde la IU del IME.
  • Como se pueden instalar varios IME en el dispositivo, ofrece una forma para que el usuario cambie a un IME distinto directamente desde la IU del método de entrada.
  • Abre la IU del IME rápido. Precarga o carga a pedido cualquier recurso grande para que los usuarios vean el IME tan pronto como presionen un campo de texto. Almacena en caché recursos y vistas para invocaciones posteriores del método de entrada.
  • Por el contrario, a fin de que las aplicaciones puedan tener suficiente memoria para ejecutarse, debes liberar asignaciones de memoria grandes poco después de que se oculte la ventana del método de entrada. Considera usar un mensaje retrasado para liberar recursos si el IME está en estado oculto durante unos segundos.
  • Asegúrate de que los usuarios puedan ingresar tantos caracteres como sea posible para la configuración regional o el idioma asociados con el IME. Recuerda que los usuarios pueden usar signos de puntuación en contraseñas o nombres de usuario. Por lo tanto, tu IME debe ofrecer muchos caracteres diferentes para permitir que los usuarios ingresen una contraseña y obtengan acceso al dispositivo.