Plantillas de secciones

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

Cómo definir tu plantilla de sección

Las plantillas se construyen utilizando 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 construyen.

SliceAction

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

  • Botón de icono
  • 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 relleno:

Procesa encabezados en diferentes superficies

Cuando se necesita una sección, la superficie de visualización determina cómo procesar la sección. 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 muestra 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 muestra la primera fila agregada al ListBuilder.

Ejemplo de HeaderBuilder: sección 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 secciones 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 sección
  • 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 relleno:

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 sección 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 sección 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 tiene contenido de secciones que no se puede cargar rápidamente, puedes construir tu partición con null o contenido de marcador de posición mientras observas 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 sección. Esto dará como resultado otra llamada a SliceProvider#onBindSlice, donde podrás volver a construir la sección 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"
        );
    }
    

Cómo controlar el desplazamiento inhabilitado dentro de tu sección

Es posible que la superficie que presenta la plantilla de secciones 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 sección 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) {
            // ...
            setSeeMoreAction(seeAllNetworksPendingIntent)
            // ...
        }
    

Java

    public Slice seeMoreActionSlice(Uri sliceUri) {
        if (getContext() == null) {
            return null;
        }
        ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);
        // ...
        listBuilder.setSeeMoreAction(seeAllNetworksPendingIntent);
        // ...
        return listBuilder.build();
    }
    

Se muestra como en la siguiente imagen:

Cuando el usuario presiona 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) {
            // ...
            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)
                // ...
                .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"))
                );
        // ...
        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 sección inhabilitó el desplazamiento en la vista.
  • No todas las filas se pueden mostrar en el espacio disponible.

Cómo combinar plantillas

Puedes crear una sección enriquecida y dinámica combinando varios tipos de filas. Por ejemplo, una sección 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 sección con una fila de encabezado junto con una cuadrícula que contiene tres celdas.