Tarjetas

Organiza tus páginas con colecciones Guarda y categoriza el contenido según tus preferencias.

Las tarjetas brindan un fácil acceso a la información y a las acciones que los usuarios necesitan para realizar sus tareas. Con solo deslizar el dedo sobre la cara de reloj, un usuario puede consultar el pronóstico actualizado o iniciar un temporizador.

se deslizan tarjetas desde el reloj hasta el clima y el temporizador.

Los usuarios pueden elegir las tarjetas que quieren ver. Hay tarjetas para consultar el clima, establecer un temporizador, hacer un seguimiento del progreso diario del entrenamiento, iniciar rápidamente un entrenamiento, reproducir una canción, ver reuniones próximas y enviar un mensaje a un contacto favorito.

Ejemplos de tarjetas ubicadas una al lado de la otra.

La API de Tiles le permite a los desarrolladores compilar tarjetas personalizadas que los usuarios pueden incluir en sus relojes. Para usar la API de Tiles, se requiere una API de nivel 26 o superior.

Para obtener más información, consulta la entrada de blog sobre tarjetas.

Prácticas recomendadas

Para trabajar con tarjetas, se requieren algunas consideraciones específicas:

  • Usa componentes de Material y diseños de Material en lugar de componentes más básicos para garantizar el cumplimiento de las recomendaciones de la interfaz de usuario de Material Design y la coherencia general.
  • Mientras que el SO se encarga de renderizar la IU de la tarjeta, tú proporcionas el diseño, la información y los recursos con TileService.
  • Las tarjetas están pensadas para brindar información fácil de ver que los usuarios pueden leer en cuestión de segundos. Muestra solo el contenido más importante con una jerarquía de información clara.
  • Para proteger la batería del usuario, evita usar elementos que requieran volver a renderizarlos con frecuencia.
  • Guarda experiencias altamente interactivas para tus actividades. Sin embargo, puedes vincular esas actividades desde tu tarjeta.
  • Evita texto como "hace x minutos" o "en x minutos" para eventos pasados o futuros, ya que esto requiere actualizaciones frecuentes. En su lugar, muestra la hora de inicio o finalización real, o bien usa una declaración como "en el pasado".
  • Evita el trabajo asíncrono de larga duración cuando proporciones el diseño o los recursos de una tarjeta. Tu código de tarjeta debería ejecutarse con rapidez.
  • Considera permitir que el usuario presione las tarjetas a fin de obtener más información y tomar medidas en una superposición, donde haya compatibilidad con la interactividad completa y el usuario pueda desplazarse para obtener más información.
  • Si tienes una app grande que admite que el usuario realice varias tareas, considera la idea de crear una tarjeta para cada tarea. Por ejemplo, una app de fitness puede tener una tarjeta de Objetivos y una de Actividad de entrenamiento.

Primeros pasos

Configuración

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

Groovy

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

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

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

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

Kotlin

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

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

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

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

Nota: Esta API es asíncrona y depende de ListenableFuture en gran medida. Consulta Cómo usar un objeto ListenableFuture para obtener más información sobre este concepto.

Cómo crear una tarjeta

Para proporcionar una tarjeta desde tu aplicación, crea una clase que extienda TileService e implementa los métodos, como se muestra en la siguiente muestra de código:

Kotlin

private val RESOURCES_VERSION = "1"
class MyTileService : TileService() {
    override fun onTileRequest(requestParams: RequestBuilders.TileRequest) =
        Futures.immediateFuture(Tile.Builder()
            .setResourcesVersion(RESOURCES_VERSION)
            .setTimeline(Timeline.Builder().addTimelineEntry(
                TimelineEntry.Builder().setLayout(
                    Layout.Builder().setRoot(
                        Text.Builder().setText("Hello world!").setFontStyle(
                            FontStyle.Builder().setColor(argb(0xFF000000)).build()
                        ).build()
                    ).build()
                ).build()
            ).build()
        ).build())

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

Java

public class MyTileService extends TileService {
    private static final String RESOURCES_VERSION = "1";

    @NonNull
    @Override
    protected ListenableFuture<Tile> onTileRequest(
        @NonNull TileRequest requestParams
    ) {
        return Futures.immediateFuture(new Tile.Builder()
            .setResourcesVersion(RESOURCES_VERSION)
            .setTimeline(new Timeline.Builder()
                .addTimelineEntry(new TimelineEntry.Builder()
                    .setLayout(new Layout.Builder()
                        .setRoot(new Text.Builder()
                            .setText("Hello world!")
                            .setFontStyle(new FontStyle.Builder()
                                .setColor(argb(0xFF000000)).build()
                            ).build()
                        ).build()
                    ).build()
                ).build()
            ).build()
        );
   }

   @NonNull
   @Override
   protected ListenableFuture<Resources> onResourcesRequest(
       @NonNull ResourcesRequest requestParams
   ) {
       return Futures.immediateFuture(new Resources.Builder()
               .setVersion(RESOURCES_VERSION)
               .build()
       );
   }
}

Luego, agrega un servicio dentro de la etiqueta <application> de tu AndroidManifest.xml.

<service
   android:name=".MyTileService"
   android:label="@string/tile_label"
   android:description="@string/tile_description"
   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 y la descripción se muestran al usuario cuando este configura tarjetas en su teléfono o reloj.

Usa la etiqueta de metadatos de vista previa para mostrar una vista previa de la tarjeta cuando se configura en el teléfono.

Nota: En la mayoría de los casos, debes proporcionar recursos para las pantallas redondas (drawable-round-nodpi) y cuadradas (drawable-notround-nodpi).

Cómo obtener una vista previa de la tarjeta en tu app

La biblioteca de wear-tiles-renderer proporciona una forma de obtener una vista previa de las tarjetas en una actividad dentro de tu app.

A fin de obtener una vista previa de tu tarjeta, crea una actividad que use la biblioteca del procesador para renderizarla. Agrega esta actividad en src/debug en lugar de src/main, ya que la usarás solo con fines de depuración. Consulta el siguiente código para ver un ejemplo:

Kotlin

class MainActivity : ComponentActivity() {
    private lateinit var binding: ActivityMainBinding
    private lateinit var tileUiClient: TileUiClient

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        tileUiClient = TileUiClient(
            context = this,
            component = ComponentName(this, MyTileService::class.java),
            parentView = binding.tileContainer
        )
        tileUiClient.connect()
    }

    override fun onDestroy() {
        super.onDestroy()
        tileUiClient.close()
    }
}

Java

public class MainActivity extends ComponentActivity {
    private ActivityMainBinding binding;
    private TileUiClient mTileUiClient;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        mTileUiClient = new TileUiClient(
                this,
                new ComponentName(this, MyTileService.class),
                binding.tileContainer
        );
        mTileUiClient.connect();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mTileUiClient.close();
    }
}

Crea un archivo de diseño en activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:id="@+id/tile_container"
   android:layout_width="match_parent"
   android:layout_height="match_parent" />

Cómo trabajar con cronogramas

Un cronograma consta de una o más instancias de TimelineEntry, cada una de las cuales contiene un diseño que se muestra durante un intervalo específico. Todas las tarjetas necesitan un cronograma.

Diagrama del cronograma de tarjetas

Tarjetas de una sola entrada

Con frecuencia, una tarjeta se puede describir con un solo elemento TimelineEntry. El diseño es fijo y solo cambia la información dentro del diseño. Por ejemplo, una tarjeta que muestra tu progreso de entrenamiento del día siempre tendrá el mismo diseño de progreso, aunque puedes ajustar ese diseño para que muestre valores diferentes. En estos casos, no sabes de antemano el momento en que puede cambiar el contenido.

Consulta el siguiente ejemplo de una tarjeta con un solo elemento TimelineEntry:

Kotlin

override fun onTileRequest(
    requestParams: TileRequest
): ListenableFuture<Tile> {
    val tile = Tile.Builder()
        .setResourcesVersion(RESOURCES_VERSION)
        .setTimeline(Timeline.Builder()
            // We add a single timeline entry when our layout is fixed, and
            // we don't know in advance when its contents might change.
            .addTimelineEntry(TimelineEntry.Builder()
                // .setLayout(...).build()
            ).build()
        ).build()
    return Futures.immediateFuture(tile)
}

Java

@Override
protected ListenableFuture<Tile> onTileRequest(
       @NonNull TileRequest requestParams
) {
   Tile Tile = new Tile.Builder()
       .setResourcesVersion(RESOURCES_VERSION)
       .setTimeline(
           new Timeline.Builder()
               // We add a single timeline entry when our layout is fixed, and
               // we don't know in advance when its contents might change.
               .addTimelineEntry(new TimelineEntry.Builder()
                   // .setLayout(...).build()
               ).build()
       ).build();
   return Futures.immediateFuture(tile);
}

Entradas del cronograma con un plazo

De manera opcional, un objeto TimelineEntry puede definir un período de validez, lo que permite que una tarjeta cambie su diseño en un momento conocido sin necesidad de que la app envíe una nueva tarjeta.

El ejemplo canónico es una tarjeta de agenda cuyo cronograma contiene una lista de eventos futuros. Cada evento futuro contiene un período de validez para indicar cuándo mostrarlo.

La API de Tiles permite superponer períodos de validez, en los que se muestra la pantalla con el período más corto restante. Solo se muestra un evento a la vez.

Los desarrolladores pueden proporcionar una entrada de resguardo predeterminada. Por ejemplo, la tarjeta de agenda podría tener una tarjeta con un período de validez infinito, que se utiliza si ninguna otra entrada de cronograma es válida, como se muestra en el siguiente ejemplo de código:

Kotlin

public override fun onTileRequest(
    requestParams: TileRequest
): ListenableFuture<Tile> {
    val timeline = Timeline.builder()

    // Add fallback "no meetings" entry
    timeline.addTimelineEntry(TimelineEntry.Builder().setLayout(getNoMeetingsLayout()).build())

    // Retrieve a list of scheduled meetings
    val meetings = MeetingsRepo.getMeetings()
    // Add a timeline entry for each meeting
    meetings.forEach { meeting ->
        timeline.addTimelineEntry(TimelineEntry.Builder()
            .setLayout(getMeetingLayout(meeting))
            .setValidity(
                // The Tile should disappear when the meeting begins
                TimeInterval.Builder()
                    .setEndMillis(meeting.dateTimeMillis).build()
            ).build()
        )
    }

    val tile = Tile.Builder()
        .setResourcesVersion(RESOURCES_VERSION)
        .setTimeline(timeline.build())
        .build()
    return Futures.immediateFuture(tile)
}

Java

@Override
protected ListenableFuture<Tile> onTileRequest(
       @NonNull TileRequest requestParams
) {
   Timeline.Builder timeline = new Timeline.Builder();
   // Add fallback "no meetings" entry
   timeline.addTimelineEntry(new TimelineEntry.Builder().setLayout(getNoMeetingsLayout()).build());
   // Retrieve a list of scheduled meetings
   List<Meeting> meetings = MeetingsRepo.getMeetings();
   // Add a timeline entry for each meeting
   for(Meeting meeting : meetings) {
        timeline.addTimelineEntry(new TimelineEntry.Builder()
            .setLayout(getMeetingLayout(meeting))
            .setValidity(
                // The Tile should disappear when the meeting begins
                new TimeInterval.builder()
                    .setEndMillis(meeting.getDateTimeMillis()).build()
            ).build()
        );
    }

    Tile Tile = new Tile.Builder()
        .setResourcesVersion(RESOURCES_VERSION)
        .setTimeline(timeline.build())
        .build();
    return Futures.immediateFuture(tile);
}

Cómo actualizar una tarjeta

La información que se muestra en una tarjeta puede vencer después de un tiempo. Por ejemplo, una tarjeta del clima que muestra la misma temperatura durante el día no es precisa.

Para lidiar con los datos que vencen, establece un intervalo de actualización cuando creas una tarjeta que especifique el tiempo de validez de la tarjeta. En el ejemplo de la tarjeta de clima, es posible que actualicemos su contenido una vez por hora, como se muestra en la siguiente muestra de código:

Kotlin

override fun onTileRequest(requestParams: RequestBuilders.TileRequest) =
    Futures.immediateFuture(Tile.Builder()
        .setResourcesVersion(RESOURCES_VERSION)
        .setFreshnessIntervalMillis(60 * 60 * 1000) // 60 minutes
        .setTimeline(Timeline.Builder()
            .addTimelineEntry(TimelineEntry.Builder()
                .setLayout(getWeatherLayout())
                .build()
            ).build()
        ).build()
    )

Java

@Override
protected ListenableFuture<Tile> onTileRequest(
       @NonNull TileRequest requestParams
) {
    return Futures.immediateFuture(new Tile.Builder()
        .setResourcesVersion(RESOURCES_VERSION)
        .setFreshnessIntervalMillis(60 * 60 * 1000) // 60 minutes
        .setTimeline(new Timeline.Builder()
            .addTimelineEntry(new TimelineEntry.Builder()
                .setLayout(getWeatherLayout())
                .build()
            ).build()
        ).build());
}

Cuando configuras un intervalo de actualización, el sistema llama a onTileRequest() poco después de que finalice el intervalo. Si no estableces un intervalo de actualización, el sistema no llamará a onTileRequest().

Una tarjeta también puede vencer por un evento externo. Por ejemplo, un usuario podría quitar una reunión de su calendario y, si no se actualizara la tarjeta, aún mostraría esa reunión borrada. En este caso, solicita una actualización desde cualquier lugar del código de la aplicación, como se muestra en la siguiente muestra de código:

Kotlin

fun eventDeletedCallback() {
     TileService.getUpdater(context)
             .requestUpdate(MyTileService::class.java)
}

Java

public void eventDeletedCallback() {
   TileService.getUpdater(context)
           .requestUpdate(MyTileService.class);
}

Diseños

El diseño de una tarjeta se escribe con un patrón de compilador. Este diseño se compila como un árbol que consta de contenedores y elementos de diseño básicos. Cada elemento de diseño tiene propiedades, que puedes configurar con varios métodos set.

Elementos de diseño básicos

Se admiten los siguientes elementos visuales:

  • Text: Renderiza una string de texto (opcionalmente con una unión).
  • Image: Renderiza una imagen.
  • Spacer: Proporciona padding entre los elementos o puede actuar como divisor cuando configuras el color de fondo.

Componentes de Material

Además de los elementos básicos, la biblioteca de tiles-material proporciona componentes que garantizan un diseño de tarjeta, de acuerdo con las recomendaciones de la interfaz de usuario de Material Design.

  • Button: Es un componente circular en el que se puede hacer clic y que se diseñó para contener un ícono.
  • Chip: Es un componente con forma de estadio en el que se puede hacer clic y que se diseñó para contener hasta dos líneas de texto y un ícono opcional.
  • CompactChip: Es un componente con forma de estadio en el que se puede hacer clic y que se diseñó para contener una línea de texto.
  • TitleChip: Es un componente con forma de estadio en el que se puede hacer clic, similar a Chip, pero con una altura mayor para adaptarse al texto del título.
  • CircularProgressIndicator: Es un indicador de progreso circular que se puede colocar dentro de un ProgressIndicatorLayout para mostrar el progreso en los bordes de la pantalla.

Contenedores de diseño

Se admiten los siguientes contenedores:

  • Row: Ubica los elementos secundarios de forma horizontal, uno tras otro.
  • Column: Ubica los elementos secundarios de forma vertical, uno tras otro.
  • Box: Superpone los elementos secundarios uno sobre otro.
  • Arc: Ubica los elementos secundarios en un círculo.
  • Spannable: Aplica el elemento FontStyles específico a secciones de texto junto con imágenes y texto intercalados. Para obtener más información, consulta Spannables.

Cada contenedor puede incluir uno o más elementos secundarios, que también pueden ser contenedores. Por ejemplo, un Column puede contener varios elementos Row como elementos secundarios, lo que genera un diseño similar a una cuadrícula.

A modo de ejemplo, una tarjeta con un diseño de contenedor y dos elementos de diseño secundarios podría verse de la siguiente manera:

Kotlin

private fun myLayout(): LayoutElement =
    Row.Builder()
        .setWidth(wrap())
        .setHeight(expand())
        .setVerticalAlignment(VALIGN_BOTTOM)
        .addContent(Text.Builder()
            .setText("Hello world")
            .build()
        )
        .addContent(Image.Builder()
            .setResourceId("image_id")
            .setWidth(dp(24f))
            .setHeight(dp(24f))
            .build()
        ).build()

Java

private LayoutElement myLayout() {
    return new Row.Builder()
        .setWidth(wrap())
        .setHeight(expand())
        .setVerticalAlignment(VALIGN_BOTTOM)
        .addContent(new Text.Builder()
            .setText("Hello world")
            .build()
        )
        .addContent(new Image.Builder()
            .setResourceId("image_id")
            .setWidth(dp(24f))
            .setHeight(dp(24f))
            .build()
        ).build();
}

Diseños de Material

Además de los diseños básicos, la biblioteca de tiles-material proporciona algunos diseños bien definidos para contener elementos dentro de "espacios" específicos.

  • PrimaryLayout: Ubica una sola acción principal de CompactChip en la parte inferior con el contenido centrado sobre esta.
  • MultiSlotLayout: Ubica las etiquetas primarias y secundarias con contenido opcional intermedio y un elemento CompactChip opcional en la parte inferior.
  • ProgressIndicatorLayout: Ubica un objeto CircularProgressIndicator alrededor de los bordes de la pantalla y el contenido determinado dentro.

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.

Nota: Mientras que ArcText dibuja un texto curvo alrededor del arco, el uso de Text en ArcAdapter dibuja un texto lineal 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.
  • Agregar funcionalidad. Por ejemplo, agrega un modificador en el que se pueda hacer clic a tu elemento de diseño para que tu tarjeta sea interactiva. Para obtener más información, consulta Cómo interactuar con la tarjeta.

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 substring 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 la siguiente muestra 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 onResourcesRequest() y proporciona los recursos de forma manual.

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

Kotlin

override fun onResourcesRequest(
    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> onResourcesRequest(
       @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()
);
}

Cómo interactuar con la tarjeta

Si agregas el modificador Clickable a un elemento de diseño, podrás reaccionar cuando un usuario presione ese elemento de diseño. Como reacción a un evento de clic, puedes realizar dos acciones:

  • LaunchAction: Inicia una actividad que se declare explícitamente como android:exported="true" en AndroidManifest.xml.
  • LoadAction: Se fuerza la actualización de la tarjeta, llamando a onTileRequest().

Para configurar un elemento LaunchAction, pasa el nombre de clase y el nombre de paquete de la actividad que quieras iniciar cuando el usuario presione el elemento, como se observa en la siguiente muestra de código:

Kotlin

private fun tappableElement(): LayoutElement =
    Text.Builder()
        .setText("Tap me!")
        .setModifiers(Modifiers.Builder()
            .setClickable(Clickable.Builder()
                .setId("foo")
                .setOnClick(LaunchAction.Builder()
                    .setAndroidActivity(AndroidActivity.Builder()
                        .setClassName(MyActivity::class.java.getName())
                        .setPackageName(this.packageName)
                        .build()
                    ).build()
                ).build()
            ).build()
        ).build()

Java

private LayoutElement tappableElement() {
    return new Text.Builder()
        .setText("Tap me!")
        .setModifiers(new Modifiers.Builder()
            .setClickable(new Clickable.Builder()
                .setId("foo")
                .setOnClick(new LaunchAction.Builder()
                    .setAndroidActivity(new AndroidActivity.Builder()
                        .setClassName(MyActivity.class.getName())
                        .setPackageName(this.getPackageName())
                        .build()
                    ).build()
                ).build()
            ).build()
        ).build();
}

Dentro de la actividad que se inició, puedes recuperar el ID que se usó para la tarjeta:

Kotlin

class MyActivity : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val clickableId =
            intent.getStringExtra(TileService.EXTRA_CLICKABLE_ID)
        // clickableId will be "foo" when launched from the Tile
    }
}

Java

public class MyActivity extends FragmentActivity {
   @Override
   public void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       String clickableId =
           getIntent().getStringExtra(TileService.EXTRA_CLICKABLE_ID);
       // clickableId will be "foo" when launched from the Tile
   }
}

Nota: Puedes iniciar una actividad desde tu propia aplicación o desde otra, siempre que se declare android:exported="true" en el manifiesto de tu app.

Como alternativa, usa LoadAction para actualizar la tarjeta cuando el usuario presiona el elemento de diseño, como se muestra en la siguiente muestra de código:

Kotlin

private fun tappableElement(): LayoutElement =
    Text.Builder()
        .setText("Tap me!")
        .setModifiers(Modifiers.Builder()
            .setClickable(Clickable.Builder()
                .setId("foo")
                .setOnClick(LoadAction.Builder().build())
                .build()
            ).build()
        ).build()

Java

private LayoutElement tappableElement() {
    return new Text.Builder()
        .setText("Tap me!")
        .setModifiers(new Modifiers.Builder()
            .setClickable(new Clickable.Builder()
                .setId("foo")
                .setOnClick(new LoadAction.Builder().build())
                .build()
            ).build()
        ).build()
}

En este caso, el ID en el que se puede hacer clic configurado en setId() se pasa a la llamada onTileRequest(), por lo que puedes renderizar un diseño diferente según este ID, como se muestra en la siguiente muestra de código:

Kotlin

override fun onTileRequest(requestParams: TileRequest) = Futures.immediateFuture(
    Tile.Builder()
        .setResourcesVersion("1")
        .setTimeline(Timeline.Builder()
            .addTimelineEntry(TimelineEntry.Builder()
                .setLayout(Layout.Builder()
                    .setRoot(
                        when(requestParams.state.lastClickableId) {
                            "foo" -> myFooLayout()
                            else -> myOtherLayout()
                        }
                    ).build()
                ).build()
            ).build()
        ).build()
)

Java

@NonNull
@Override
protected ListenableFuture<Tile> onTileRequest(
   @NonNull TileRequest requestParams
) {
    LayoutElement root;
    if(requestParams.getState().getLastClickableId().equals("foo")) {
        root = myFooLayout();
    } else {
        root = myOtherLayout();
    }
    return Futures.immediateFuture(new Tile.Builder()
        .setResourcesVersion("1")
        .setTimeline(new Timeline.Builder()
            .addTimelineEntry(TimelineEntry.Builder()
                .setLayout(Layout.Builder()
                    .setRoot(root)
                    .build()
                ).build()
            ).build()
        ).build());
}