Modèles de Slices

Ce document explique comment utiliser les compilateurs de modèles dans Android Jetpack pour créer des segments d'application.

Définir votre modèle de segment d'application

Les segments d'application sont construits à l'aide d'un élément ListBuilder. ListBuilder vous permet d'ajouter différents types de lignes affichées dans une liste. Cette section décrit chacun de ces types de lignes et la façon dont ils sont construits.

Action SliceAction (SliceAction)

L'élément le plus basique d'un modèle de segment d'application est un élément SliceAction. Un SliceAction contient un libellé ainsi qu'un élément PendingIntent. Il correspond à l'un des éléments suivants:

  • Bouton d'icône
  • Activer/Désactiver par défaut
  • Bouton d'activation personnalisé (drawable avec un état activé/désactivé)

SliceAction est utilisé par les compilateurs de modèles décrits dans le reste de cette section. Un SliceAction peut avoir un mode d'image défini qui détermine la façon dont l'image est présentée pour l'action:

  • ICON_IMAGE: petite taille et teintée
  • SMALL_IMAGE: petite taille et non teintée
  • LARGE_IMAGE: plus grande taille et non teinté

Générateur d'en-têtes

Dans la plupart des cas, vous devez définir un en-tête pour votre modèle à l'aide d'un HeaderBuilder. Un en-tête peut accepter les éléments suivants:

  • Titre
  • Sous-titre
  • Sous-titre récapitulatif
  • Action principale

Vous trouverez ci-dessous quelques exemples de configurations d'en-tête. Notez que les zones grises indiquent l'icône et l'emplacement potentiels de la marge intérieure:

Rendu de l'en-tête sur différentes surfaces

Lorsqu'un segment d'application est nécessaire, la surface d'affichage détermine comment l'afficher. Notez que le rendu peut varier légèrement entre les surfaces d'hébergement.

Dans les formats plus petits, seul l'en-tête s'affiche généralement, le cas échéant. Si vous avez spécifié un résumé pour l'en-tête, le texte récapitulatif est affiché à la place du sous-titre.

Si vous n'avez pas spécifié d'en-tête dans votre modèle, la première ligne ajoutée à votre ListBuilder s'affiche généralement à la place.

Exemple de HeaderBuilder – Tranche de liste simple avec en-tête

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 dans les en-têtes

Les en-têtes de segments d'application peuvent également afficher des actions de segment d'application:

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

Vous pouvez créer une ligne de contenu à l'aide d'un RowBuilder. Une ligne peut accepter l'un des éléments suivants:

  • Titre
  • Sous-titre
  • Élément de début: SliceAction, Icon ou un code temporel
  • Éléments de fin: SliceAction, Icon ou un code temporel
  • Action principale

Vous pouvez combiner le contenu des lignes de plusieurs manières, sous réserve des restrictions suivantes:

  • Les éléments de début ne s'affichent pas sur la première ligne d'un segment d'application
  • Les éléments de fin ne peuvent pas être une combinaison d'objets SliceAction et Icon
  • Une ligne ne peut contenir qu'un seul code temporel

Les images suivantes présentent des exemples de lignes de contenu. Notez que les zones grises indiquent les emplacements potentiels de l'icône et de la marge intérieure:

Exemple de RowBuilder – Activation/Désactivation du Wi-Fi

L'exemple ci-dessous présente une ligne avec une action principale et un bouton d'activation/désactivation par défaut.

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

Vous pouvez créer une grille de contenu à l'aide d'un GridBuilder. Une grille peut accepter les types d'images suivants:

  • ICON_IMAGE: petite taille et teintée
  • SMALL_IMAGE: petite taille et non teintée
  • LARGE_IMAGE: plus grande taille et non teinté

Une cellule de grille est construite à l'aide d'un élément CellBuilder. Une cellule peut accepter jusqu'à deux lignes de texte et une image. Une cellule ne peut pas être vide.

Des exemples de grille sont illustrés dans les images suivantes:

Exemple GridRowBuilder – Restaurants à proximité

L'exemple ci-dessous illustre une ligne de grille contenant des images et du texte.

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();
  }

Créateur de plages

Un RangeBuilder vous permet de créer une ligne contenant une barre de progression ou une plage d'entrée, telle qu'un curseur.

Les images suivantes présentent des exemples de progression et de curseur:

Exemple RangeBuilder – Curseur

L'exemple ci-dessous montre comment créer un segment d'application contenant un curseur de volume à l'aide d'un InputRangeBuilder. Pour créer une ligne de progression, utilisez 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();
}

Contenu différé

Vous devez renvoyer un segment d'application le plus rapidement possible à partir de SliceProvider.onBindSlice(). Les appels chronophages peuvent entraîner des problèmes d'affichage, tels qu'un scintillement ou un redimensionnement brusque.

Si le contenu du segment d'application ne peut pas être chargé rapidement, vous pouvez créer votre segment d'application avec un contenu d'espace réservé tout en indiquant dans le compilateur que ce contenu est en cours de chargement. Une fois que le contenu est prêt à être affiché, appelez getContentResolver().notifyChange(sliceUri, null) à l'aide de l'URI du segment d'application. Cela entraîne un autre appel à SliceProvider.onBindSlice(), où vous pouvez reconstruire le segment d'application avec un nouveau contenu.

Exemple de contenu retardé : trajet jusqu'au travail

Dans la ligne "Trajet au travail" ci-dessous, la distance jusqu'au travail est déterminée de manière dynamique et peut ne pas être disponible immédiatement. L'exemple de code illustre l'utilisation d'un sous-titre nul comme espace réservé pendant le chargement du contenu:

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"
    );
}

Gérer le défilement désactivé dans votre segment d'application

La surface qui présente votre modèle de segment d'application peut ne pas être compatible avec le défilement dans le modèle. Dans ce cas, une partie de votre contenu peut ne pas s'afficher.

Prenons l'exemple d'un segment d'application affichant une liste de réseaux Wi-Fi:

Si la liste des réseaux Wi-Fi est longue et que le défilement est désactivé, vous pouvez ajouter un bouton See more (Voir plus) pour vous assurer que les utilisateurs ont la possibilité d'afficher tous les éléments de la liste. Vous pouvez ajouter ce bouton à l'aide de addSeeMoreAction(), comme indiqué dans l'exemple suivant:

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();
}

Cela s'affiche comme illustré dans l'image suivante:

Si vous appuyez sur Voir plus, vous envoyez seeAllNetworksPendingIntent.

Si vous souhaitez fournir un message ou une ligne personnalisés, vous pouvez également ajouter un 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 ligne ou l'action ajoutée via cette méthode ne s'affiche que si l'une des conditions suivantes est remplie:

  • Le présentateur de votre segment d'application a désactivé le défilement dans la vue
  • Toutes vos lignes ne peuvent pas s'afficher dans l'espace disponible

Combiner des modèles

Vous pouvez créer un segment d'application dynamique et enrichi en combinant plusieurs types de lignes. Par exemple, un segment d'application peut contenir une ligne d'en-tête, une grille avec une seule image et une grille avec deux cellules de texte.

Voici un segment d'application avec une ligne d'en-tête et une grille contenant trois cellules.