Compatibilidad de entrada en pantallas grandes

En dispositivos de pantalla grande, los usuarios suelen usar un teclado, un mouse, un panel táctil, una pluma stylus o un control de juegos para interactuar con las apps. Para permitir que tu app acepte entradas de dispositivos externos, haz lo siguiente:

  • Prueba la compatibilidad básica del teclado, como la navegación con las teclas Tab y de flecha del teclado, la tecla Intro para confirmar la entrada de texto y la barra espaciadora para reproducir y pausar contenido en apps de multimedia.
  • Agrega combinaciones de teclas estándares cuando corresponda (por ejemplo, Ctrl + Z para deshacer y Ctrl + S para guardar).
  • Prueba las interacciones básicas del mouse, como hacer clic con el botón derecho para el menú contextual y ver los cambios del ícono cuando se coloca el cursor sobre un elemento y los eventos de desplazamiento del panel táctil y de la rueda del mouse en las vistas personalizadas.
  • Prueba dispositivos de entrada específicos de la app, como la pluma stylus para apps de dibujo, los controles de juegos y los controles MIDI para apps de música.
  • Considera admitir tipos de entradas más avanzadas con las que la app podría destacarse en entornos de escritorio (por ejemplo, el panel táctil como dispositivo de reproducción sin pausa para apps de DJ, la captura del mouse para juegos y combinaciones de teclas más especializadas para quienes utilizan mucho el teclado).

Teclado

La manera en que la app responde a la entrada del teclado contribuye a una buena experiencia de pantalla grande. Existen tres tipos de entrada de teclado: navegación, pulsaciones de teclas y combinaciones de teclas.

La navegación con teclado casi nunca se implementa en apps centradas en pantallas táctiles, pero los usuarios la esperan cuando usan una app y tienen las manos en el teclado. También puede ser fundamental para los usuarios con necesidades de accesibilidad en teléfonos, tablets, dispositivos plegables y dispositivos de escritorio.

En muchas apps, solo se necesita una navegación simple con la tecla de flecha y la tecla Tab, que se gestiona automáticamente en el framework de Android. Por ejemplo, una vista de un objeto Button es enfocable de forma predeterminada y la navegación con teclado suele funcionar sin ningún código adicional. Para habilitar la navegación con teclado en las vistas que no son enfocables de forma predeterminada, los desarrolladores deben marcarlas como enfocables, lo cual se puede hacer de manera programática o en XML, como se muestra a continuación. Consulta Control de enfoque para obtener más información.

Kotlin

yourView.isFocusable = true

Java

yourView.setFocusable(true);

También puedes establecer el atributo focusable en el archivo del diseño:

android:focusable="true"

Una vez que se habilite el enfoque, el framework de Android creará una asignación de navegación para todas las vistas enfocables según su posición. Por lo general, funciona como se espera, y no se necesita ningún trabajo adicional. Cuando la asignación predeterminada no es correcta para las necesidades de una app, se puede anular de la siguiente manera:

Kotlin

// Arrow keys
yourView.nextFocusLeftId = R.id.view_to_left
yourView.nextFocusRightId = R.id.view_to_right
yourView.nextFocusTopId = R.id.view_above
yourView.nextFocusBottomId = R.id.view_below

// Tab key
yourView.nextFocusForwardId = R.id.next_view

Java

// Arrow keys
yourView.setNextFocusLeftId(R.id.view_to_left);
yourView.setNextFocusRightId(R.id.view_to_left);
yourView.setNextFocusTopId(R.id.view_to_left);
yourView.setNextFocusBottomId(R.id.view_to_left);

// Tab key
yourView.setNextFocusForwardId(R.id.next_view);

Te recomendamos que intentes acceder, solo con el teclado, a todas las funciones de la app antes de cada lanzamiento. Debería ser fácil acceder a las acciones más habituales sin usar el mouse o mediar una entrada táctil.

Recuerda que la compatibilidad con el teclado puede ser fundamental para los usuarios con necesidades de accesibilidad.

Pulsaciones de teclas

Para la entrada de texto que se controlaría con un teclado virtual en pantalla (IME), como un objeto EditText, las apps deberían comportarse como se espera en dispositivos de pantalla grande, sin que se requieran acciones adicionales por parte del desarrollador. En el caso de que el framework no pueda anticipar las pulsaciones de teclas, las apps deberán controlar el comportamiento por sí mismas, en especial, las que tienen vistas personalizadas.

Algunos ejemplos son las apps de chat que usan la tecla Intro para enviar un mensaje, las apps de multimedia que inician y detienen la reproducción con la barra espaciadora, y los juegos que controlan el movimiento con las teclas W, A, S y D.

La mayoría de las apps anulan la devolución de llamada onKeyUp() y agregan el comportamiento esperado para cada código de clave recibido, como se muestra a continuación:

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
    return when (keyCode) {
        KeyEvent.KEYCODE_ENTER -> {
            sendChatMessage()
            true
        }
        KeyEvent.KEYCODE_SPACE -> {
            playOrPauseMedia()
            true
        }
        else -> super.onKeyUp(keyCode, event)
    }
}

Java

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_ENTER) {
        sendMessage();
        return true;
    } else if (KeyEvent.KEYCODE_SPACE){
        playOrPauseMedia();
        return true;
    } else {
        return super.onKeyUp(keyCode, event);
    }
}

Un evento onKeyUp se produce cuando se suelta una tecla. El uso de esta devolución de llamada impide que las apps necesiten procesar varios eventos onKeyDown si se mantiene presionada una tecla o si esta se suelta lentamente. Los juegos y las apps que desean saber el momento en que se presiona una tecla o que esperan que los usuarios mantengan presionadas las teclas pueden buscar el evento onKeyDown() y controlar los eventos onKeyDown repetidos.

Si deseas obtener más información para proporcionar compatibilidad con el teclado, consulta Cómo controlar las acciones del teclado.

Combinaciones de teclas

Cuando se utiliza un teclado de hardware, se esperan combinaciones de teclas habituales basadas en Ctrl, Alt y Mayúsculas. Si una app no las implementa, la experiencia puede resultar frustrante para los usuarios. Los usuarios avanzados también valoran estas combinaciones para las tareas específicas de la app que se usan con frecuencia. Las combinaciones de teclas facilitan el uso de una app y la diferencian de aquellas que no las tienen.

Algunas combinaciones de teclas habituales incluyen Ctrl + S (guardar), Ctrl + Z (deshacer) y Ctrl + Mayúsculas + Z (rehacer). Para ver un ejemplo de combinaciones de teclas más avanzadas, consulta la lista de combinaciones de teclas de VLC Media Player.

Se pueden implementar combinaciones de teclas con dispatchKeyShortcutEvent(). Esto intercepta todas las combinaciones de teclas meta (Alt, Ctrl y Mayúsculas) para un código de clave determinado. Para verificar una tecla meta específica, usa KeyEvent.isCtrlPressed(), KeyEvent.isShiftPressed(), KeyEvent.isAltPressed() o KeyEvent.hasModifiers().

La separación del código de combinaciones de teclas de otros controles de pulsaciones de teclas (como los eventos onKeyUp() o onKeyDown()) puede facilitar el mantenimiento del código y habilitar la aceptación predeterminada de las teclas meta sin tener que implementar, de forma manual, las comprobaciones de las teclas meta en cada caso. Permitir todas las combinaciones de teclas meta también puede resultar más conveniente para los usuarios que están acostumbrados a diferentes diseños de teclado y sistemas operativos.

Kotlin

override fun dispatchKeyShortcutEvent(event: KeyEvent): Boolean {
  return when (event.keyCode) {
    KeyEvent.KEYCODE_O -> {
      openFile() // Ctrl+O, Shift+O, Alt+O
      true
    }
    KeyEvent.KEYCODE_Z-> {
      if (event.isCtrlPressed) {
        if (event.isShiftPressed) {
          redoLastAction() // Ctrl+Shift+Z pressed
          true
        } else {
          undoLastAction() // Ctrl+Z pressed
          true
        }
      }
    }
    else -> {
      return super.dispatchKeyShortcutEvent(event)
    }
  }
}

Java

@Override
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
  if (event.getKeyCode() == KeyEvent.KEYCODE_O) {
      openFile(); // Ctrl+O, Shift+O, Alt+O
      return true;
  } else if(event.getKeyCode() == KeyEvent.KEYCODE_Z) {
      if (event.isCtrlPressed()) {
          if (event.isShiftPressed()) {
              redoLastAction();
              return true;
          }
          else {
              undoLastAction();
              return true;
          }
      }
  }
  return super.dispatchKeyShortcutEvent(event);
}

También puedes implementar combinaciones de teclas en el objeto onKeyUp() si verificas los elementos KeyEvent.isCtrlPressed(), KeyEvent.isShiftPressed() o KeyEvent.isAltPressed() de la misma manera que se explicó anteriormente. Se puede mantener, de forma más fácil, si el comportamiento meta es más una modificación del comportamiento de una app que una combinación de teclas. Por ejemplo, cuando W significa "caminar hacia adelante" y Mayúscula + W significa "correr hacia adelante".

Kotlin

override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
  return when(keyCode) {
    KeyEvent.KEYCODE_W-> {
      if (event.isShiftPressed) {
        if (event.isCtrlPressed) {
          flyForward() // Ctrl+Shift+W pressed
          true
        } else {
          runForward() // Shift+W pressed
          true
        }
      } else {
        walkForward() // W pressed
        true
      }
    }
    else -> super.onKeyUp(keyCode, event)
  }
}

Java

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_W) {
        if (event.isShiftPressed()) {
            if (event.isCtrlPressed()) {
                flyForward(); // Ctrl+Shift+W pressed
                return true;
            } else {
                runForward(); // Shift+W pressed
                return true;
            }
        } else {
            walkForward();
            return true;
        }
    }
    return super.onKeyUp(keyCode, event);
}

Pluma stylus

Muchos dispositivos de pantalla grande incluyen una pluma stylus, y las apps para Android las controlan como entrada de pantalla táctil. Algunos dispositivos también pueden tener un tablero de dibujo con USB o Bluetooth, como Wacom Intuos. Las apps para Android pueden recibir entradas Bluetooth, pero no funcionarán con entrada USB.

Un evento de pluma stylus se informa como un evento de pantalla táctil a través de los objetos View.onTouchEvent() o View.onGenericMotionEvent() y contiene un elemento MotionEvent.getSource() del tipo SOURCE_STYLUS.

El objeto MotionEvent también contendrá datos adicionales:

Puntos históricos

Android agrupa eventos de entrada y los entrega una vez por fotograma. Una pluma stylus puede informar eventos con frecuencias mucho más altas que la pantalla. Cuando creas apps de dibujo, es importante verificar los eventos que pueden estar en el pasado reciente mediante las APIs de getHistorical:

  • MotionEvent.getHistoricalX()
  • MotionEvent.getHistoricalY()
  • MotionEvent.getHistoricalPressure()
  • MotionEvent.getHistoricalAxisValue()

Rechazo de la palma

Cuando los usuarios dibujan, escriben o interactúan con la app con una pluma stylus, a veces tocan la pantalla con la palma de las manos. El evento táctil (configurado en ACTION_DOWN o ACTION_POINTER_DOWN) se puede informar a tu app antes de que el sistema reconozca e ignore el toque inadvertido de la palma.

Para cancelar los eventos táctiles de la palma, Android envía un MotionEvent. Si tu app recibe ACTION_CANCEL, cancela el gesto. Si tu app recibe ACTION_POINTER_UP, verifica si FLAG_CANCELED está configurado. Si es así, cancela el gesto.

No verifiques solo FLAG_CANCELED. A partir de Android 13, por conveniencia, el sistema establece FLAG_CANCELED para eventos ACTION_CANCEL, pero esto no sucede para versiones anteriores.

Android 12

En Android 12 (nivel de API 32) y versiones anteriores, solo se puede detectar el rechazo de la palma de la mano para eventos táctiles de un solo puntero. Si un toque de palma es el único puntero, el sistema configura ACTION_CANCEL en el objeto de evento de movimiento para cancelar el evento. Si otros punteros están inactivos, el sistema establece ACTION_POINTER_UP, que no es suficiente para detectar el rechazo de la palma.

Android 13

En Android 13 (nivel de API 33) y versiones posteriores, si un toque de palma es el único puntero, el sistema configura ACTION_CANCEL y FLAG_CANCELED en el objeto de evento de movimiento para cancelar el evento. Si otros punteros están inactivos, el sistema establece ACTION_POINTER_UP y FLAG_CANCELED.

Cuando tu app reciba un evento de movimiento con ACTION_POINTER_UP, busca FLAG_CANCELED para determinar si el evento indica el rechazo de la palma (o cualquier otra cancelación del evento).

Apps para tomar notas

ChromeOS tiene un intent especial que les muestra a los usuarios apps registradas para tomar notas. Para registrar una app como una para tomar notas, agrega lo siguiente al manifiesto de Android:

<intent-filter>
    <action android:name="org.chromium.arc.intent.action.CREATE_NOTE" />
    <category android:name="android.intent.category.DEFAULT" />
</intent-filter>

Cuando se registra una app, el usuario puede seleccionarla como la predeterminada para tomar notas. Cuando se solicita una nota nueva, la app debe crear una nota vacía y lista para la entrada de la pluma stylus. Cuando el usuario desea escribir en una imagen (como una captura de pantalla o una imagen descargada), la app se inicia con el objeto ClipData que contiene uno o más elementos con URI de content://. La app debe crear una nota que use la primera imagen adjunta como imagen de fondo y que ingrese un modo en el que el usuario pueda dibujarla en la pantalla con una pluma stylus.

Cómo probar intents para tomar notas sin una pluma stylus

Si quieres probar si una app responde correctamente a los intents para tomar notas sin una pluma stylus activa, usa el siguiente método para mostrar las opciones correspondientes en ChromeOS:

  1. Cambia al modo de desarrollo y haz que se pueda escribir en el dispositivo.
  2. Presiona Ctrl + Alt + F2 para abrir una terminal.
  3. Ejecuta el comando sudo vi /etc/chrome_dev.conf.
  4. Presiona i para editar y agregar el elemento --ash-enable-palette a una nueva línea al final del archivo.
  5. Para guardar, presiona Esc, escribe :, w, q y presiona Intro.
  6. Presiona Ctrl + Alt + F1 para volver a la IU normal de ChromeOS.
  7. Sal y vuelve a acceder.

Ahora debería haber un menú de la pluma stylus en la barra:

  • Presiona el botón de la pluma stylus en la barra y elige Nueva nota. Se debería abrir una nota de dibujo en blanco.
  • Toma una captura de pantalla. En la barra, selecciona el botón de la pluma stylus > Captura de pantalla o descarga una imagen. En la notificación, debería aparecer la opción "Escribir en la imagen". Se debería iniciar la app con la imagen lista para poder escribir sobre ella.

Compatibilidad con mouse y panel táctil

Por lo general, la mayoría de las apps solo necesitan controlar tres eventos grandes centrados en pantallas grandes: hacer clic con el botón derecho .colocar el cursor sobre un elemento yarrastrar y soltar.

Hacer clic con el botón derecho

Cualquier acción que permita que una app muestre un menú contextual, como mantener presionado un elemento de la lista, también debe responder a eventos de clic con el botón derecho. Para controlar eventos de clic con el botón derecho, las apps deben registrar un objeto View.OnContextClickListener. Obtén información detallada para crear un menú contextual: consulta Cómo crear menús contextuales.

Kotlin

yourView.setOnContextClickListener {
  showContextMenu()
  true
}

Java

yourView.setOnContextClickListener(v -> {
    showContextMenu();
    return true;
});

Colocar el cursor sobre un elemento

Los desarrolladores pueden lograr que los diseños de sus apps se sientan optimizados y más fáciles de usar si se controlan los eventos de desplazamiento, en especial, las vistas personalizadas. Los dos ejemplos más comunes son los siguientes:

  • Cambiar el ícono del puntero del mouse para indicarles a los usuarios si un elemento tiene un comportamiento interactivo, como elementos que se pueden editar o en los que se puede hacer clic
  • Agregar comentarios visuales a los elementos en una lista o una cuadrícula grande cuando el puntero se coloca sobre ellos

Kotlin

// Change the icon to a "hand" pointer on hover,
// Highlight the view by changing the background.
yourView.setOnHoverListener { view, _ ->
  addVisualHighlighting(true)
  view.pointerIcon =
    PointerIcon.getSystemIcon(view.context,
    PointerIcon.TYPE_HAND)
  false // listener did not consume the event.
}

Java

yourView.setOnHoverListener((view, event) -> {
    addVisualHighlighting(true);
    view.setPointerIcon(PointerIcon
            .getSystemIcon(view.getContext(), PointerIcon.TYPE_HAND));
    return true;
});

Arrastrar y soltar

En un entorno multiventana, los usuarios esperan poder arrastrar y soltar elementos entre apps. Esto se aplica a dispositivos de escritorio, así como a tablets, teléfonos y dispositivos plegables en modo de pantalla dividida.

Los desarrolladores deben considerar si es probable que los usuarios arrastren elementos a su app. Entre algunos ejemplos comunes, se incluyen los siguientes: editores de fotos que esperan recibir fotos, reproductores de audio que esperan recibir archivos de audio y programas de dibujo que esperan recibir fotos.

Para agregar compatibilidad con la función de arrastrar y soltar, sigue la documentación de Arrastrar y soltar de Android y consulta esta entrada de blog de ChromeOS.

Consideraciones especiales para ChromeOS

  • Recuerda solicitar permiso a través del objeto requestDragAndDropPermissions para acceder a los elementos que se arrastren desde fuera de la app.
  • Un elemento debe tener la marca View.DRAG_FLAG_GLOBAL a fin de poder arrastrarlo a otras aplicaciones.

Compatibilidad con puntero avanzado

Las apps que realizan un control avanzado de la entrada del mouse y del panel táctil deben seguir la documentación de Android para el elemento View.onGenericMotionEvent() y usar el objeto MotionEvent.getSource() a los efectos de distinguir entre los parámetros SOURCE_MOUSE y SOURCE_TOUCHSCREEN.

Examina el objeto MotionEvent para implementar el comportamiento requerido:

  • El movimiento genera los eventos ACTION_HOVER_MOVE.
  • Los botones generan los eventos ACTION_BUTTON_PRESS y ACTION_BUTTON_RELEASE. También puedes verificar el estado actual de todos los botones del mouse y del panel táctil con el objeto getButtonState().
  • El desplazamiento de la rueda del mouse genera eventos ACTION_SCROLL.

Controles de juegos

Algunos dispositivos Android de pantalla grande admiten hasta cuatro controles de juegos. Los desarrolladores deben usar las API estándar de control de juegos de Android a fin de manejarlos (consulta Cómo brindar compatibilidad con controles de juegos).

Los botones se asignan a valores comunes después de una asignación común. Lamentablemente, no todos los fabricantes de controles de juegos siguen las mismas convenciones de asignación. Puedes proporcionar una experiencia mucho mejor si permites que los usuarios seleccionen diferentes asignaciones populares para controles. Si deseas obtener más información, consulta Cómo procesar la presión de las teclas de los botones de los controles de juegos.

Modo de traducción de entrada

ChromeOS habilita un modo de traducción de entrada de forma predeterminada. En la mayoría de las apps para Android, ese modo ayuda a que funcionen como se espera en un entorno de escritorio. Algunos ejemplos incluyen la habilitación automática del desplazamiento con dos dedos en el panel táctil, el desplazamiento con la rueda del mouse y la asignación de coordenadas de pantalla sin procesar a las coordenadas de la ventana. Por lo general, los desarrolladores de apps no necesitan implementar ninguno de esos comportamientos.

Si una app implementa un comportamiento de entrada personalizado (por ejemplo, definir una acción personalizada de pellizcar con dos dedos en el panel táctil) o si esas traducciones de entrada no proporcionan los eventos de entrada que espera la app, puedes inhabilitar el modo de traducción de entrada agregando la siguiente etiqueta al manifiesto de Android:

<uses-feature
    android:name="android.hardware.type.pc"
    android:required="false" />

Recursos adicionales