Cómo comenzar a usar tarjetas


Para comenzar a proporcionar tarjetas de tu app, incluye las siguientes dependencias en el archivo build.gradle.

Groovy

dependencies {
    // Use to implement support for wear tiles
    implementation "androidx.wear.tiles:tiles:1.4.1"

    // Use to utilize standard components and layouts in your tiles
    implementation "androidx.wear.protolayout:protolayout:1.2.1"

    // Use to utilize components and layouts with Material Design in your tiles
    implementation "androidx.wear.protolayout:protolayout-material:1.2.1"

    // Use to include dynamic expressions in your tiles
    implementation "androidx.wear.protolayout:protolayout-expression:1.2.1"

    // Use to preview wear tiles in your own app
    debugImplementation "androidx.wear.tiles:tiles-renderer:1.4.1"

    // Use to fetch tiles from a tile provider in your tests
    testImplementation "androidx.wear.tiles:tiles-testing:1.4.1"
}

Kotlin

dependencies {
    // Use to implement support for wear tiles
    implementation("androidx.wear.tiles:tiles:1.4.1")

    // Use to utilize standard components and layouts in your tiles
    implementation("androidx.wear.protolayout:protolayout:1.2.1")

    // Use to utilize components and layouts with Material Design in your tiles
    implementation("androidx.wear.protolayout:protolayout-material:1.2.1")

    // Use to include dynamic expressions in your tiles
    implementation("androidx.wear.protolayout:protolayout-expression:1.2.1")

    // Use to preview wear tiles in your own app
    debugImplementation("androidx.wear.tiles:tiles-renderer:1.4.1")

    // Use to fetch tiles from a tile provider in your tests
    testImplementation("androidx.wear.tiles:tiles-testing:1.4.1")
}

Conceptos clave

Las tarjetas no se compilan de la misma manera que las apps para Android y usan diferentes conceptos:

  • Plantillas de diseño: Definen la disposición general de los elementos visuales en la pantalla. Esto se logra con la función primaryLayout().
  • Elementos de diseño: Representan un elemento gráfico individual, como un botón o una tarjeta, o varios elementos de este tipo agrupados con una columna, buttonGroup o algo similar. Estos se incorporan en una plantilla de diseño.
  • Recursos: Los objetos ResourceBuilders.Resources consisten en un mapa de pares clave-valor de los recursos de Android (imágenes) que se requieren para renderizar un diseño y una versión.
  • Cronología: Un objeto TimelineBuilders.Timeline es una lista de una o más instancias de un objeto de diseño. Puedes proporcionar varios mecanismos y expresiones para indicar cuándo el renderizador debe cambiar de un objeto de diseño a otro, por ejemplo, para dejar de mostrar un diseño en un momento específico.
  • Estado: Es una estructura de datos de tipo StateBuilders.State que se pasa entre la tarjeta y la app para permitir que los dos componentes se comuniquen entre sí. Por ejemplo, si se presiona un botón en la tarjeta, el estado contiene el ID del botón. También puedes intercambiar tipos de datos con un mapa.
  • Tarjeta: Es un objeto TileBuilders.Tile que representa una tarjeta, que consta de un cronograma, un ID de versión de recursos, un intervalo de actualización y un estado.
  • Protolayout: Este término aparece en el nombre de varias clases relacionadas con tarjetas y hace referencia a la biblioteca de Protolayout de Wear OS, una biblioteca de gráficos que se usa en varias plataformas de Wear OS.

Cómo crear una tarjeta

Para proporcionar una tarjeta desde tu app, implementa un servicio de tipo TileService y regístralo en tu manifiesto. A partir de esto, el sistema solicita las tarjetas necesarias durante las llamadas a onTileRequest() y los recursos durante las llamadas a onTileResourcesRequest().

class MyTileService : TileService() {

    override fun onTileRequest(requestParams: RequestBuilders.TileRequest) =
        Futures.immediateFuture(
            Tile.Builder()
                .setResourcesVersion(RESOURCES_VERSION)
                .setTileTimeline(
                    Timeline.fromLayoutElement(
                        materialScope(this, requestParams.deviceConfiguration) {
                            primaryLayout(
                                mainSlot = {
                                    text("Hello, World!".layoutString, typography = BODY_LARGE)
                                }
                            )
                        }
                    )
                )
                .build()
        )

    override fun onTileResourcesRequest(requestParams: ResourcesRequest) =
        Futures.immediateFuture(
            Resources.Builder().setVersion(RESOURCES_VERSION).build()
        )
}

A continuación, agrega un servicio dentro de la etiqueta <application> del archivo AndroidManifest.xml.

<service
    android:name=".snippets.m3.tile.MyTileService"
    android:label="@string/tile_label"
    android:description="@string/tile_description"
    android:icon="@mipmap/ic_launcher"
    android:exported="true"
    android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
    <intent-filter>
        <action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
    </intent-filter>

    <meta-data android:name="androidx.wear.tiles.PREVIEW"
        android:resource="@drawable/tile_preview" />
</service>

El filtro de intents y permisos registra este servicio como un proveedor de tarjetas.

El ícono, la etiqueta, la descripción y el recurso de vista previa se muestran al usuario cuando este configura tarjetas en su teléfono o reloj. Ten en cuenta que el recurso de vista previa admite todos los calificadores de recursos estándar de Android, por lo que es posible variar la vista previa según factores como el tamaño de la pantalla y el idioma del dispositivo. Consulta la lista de tareas de la vista previa para obtener recomendaciones adicionales.

Implementa tu app y agrega la tarjeta al carrusel de tarjetas (también hay una forma más fácil para los desarrolladores de obtener una vista previa de una tarjeta, pero por ahora hazlo de forma manual).

Tarjeta &quot;Hello World&quot;.
Figura 1. Tarjeta "Hello World".

Para ver un ejemplo completo, consulta la muestra de código en GitHub o el codelab.

Cómo crear una IU para tarjetas

Los elementos de la IU expresiva de Material 3 se crean con un enfoque estructurado que se basa en el patrón de compilador de acceso seguro a tipos de Kotlin.

Diseño

Para crear tu diseño, haz lo siguiente:

  1. Inicia un alcance de Material Design: Llama a la función materialScope() y proporciona los context y deviceConfiguration necesarios. Puedes incluir parámetros opcionales, como allowDynamicTheme y defaultColorScheme. El allowDynamicTheme es true de forma predeterminada, y defaultColorScheme representa el ColorScheme que se usa cuando los colores dinámicos no están disponibles (por ejemplo, cuando el usuario desactivó la función) o cuando el dispositivo no la admite, o cuando allowDynamicTheme es false).

  2. Compila tu IU dentro del alcance: Todos los componentes de la IU para un diseño de tarjeta determinado deben definirse dentro de la lambda de una sola llamada materialScope() de nivel superior. Estas funciones de componentes, como primaryLayout() y textEdgeButton(), son funciones de extensión en MaterialScope y solo están disponibles cuando se las llama en este alcance del receptor.

    materialScope(
        context = context,
        deviceConfiguration = requestParams.deviceConfiguration, // requestParams is passed to onTileRequest
        defaultColorScheme = myFallbackColorScheme
    ) {
        // inside the MaterialScope, you can call functions like primaryLayout()
        primaryLayout(
            titleSlot = { text(text = "Title".layoutString) },
            mainSlot = { text(text = "Main Content".layoutString) },
            bottomSlot = { textEdgeButton(text = "Action".layoutString) }
        )
    }
    

Máquinas tragamonedas

En M3, el diseño de tarjetas usa un enfoque inspirado en Compose que hace uso de tres ranuras distintas. De arriba abajo, son los siguientes:

  1. El titleSlot, por lo general, para un título o encabezado principal.
  2. El mainSlot, para el contenido principal.
  3. El bottomSlot, que se usa a menudo para acciones o información complementaria. También es aquí donde aparece un botón de borde.
Diseño de tarjetas que muestra titleSlot, mainSlot y bottomSlot
Figura 2: titleSlot, mainSlot y bottomSlot.

El contenido de cada espacio es el siguiente:

  • titleSlot (opcional): Por lo general, son algunas palabras generadas por text().
  • mainSlot (obligatorio): Componentes organizados en estructuras como filas, columnas y grupos de botones. Estos componentes también se pueden incorporar de forma recursiva entre sí; por ejemplo, una columna puede contener filas.
  • bottomSlot (opcional): Por lo general, se completa con un botón que se ajusta a los bordes o una etiqueta de texto.

Como no se puede desplazar las tarjetas, no hay componentes para paginar, desplazar ni controlar listas largas de contenido. Asegúrate de que el contenido permanezca visible cuando el tamaño de la fuente aumente o el texto se haga más largo debido a la traducción.

Componentes de IU

La biblioteca de protolayout-material3 proporciona una gran cantidad de componentes diseñados según las especificaciones de Material 3 Expressive y las recomendaciones de la interfaz de usuario.

Botones

  • textButton(): Es un botón con una sola ranura para contenido de texto (breve).
  • iconButton(): Es un botón con una sola ranura para representar un ícono.
  • avatarButton(): Es un botón de avatar con forma de píldora que ofrece hasta tres ranuras para tomar contenido que representa una etiqueta y una etiqueta secundaria apiladas verticalmente, y una imagen (avatar) junto a ella.
  • imageButton(): Es un botón de imagen en el que se puede hacer clic que no ofrece espacios adicionales, solo la imagen (por ejemplo, backgroundImage como fondo).
  • compactButton(): Es un botón compacto que ofrece hasta dos ranuras para incluir contenido apilado horizontalmente que representa un ícono y texto junto a él.
  • button(): Es un botón con forma de píldora que ofrece hasta tres ranuras para incluir contenido que representa una etiqueta y una etiqueta secundaria apiladas verticalmente, y un ícono junto a ella.

Botones de Edge

  • iconEdgeButton(): Es un botón de borde que ofrece una sola ranura para colocar un ícono o contenido pequeño y redondo similar.
  • textEdgeButton(): Es un botón de borde que ofrece un solo espacio para ingresar texto o contenido similarmente largo y ancho.

Tarjetas

  • titleCard(): Es una tarjeta de título que ofrece de uno a tres espacios, por lo general, basados en texto.
  • appCard(): Es una tarjeta de aplicación que ofrece hasta cinco espacios, por lo general, basados en texto.
  • textDataCard(): Tarjeta de datos que ofrece hasta tres ranuras apiladas verticalmente, por lo general, basadas en texto o números.
  • iconDataCard(): Tarjeta de datos que ofrece hasta tres ranuras apiladas verticalmente, por lo general, con texto o números, con un ícono
  • graphicDataCard(): Es una tarjeta de datos gráficos que ofrece una ranura para datos gráficos, como un indicador de progreso, y hasta dos ranuras apiladas verticalmente, por lo general, para descripciones de texto.

Indicadores de progreso

Cómo agrupar elementos de diseño

  • buttonGroup(): Es un diseño de componentes que ubica sus elementos secundarios en una secuencia horizontal.
  • primaryLayout(): Es un diseño de pantalla completa que representa un estilo de diseño M3 sugerido que es responsivo y se ocupa de la ubicación de los elementos, junto con los márgenes y el padding recomendados.

Temas

En Material 3 expresivo, el sistema de colores se define por 29 roles de color estándar, organizados en seis grupos: primario, secundario, terciario, error, superficie y contorno.

Sistema de colores expresivos de Material 3
Figura 3: El sistema de colores expresivos de Material 3.

Un ColorScheme asigna cada uno de estos 29 roles a un color correspondiente y, como forma parte de MaterialScope y los componentes se deben crear dentro de él, toman automáticamente los colores del esquema. Este enfoque permite que todos los elementos de la IU se adhieran automáticamente a los estándares de Material Design.

Para permitir que los usuarios elijan entre un esquema de colores que definas (como uno que refleje los colores de tu marca) y uno que proporcione el sistema (ya sea derivado de la cara de reloj actual del usuario o uno que elija el usuario), inicializa MaterialScope de la siguiente manera:

val myColorScheme =
    ColorScheme(
        primary = ...
        onPrimary = ...
        // 27 more
    )

materialScope(
  defaultColorScheme = myColorScheme
) {
  // If the user selects "no theme" in settings, myColorScheme is used.
  // Otherwise, the system-provided theme is used.
}

Para forzar que tus tarjetas aparezcan en el esquema de colores que proporcionas, inhabilita la compatibilidad con temas dinámicos configurando allowDynamicTheme como false:

materialScope(
  allowDynamicTheme = false,
  defaultColorScheme = myColorScheme
) {
  // myColorScheme is *always* used.
}

Color

Cada componente individual usa un subconjunto de los 29 roles de color definidos por un ColorScheme. Por ejemplo, los botones usan hasta cuatro colores, que de forma predeterminada se toman del grupo "primary" del ColorScheme activo:

Token de componente ButtonColors El rol de ColorScheme
containerColor primary
iconColor onPrimary
labelColor onPrimary
secondaryLabelColor onPrimary (opacidad 0.8)

Es posible que debas desviarte de los tokens de color predeterminados para elementos específicos de la IU. Por ejemplo, es posible que desees que un textEdgeButton use colores del grupo “secundario” o “terciario”, en lugar de “primario”, para destacarse y brindar un mejor contraste.

Puedes personalizar los colores de los componentes de varias maneras:

  1. Usa una función auxiliar para los colores predefinidos. Usa funciones auxiliares como filledTonalButtonColors() para aplicar los estilos de botones estándar de Material 3 expresivo. Estas funciones crean instancias preconfiguradas de ButtonColors que asignan estilos comunes, como rellenos, tonales o con contornos, a los roles adecuados del ColorScheme activo dentro de MaterialScope. Esto te permite aplicar estilos coherentes sin definir manualmente cada color para los tipos de botones comunes.

    textEdgeButton(
        colors = filledButtonColors() // default
        /* OR colors = filledTonalButtonColors() */
        /* OR colors = filledVariantButtonColors() */
        // ... other parameters
    )
    

    Para las tarjetas, usa la familia de funciones filledCardColors() equivalente.

    También puedes modificar el objeto ButtonColors que muestran las funciones auxiliares con su método copy() si solo necesitas cambiar uno o dos tokens:

    textEdgeButton(
        colors =
            filledButtonColors()
                .copy(
                    containerColor = colorScheme.tertiary,
                    labelColor = colorScheme.onTertiary
                )
        // ... other parameters
    )
    
  2. Proporciona roles de color de reemplazo de forma explícita. Crea tu propio objeto ButtonColors y pásalo al componente. Para las tarjetas, usa el objeto CardColors equivalente.

    textEdgeButton(
        colors =
            ButtonColors(
                // the materialScope makes colorScheme available
                containerColor = colorScheme.secondary,
                iconColor = colorScheme.secondaryDim,
                labelColor = colorScheme.onSecondary,
                secondaryLabelColor = colorScheme.onSecondary
            )
        // ... other parameters
    )
    
  3. Especifica colores fijos (úsalos con precaución). Si bien, por lo general, se recomienda especificar los colores por su rol semántico (p.ej., colorScheme.primary), también puedes proporcionar valores de color directos. Este enfoque debe usarse con moderación, ya que puede generar inconsistencias con el tema general, especialmente si este cambia de forma dinámica.

    textEdgeButton(
        colors = filledButtonColors().copy(
            containerColor = android.graphics.Color.RED.argb, // Using named colors
            labelColor = 0xFFFFFF00.argb // Using a hex code for yellow
        )
        // ... other parameters
    )
    

Tipografía

Para crear coherencia visual en la plataforma de Wear OS y optimizar el rendimiento, todo el texto de las tarjetas se renderiza con una fuente proporcionada por el sistema. Es decir, las tarjetas no admiten tipos de letra personalizados. En Wear OS 6 y versiones posteriores, esta es una fuente específica del OEM. En la mayoría de los casos, será una fuente variable, que ofrece una experiencia más expresiva y un control más detallado.

Para crear un estilo de texto, por lo general, se usa el método text() combinado con constantes tipográficas. Este componente te permite usar roles de tipografía predefinidos en Material 3 expresivo, lo que ayuda a que tu tarjeta cumpla con las prácticas recomendadas de tipografía establecidas para la legibilidad y la jerarquía. La biblioteca ofrece un conjunto de 18 constantes de tipografía semántica, como BODY_MEDIUM. Estas constantes también afectan a los ejes de la fuente, además del tamaño.

text(
    text = "Hello, World!".layoutString,
    typography = BODY_MEDIUM,
)

Para obtener más control, puedes proporcionar parámetros de configuración adicionales. En Wear OS 6 y versiones posteriores, es probable que se use una fuente variable, que puedes modificar en los ejes cursiva, grosor, ancho y redondez. Puedes controlar estos ejes con el parámetro settings:

text(
    text = "Hello, World".layoutString,
    italic = true,

    // Use elements defined in androidx.wear.protolayout.LayoutElementBuilders.FontSetting
    settings =
        listOf(weight(500), width(100F), roundness(100)),
)

Por último, si necesitas controlar el tamaño o el espacio entre letras (no recomendado), usa basicText() en lugar de text() y construye un valor para la propiedad fontStyle con fontStyle().

Forma y márgenes

Puedes cambiar el radio de las esquinas de casi todos los componentes con su propiedadshape. Los valores provienen de la propiedad MaterialScope shapes:

textButton(
   height = expand(),
   width = expand(),
   shape = shapes.medium, // OR another value like shapes.full
   colors = filledVariantButtonColors(),
   labelContent = { text("Hello, World!".layoutString) },
)

Después de alterar la forma de un componente, si crees que deja demasiado o muy poco espacio alrededor del borde de la pantalla, ajusta los márgenes con el parámetro margin de primaryLayout():

primaryLayout(
    mainSlot = {
        textButton(
            shape = shapes.small,
            /* ... */
        )
    },
    // margin constants defined in androidx.wear.protolayout.material3.PrimaryLayoutMargins
    margins = MAX_PRIMARY_LAYOUT_MARGIN,
)

Arcos

Se admiten los siguientes contenedores secundarios de Arc:

  • ArcLine: Renderiza una línea curva alrededor del arco.
  • ArcText: Renderiza el texto curvo en el arco.
  • ArcAdapter: Renderiza un elemento de diseño básico en el arco, dibujado en una tangente al arco.

Para obtener más información, consulta la documentación de referencia de cada uno de los tipos de elementos.

Modificadores

De manera opcional, a cada elemento de diseño disponible se le pueden aplicar modificadores. Usa estos modificadores con los siguientes fines:

  • Cambia la apariencia visual del diseño. Por ejemplo, agrega un fondo, un borde o un padding al elemento de diseño.
  • Agrega metadatos sobre el diseño. Por ejemplo, agrega un modificador de semántica a tu elemento de diseño para usarlo con lectores de pantalla.
  • Agrega funcionalidad. Por ejemplo, agrega un modificador en el que se pueda hacer clic a tu elemento de diseño para que la tarjeta sea interactiva. Para obtener más información, consulta el artículo sobre interacciones con tarjetas.

Por ejemplo, podemos personalizar el aspecto y los metadatos predeterminados de un Image, como se muestra en la siguiente muestra de código:

Kotlin

private fun myImage(): LayoutElement =
    Image.Builder()
        .setWidth(dp(24f))
        .setHeight(dp(24f))
        .setResourceId("image_id")
        .setModifiers(Modifiers.Builder()
            .setBackground(Background.Builder().setColor(argb(0xFFFF0000)).build())
            .setPadding(Padding.Builder().setStart(dp(12f)).build())
            .setSemantics(Semantics.builder()
                .setContentDescription("Image description")
                .build()
            ).build()
        ).build()

Java

private LayoutElement myImage() {
   return new Image.Builder()
           .setWidth(dp(24f))
           .setHeight(dp(24f))
           .setResourceId("image_id")
           .setModifiers(new Modifiers.Builder()
                   .setBackground(new Background.Builder().setColor(argb(0xFFFF0000)).build())
                   .setPadding(new Padding.Builder().setStart(dp(12f)).build())
                   .setSemantics(new Semantics.Builder()
                           .setContentDescription("Image description")
                           .build()
                   ).build()
           ).build();
}

Spannables

Un Spannable es un tipo especial de contenedor que presenta los elementos de manera similar al texto. Es útil cuando deseas aplicar un estilo diferente a una sola subcadena en un bloque de texto más grande, lo que no es posible con el elemento Text.

Un contenedor Spannable se llena con elementos secundarios Span. No se permiten otras instancias secundarias ni instancias Spannable anidadas.

Hay dos tipos de elementos Span secundarios:

  • SpanText: Renderiza texto con un estilo específico.
  • SpanImage: Renderiza una imagen intercalada con texto.

Por ejemplo, puedes escribir "world" en cursiva en una tarjeta "Hello world" e insertar una imagen entre las palabras, como se muestra en el siguiente ejemplo de código:

Kotlin

private fun mySpannable(): LayoutElement =
    Spannable.Builder()
        .addSpan(SpanText.Builder()
            .setText("Hello ")
            .build()
        )
        .addSpan(SpanImage.Builder()
            .setWidth(dp(24f))
            .setHeight(dp(24f))
            .setResourceId("image_id")
            .build()
        )
        .addSpan(SpanText.Builder()
            .setText("world")
            .setFontStyle(FontStyle.Builder()
                .setItalic(true)
                .build())
            .build()
        ).build()

Java

private LayoutElement mySpannable() {
   return new Spannable.Builder()
        .addSpan(new SpanText.Builder()
            .setText("Hello ")
            .build()
        )
        .addSpan(new SpanImage.Builder()
            .setWidth(dp(24f))
            .setHeight(dp(24f))
            .setResourceId("image_id")
            .build()
        )
        .addSpan(new SpanText.Builder()
            .setText("world")
            .setFontStyle(newFontStyle.Builder()
                .setItalic(true)
                .build())
            .build()
        ).build();
}

Cómo trabajar con recursos

Las tarjetas no tienen acceso a ningún recurso de tu app, lo que significa que no puedes pasar el ID de una imagen de Android a un elemento de diseño Image y esperar que se resuelva. En cambio, anula el método onTileResourcesRequest() y proporciona los recursos de forma manual.

Existen dos maneras de brindar imágenes dentro del método onTileResourcesRequest():

Kotlin

override fun onTileResourcesRequest(
    requestParams: ResourcesRequest
) = Futures.immediateFuture(
Resources.Builder()
    .setVersion("1")
    .addIdToImageMapping("image_from_resource", ImageResource.Builder()
        .setAndroidResourceByResId(AndroidImageResourceByResId.Builder()
            .setResourceId(R.drawable.image_id)
            .build()
        ).build()
    )
    .addIdToImageMapping("image_inline", ImageResource.Builder()
        .setInlineResource(InlineImageResource.Builder()
            .setData(imageAsByteArray)
            .setWidthPx(48)
            .setHeightPx(48)
            .setFormat(ResourceBuilders.IMAGE_FORMAT_RGB_565)
            .build()
        ).build()
    ).build()
)

Java

@Override
protected ListenableFuture<Resources> onTileResourcesRequest(
       @NonNull ResourcesRequest requestParams
) {
return Futures.immediateFuture(
    new Resources.Builder()
        .setVersion("1")
        .addIdToImageMapping("image_from_resource", new ImageResource.Builder()
            .setAndroidResourceByResId(new AndroidImageResourceByResId.Builder()
                .setResourceId(R.drawable.image_id)
                .build()
            ).build()
        )
        .addIdToImageMapping("image_inline", new ImageResource.Builder()
            .setInlineResource(new InlineImageResource.Builder()
                .setData(imageAsByteArray)
                .setWidthPx(48)
                .setHeightPx(48)
                .setFormat(ResourceBuilders.IMAGE_FORMAT_RGB_565)
                .build()
            ).build()
        ).build()
);
}

Lista de tareas de la imagen de vista previa de la tarjeta

El sistema muestra la imagen de vista previa de la tarjeta, a la que se hace referencia en el manifiesto de la app para Android, en el editor de carrusel de tarjetas. Este editor aparece en dispositivos Wear OS y en la app complementaria del reloj en teléfonos.

Para ayudar a los usuarios a aprovechar al máximo esta imagen de vista previa, verifica los siguientes detalles sobre tu tarjeta:

  • Refleja el diseño más reciente. La vista previa debe representar con precisión el diseño más actual de tu tarjeta.
  • Usa un tema de color estático. Usa el tema de color estático de la tarjeta, no uno dinámico.
  • Incluye el ícono de la app. Confirma que el ícono de la app aparezca en la parte superior de la imagen de vista previa.
  • Muestra el estado de carga o acceso. La vista previa debe mostrar un estado "cargado" o "accedido" completamente funcional, sin contenido vacío ni marcador de posición.
  • Aprovecha las reglas de resolución de recursos para la personalización (opcional). Considera usar las reglas de resolución de recursos de Android para proporcionar vistas previas que coincidan con el tamaño de la pantalla, el idioma o la configuración regional del dispositivo. Esto es particularmente útil si el aspecto de la tarjeta varía según el dispositivo.