Plantillas de Slice

En este documento, se proporcionan detalles sobre cómo usar los compiladores de plantillas de Android Jetpack para crear Slices.

Cómo definir tu plantilla de Slices

Para crear Slices, se utiliza un ListBuilder. ListBuilder permite agregar diferentes tipos de filas que se muestran en una lista. En esta sección, se describe cada uno de los tipos de filas y cómo se crean.

SliceAction

El elemento más básico de una plantilla de Slices es una SliceAction. Una SliceAction contiene una etiqueta junto con un PendingIntent y puede ser una de las siguientes opciones:

  • Botón de ícono
  • Activación/desactivación predeterminada
  • Activación/desactivación personalizada (un elemento de diseño con estado activado/desactivado)

Los compiladores de plantillas que se describen en el resto de esta sección usan SliceAction. Una SliceAction puede tener un modo de imagen definido que determina cómo se presenta la imagen para la acción:

  • ICON_IMAGE: tamaño pequeño y con ajuste de tono de color
  • SMALL_IMAGE: tamaño pequeño y sin ajuste de tono de color.
  • LARGE_IMAGE: tamaño más grande y sin ajuste de tono de color

HeaderBuilder

En la mayoría de los casos, debes establecer un encabezado para tu plantilla utilizando un HeaderBuilder. Un encabezado puede admitir lo siguiente:

  • Título
  • Subtítulo
  • Subtítulo de resumen
  • Acción principal

A continuación, se muestran algunas configuraciones de encabezados de ejemplo. Ten en cuenta que los cuadros grises muestran posibles ubicaciones de íconos y padding:

Procesa encabezados en diferentes superficies

Cuando se necesita una Slice, la superficie de visualización determina cómo procesarla. Ten en cuenta que el procesamiento puede diferir entre las superficies de alojamiento.

En formatos más pequeños, por lo general, solo se muestra el encabezado (si existe). Si especificaste un resumen para el encabezado, se mostrará el texto de resumen en lugar del texto de subtítulos.

Si no especificaste un encabezado en tu plantilla, en general, en su lugar se mostrará la primera fila agregada al ListBuilder.

Ejemplo de HeaderBuilder: Slice de lista simple con encabezado

Kotlin

fun createSliceWithHeader(sliceUri: Uri) =
    list(context, sliceUri, ListBuilder.INFINITY) {
        setAccentColor(0xff0F9D) // Specify color for tinting icons
        header {
            title = "Get a ride"
            subtitle = "Ride in 4 min"
            summary = "Work in 1 hour 45 min | Home in 12 min"
        }
        row {
            title = "Home"
            subtitle = "12 miles | 12 min | $9.00"
            addEndItem(
                IconCompat.createWithResource(context, R.drawable.ic_home),
                ListBuilder.ICON_IMAGE
            )
        }
    }

Java

public Slice createSliceWithHeader(Uri sliceUri) {
    if (getContext() == null) {
        return null;
    }

    // Construct the parent.
    ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
            .setAccentColor(0xff0F9D58) // Specify color for tinting icons.
            .setHeader( // Create the header and add to slice.
                    new HeaderBuilder()
                            .setTitle("Get a ride")
                            .setSubtitle("Ride in 4 min.")
                            .setSummary("Work in 1 hour 45 min | Home in 12 min.")
            ).addRow(new RowBuilder() // Add a row.
                    .setPrimaryAction(
                            createActivityAction()) // A slice always needs a SliceAction.
                    .setTitle("Home")
                    .setSubtitle("12 miles | 12 min | $9.00")
                    .addEndItem(IconCompat.createWithResource(getContext(), R.drawable.ic_home),
                            SliceHints.ICON_IMAGE)
            ); // Add more rows if needed...
    return listBuilder.build();
}

SliceActions en los encabezados

Los encabezados de las Slices también pueden mostrar SliceActions:

Kotlin

fun createSliceWithActionInHeader(sliceUri: Uri): Slice {
    // Construct our slice actions.
    val noteAction = SliceAction.create(
        takeNoteIntent,
        IconCompat.createWithResource(context, R.drawable.ic_pencil),
        ICON_IMAGE,
        "Take note"
    )

    val voiceNoteAction = SliceAction.create(
        voiceNoteIntent,
        IconCompat.createWithResource(context, R.drawable.ic_mic),
        ICON_IMAGE,
        "Take voice note"
    )

    val cameraNoteAction = SliceAction.create(
        cameraNoteIntent,
        IconCompat.createWithResource(context, R.drawable.ic_camera),
        ICON_IMAGE,
        "Create photo note"
    )

    // Construct the list.
    return list(context, sliceUri, ListBuilder.INFINITY) {
        setAccentColor(0xfff4b4) // Specify color for tinting icons
        header {
            title = "Create new note"
            subtitle = "Easily done with this note taking app"
        }
        addAction(noteAction)
        addAction(voiceNoteAction)
        addAction(cameraNoteAction)
    }
}

Java

public Slice createSliceWithActionInHeader(Uri sliceUri) {
    if (getContext() == null) {
        return null;
    }
    // Construct our slice actions.
    SliceAction noteAction = SliceAction.create(takeNoteIntent,
            IconCompat.createWithResource(getContext(), R.drawable.ic_pencil),
            ListBuilder.ICON_IMAGE, "Take note");

    SliceAction voiceNoteAction = SliceAction.create(voiceNoteIntent,
            IconCompat.createWithResource(getContext(), R.drawable.ic_mic),
            ListBuilder.ICON_IMAGE,
            "Take voice note");

    SliceAction cameraNoteAction = SliceAction.create(cameraNoteIntent,
            IconCompat.createWithResource(getContext(), R.drawable.ic_camera),
            ListBuilder.ICON_IMAGE,
            "Create photo note");


    // Construct the list.
    ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
            .setAccentColor(0xfff4b400) // Specify color for tinting icons
            .setHeader(new HeaderBuilder() // Construct the header.
                    .setTitle("Create new note")
                    .setSubtitle("Easily done with this note taking app")
            )
            .addRow(new RowBuilder()
                    .setTitle("Enter app")
                    .setPrimaryAction(createActivityAction())
            )
            // Add the actions to the ListBuilder.
            .addAction(noteAction)
            .addAction(voiceNoteAction)
            .addAction(cameraNoteAction);
    return listBuilder.build();
}

RowBuilder

Puedes construir una fila de contenido usando un RowBuilder. Una fila puede admitir cualquiera de las siguientes opciones:

  • Título
  • Subtítulo
  • Elemento de inicio: SliceAction, ícono o una marca de tiempo
  • Elementos de finalización: SliceAction, ícono o una marca de tiempo
  • Acción principal

Puedes combinar el contenido de las filas de varias formas, con las siguientes restricciones:

  • Los elementos de inicio no se mostrarán en la primera fila de una Slice
  • Los elementos de finalización no pueden ser una combinación de objetos SliceAction y Icon.
  • Una fila puede contener solo una marca de tiempo.

En las siguientes imágenes, se muestran filas de contenido de ejemplo. Ten en cuenta que los cuadros grises muestran posibles ubicaciones de íconos y padding:

Ejemplo de RowBuilder: activación/desactivación de Wi-Fi

En el siguiente ejemplo, se muestra una fila con una acción principal y una activación/desactivación predeterminada.

Kotlin

fun createActionWithActionInRow(sliceUri: Uri): Slice {
    // Primary action - open wifi settings.
    val wifiAction = SliceAction.create(
        wifiSettingsPendingIntent,
        IconCompat.createWithResource(context, R.drawable.ic_wifi),
        ICON_IMAGE,
        "Wi-Fi Settings"
    )

    // Toggle action - toggle wifi.
    val toggleAction = SliceAction.createToggle(
        wifiTogglePendingIntent,
        "Toggle Wi-Fi",
        isConnected /* isChecked */
    )

    // Create the parent builder.
    return list(context, wifiUri, ListBuilder.INFINITY) {
        setAccentColor(0xff4285) // Specify color for tinting icons / controls.
        row {
            title = "Wi-Fi"
            primaryAction = wifiAction
            addEndItem(toggleAction)
        }
    }
}

Java

public Slice createActionWithActionInRow(Uri sliceUri) {
    if (getContext() == null) {
        return null;
    }
    // Primary action - open wifi settings.
    SliceAction primaryAction = SliceAction.create(wifiSettingsPendingIntent,
            IconCompat.createWithResource(getContext(), R.drawable.ic_wifi),
            ListBuilder.ICON_IMAGE,
            "Wi-Fi Settings"
    );

    // Toggle action - toggle wifi.
    SliceAction toggleAction = SliceAction.createToggle(wifiTogglePendingIntent,
            "Toggle Wi-Fi", isConnected /* isChecked */);

    // Create the parent builder.
    ListBuilder listBuilder = new ListBuilder(getContext(), wifiUri, ListBuilder.INFINITY)
            // Specify color for tinting icons / controls.
            .setAccentColor(0xff4285f4)
            // Create and add a row.
            .addRow(new RowBuilder()
                    .setTitle("Wi-Fi")
                    .setPrimaryAction(primaryAction)
                    .addEndItem(toggleAction));
    // Build the slice.
    return listBuilder.build();
}

GridBuilder

Puedes construir una cuadrícula de contenido utilizando un GridBuilder. Una cuadrícula puede admitir los siguientes tipos de imágenes:

  • ICON_IMAGE: tamaño pequeño y con ajuste de tono de color
  • SMALL_IMAGE: tamaño pequeño y sin ajuste de tono de color.
  • LARGE_IMAGE: tamaño más grande y sin ajuste de tono de color

Una celda de cuadrícula se construye utilizando un CellBuilder. Una celda puede admitir hasta dos líneas de texto y una imagen. Una celda no puede estar vacía.

Los ejemplos de cuadrícula se muestran en las siguientes imágenes:

Ejemplo de GridRowBuilder: restaurantes cercanos

En el siguiente ejemplo, se muestra una fila de cuadrícula que contiene imágenes y texto.

Kotlin

fun createSliceWithGridRow(sliceUri: Uri): Slice {
    // Create the parent builder.
    return list(context, sliceUri, ListBuilder.INFINITY) {
        header {
            title = "Famous restaurants"
            primaryAction = SliceAction.create(
                pendingIntent, icon, ListBuilder.ICON_IMAGE, "Famous restaurants"
            )
        }
        gridRow {
            cell {
                addImage(image1, LARGE_IMAGE)
                addTitleText("Top Restaurant")
                addText("0.3 mil")
                contentIntent = intent1
            }
            cell {
                addImage(image2, LARGE_IMAGE)
                addTitleText("Fast and Casual")
                addText("0.5 mil")
                contentIntent = intent2
            }
            cell {
                addImage(image3, LARGE_IMAGE)
                addTitleText("Casual Diner")
                addText("0.9 mi")
                contentIntent = intent3
            }
            cell {
                addImage(image4, LARGE_IMAGE)
                addTitleText("Ramen Spot")
                addText("1.2 mi")
                contentIntent = intent4
            }
        }
    }
}

Java

public Slice createSliceWithGridRow(Uri sliceUri) {
      if (getContext() == null) {
          return null;
      }
      // Create the parent builder.
      ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
              .setHeader(
                      // Create the header.
                      new HeaderBuilder()
                              .setTitle("Famous restaurants")
                              .setPrimaryAction(SliceAction
                                      .create(pendingIntent, icon, ListBuilder.ICON_IMAGE,
                                              "Famous restaurants"))
              )
              // Add a grid row to the list.
              .addGridRow(new GridRowBuilder()
                      // Add cells to the grid row.
                      .addCell(new CellBuilder()
                              .addImage(image1, ListBuilder.LARGE_IMAGE)
                              .addTitleText("Top Restaurant")
                              .addText("0.3 mil")
                              .setContentIntent(intent1)
                      ).addCell(new CellBuilder()
                              .addImage(image2, ListBuilder.LARGE_IMAGE)
                              .addTitleText("Fast and Casual")
                              .addText("0.5 mil")
                              .setContentIntent(intent2)
                      )
                      .addCell(new CellBuilder()
                              .addImage(image3, ListBuilder.LARGE_IMAGE)
                              .addTitleText("Casual Diner")
                              .addText("0.9 mi")
                              .setContentIntent(intent3))
                      .addCell(new CellBuilder()
                              .addImage(image4, ListBuilder.LARGE_IMAGE)
                              .addTitleText("Ramen Spot")
                              .addText("1.2 mi")
                              .setContentIntent(intent4))
                      // Every slice needs a primary action.
                      .setPrimaryAction(createActivityAction())
              );
      return listBuilder.build();
  }

RangeBuilder

Con RangeBuilder, puedes crear una fila que contenga una barra de progreso o un rango de entrada, como un control deslizante.

En las siguientes imágenes, se muestran ejemplos de la barra progreso y control deslizante:

Ejemplo de RangeBuilder: control deslizante

En el siguiente ejemplo, se muestra cómo compilar una Slice que contenga un control deslizante de volumen con un InputRangeBuilder. Para construir una fila de progreso, usa addRange().

Kotlin

fun createSliceWithRange(sliceUri: Uri): Slice {
    return list(context, sliceUri, ListBuilder.INFINITY) {
        inputRange {
            title = "Ring Volume"
            inputAction = volumeChangedPendingIntent
            max = 100
            value = 30
        }
    }
}

Java

public Slice createSliceWithRange(Uri sliceUri) {
    if (getContext() == null) {
        return null;
    }
    // Construct the parent.
    ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
            .addRow(new RowBuilder() // Every slice needs a row.
                    .setTitle("Enter app")
                      // Every slice needs a primary action.
                    .setPrimaryAction(createActivityAction())
            )
            .addInputRange(new InputRangeBuilder() // Create the input row.
                    .setTitle("Ring Volume")
                    .setInputAction(volumeChangedPendingIntent)
                    .setMax(100)
                    .setValue(30)
            );
    return listBuilder.build();
}

Contenido retrasado

Deberás mostrar una Slice lo más rápido posible desde SliceProvider.onBindSlice(). Las llamadas que llevan mucho tiempo pueden provocar problemas de visualización, como parpadeos y cambios de tamaño abruptos.

Si tienes contenido de una Slice que no se puede cargar rápidamente, puedes crear tu Slice con contenido de marcador de posición y tener en cuenta en el compilador que el contenido se está cargando. Una vez que el contenido esté listo para mostrarse, llama a getContentResolver().notifyChange(sliceUri, null) con tu URI de Slice. Esta acción dará como resultado otra llamada a SliceProvider.onBindSlice(), donde podrás volver a construir la Slice con contenido nuevo.

Ejemplo de contenido retrasado: trayecto al trabajo

En la fila Trayecto al trabajo que aparece a continuación, la distancia al trabajo se determina dinámicamente y es posible que no esté disponible de inmediato. En el código de ejemplo, se demuestra el uso de un subtítulo nulo como marcador de posición mientras se carga el contenido:

Kotlin

fun createSliceShowingLoading(sliceUri: Uri): Slice {
    // We’re waiting to load the time to work so indicate that on the slice by
    // setting the subtitle with the overloaded method and indicate true.
    return list(context, sliceUri, ListBuilder.INFINITY) {
        row {
            title = "Ride to work"
            setSubtitle(null, true)
            addEndItem(IconCompat.createWithResource(context, R.drawable.ic_work), ICON_IMAGE)
        }
    }
}

Java

public Slice createSliceShowingLoading(Uri sliceUri) {
    if (getContext() == null) {
        return null;
    }
    // Construct the parent.
    ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
            // Construct the row.
            .addRow(new RowBuilder()
                    .setPrimaryAction(createActivityAction())
                    .setTitle("Ride to work")
                    // We’re waiting to load the time to work so indicate that on the slice by
                    // setting the subtitle with the overloaded method and indicate true.
                    .setSubtitle(null, true)
                    .addEndItem(IconCompat.createWithResource(getContext(), R.drawable.ic_work),
                            ListBuilder.ICON_IMAGE)
            );
    return listBuilder.build();
}

private SliceAction createActivityAction() {
    return SliceAction.create(
            PendingIntent.getActivity(
                    getContext(),
                    0,
                    new Intent(getContext(), MainActivity.class),
                    0
            ),
            IconCompat.createWithResource(getContext(), R.drawable.ic_home),
            ListBuilder.ICON_IMAGE,
            "Enter app"
    );
}

Controla el desplazamiento inhabilitado dentro de tu Slice

Es posible que la superficie que presenta la plantilla de Slices no sea compatible con el desplazamiento dentro de la plantilla. En este caso, es posible que no se muestre parte de tu contenido.

A modo de ejemplo, considera una Slice que muestre una lista de redes Wi-Fi:

Si la lista de Wi-Fi es larga y el desplazamiento está inhabilitado, puedes agregar el botón Ver más para asegurarte de que los usuarios tengan una manera de ver todos los elementos de la lista. Puedes agregar este botón utilizando addSeeMoreAction(), como se muestra en el siguiente ejemplo:

Kotlin

fun seeMoreActionSlice(sliceUri: Uri) =
    list(context, sliceUri, ListBuilder.INFINITY) {
        // [START_EXCLUDE]
        // [END_EXCLUDE]
        setSeeMoreAction(seeAllNetworksPendingIntent)
        // [START_EXCLUDE]
        // [END_EXCLUDE]
    }

Java

public Slice seeMoreActionSlice(Uri sliceUri) {
    if (getContext() == null) {
        return null;
    }
    ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);
    // [START_EXCLUDE]
    listBuilder.addRow(new RowBuilder()
            .setTitle("Hello")
            .setPrimaryAction(createActivityAction())
    );
    // [END_EXCLUDE]
    listBuilder.setSeeMoreAction(seeAllNetworksPendingIntent);
    // [START_EXCLUDE]
    // [END_EXCLUDE]
    return listBuilder.build();
}

Se muestra como en la siguiente imagen:

Cuando presionas Ver más, se envía seeAllNetworksPendingIntent.

Como alternativa, si quieres proporcionar un mensaje o una fila personalizados, considera agregar una instancia de RowBuilder:

Kotlin

fun seeMoreRowSlice(sliceUri: Uri) =
    list(context, sliceUri, ListBuilder.INFINITY) {
        // [START_EXCLUDE]
        // [END_EXCLUDE]
        seeMoreRow {
            title = "See all available networks"
            addEndItem(
                IconCompat.createWithResource(context, R.drawable.ic_right_caret), ICON_IMAGE
            )
            primaryAction = SliceAction.create(
                seeAllNetworksPendingIntent,
                IconCompat.createWithResource(context, R.drawable.ic_wifi),
                ListBuilder.ICON_IMAGE,
                "Wi-Fi Networks"
            )
        }
    }

Java

public Slice seeMoreRowSlice(Uri sliceUri) {
    if (getContext() == null) {
        return null;
    }
    ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY)
            // [START_EXCLUDE]
            .addRow(new RowBuilder()
                    .setTitle("Hello")
                    .setPrimaryAction(createActivityAction())
            )
            // [END_EXCLUDE]
            .setSeeMoreRow(new RowBuilder()
                    .setTitle("See all available networks")
                    .addEndItem(IconCompat
                                    .createWithResource(getContext(), R.drawable
                                            .ic_right_caret),
                            ListBuilder.ICON_IMAGE)
                    .setPrimaryAction(SliceAction.create(seeAllNetworksPendingIntent,
                            IconCompat.createWithResource(getContext(), R.drawable.ic_wifi),
                            ListBuilder.ICON_IMAGE,
                            "Wi-Fi Networks"))
            );
    // [START_EXCLUDE]
    // [END_EXCLUDE]
    return listBuilder.build();
}

La fila o la acción agregadas a través de este método se mostrarán solo cuando se cumpla una de las siguientes condiciones:

  • El presentador de tu Slice inhabilitó el desplazamiento en la vista.
  • No todas las filas se pueden mostrar en el espacio disponible.

Cómo combinar plantillas

Puedes crear una Slice enriquecida y dinámica combinando varios tipos de filas. Por ejemplo, una Slice puede contener una fila de encabezado, una cuadrícula con una sola imagen y una cuadrícula con dos celdas de texto.

Esta es una Slice con una fila de encabezado junto con una cuadrícula que contiene tres celdas.