Modelos do Slices

Este documento traz detalhes sobre como usar os builders de modelo no Android Jetpack para criar Slices.

Definir seu modelo Slice

Os Slices são criados com um ListBuilder. O ListBuilder permite adicionar diferentes tipos de linhas que são exibidas em uma lista. Esta seção descreve cada um desses tipos de linha e como eles são criados.

SliceAction

O elemento mais básico de um modelo Slice é uma SliceAction. Uma SliceAction contém uma etiqueta junto com um PendingIntent e pode ser um dos itens a seguir:

  • Botão de ícone
  • Alternância padrão
  • Alternância personalizada (drawable com um estado ativado/desativado)

A SliceAction é usada pelos builders de modelo descritos no restante desta seção. Uma SliceAction pode ter um modo de imagem definido que determina como a imagem será apresentada para a ação:

  • ICON_IMAGE: tamanho muito pequeno e colorida
  • SMALL_IMAGE: tamanho pequeno e não colorida
  • LARGE_IMAGE: tamanho máximo e não colorida

HeaderBuilder

Na maioria dos casos, é preciso definir um cabeçalho para o modelo usando um HeaderBuilder. Um cabeçalho pode ser compatível com:

  • Título
  • Subtítulo
  • Subtítulo de resumo
  • Ação principal

Veja abaixo alguns exemplos de configurações de cabeçalho. As caixas cinza mostram possíveis locais de ícones e padding:

Renderização de cabeçalho em diferentes superfícies

Quando um Slice é necessário, a superfície em exibição determina como renderizá-lo. A renderização pode apresentar algumas diferenças entre as superfícies de hospedagem.

Em formatos menores, apenas o cabeçalho, se existir, costuma ser exibido. Se você especificou um resumo para o cabeçalho, o texto do resumo será exibido em vez do texto do subtítulo.

Se você não especificou um cabeçalho no modelo, a primeira linha adicionada ao ListBuilder é geralmente exibida.

Exemplo de HeaderBuilder: Slice de lista simples com cabeçalho

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 nos cabeçalhos

Os cabeçalhos de Slices também podem exibir 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

Você pode criar uma linha de conteúdo por meio de um RowBuilder. Uma linha pode ser compatível com qualquer destes itens:

  • Título
  • Subtítulo
  • Item de início: SliceAction, ícone ou carimbo de data/hora
  • Itens de término: SliceAction, ícone ou carimbo de data/hora
  • Ação principal

É possível combinar conteúdos de linha de várias formas, de acordo com as seguintes restrições:

  • Os itens de início não são exibidos na primeira linha de um Slice.
  • Os itens de término não podem ser uma mistura de objetos SliceAction e Icon.
  • Uma linha pode conter apenas um carimbo de data/hora.

Veja exemplos de linha de conteúdo nas imagens a seguir. As caixas cinza mostram possíveis locais de ícones e padding:

Exemplo de RowBuilder: alternância de Wi-Fi

O exemplo abaixo demonstra uma linha com uma ação principal e uma alternância padrão.

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

Você pode criar uma grade de conteúdo por meio de um GridBuilder. Uma grade pode ser compatível com os seguintes tipos de imagem:

  • ICON_IMAGE: tamanho muito pequeno e colorida
  • SMALL_IMAGE: tamanho pequeno e não colorida
  • LARGE_IMAGE: tamanho máximo e não colorida

Uma célula de grade é criada com um CellBuilder. Uma célula pode ser compatível com até duas linhas de texto e uma imagem. Uma célula não pode ficar vazia.

Veja exemplos de grade nas imagens a seguir:

Exemplo de GridRowBuilder: restaurantes por perto

O exemplo abaixo demonstra uma linha de grade que contém imagens e 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

Com um RangeBuilder, você pode criar uma linha que contenha uma barra de progresso ou um intervalo de entrada, como um controle deslizante.

Veja exemplos de barra de progresso e controle deslizante nas seguintes imagens:

Exemplo de RangeBuilder: controle deslizante

O exemplo abaixo demonstra como criar um Slice que contém um controle deslizante de volume por meio de um InputRangeBuilder. Para criar uma linha de progresso, use 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();
}

Conteúdo atrasado

Retorne um Slice o mais rápido possível a partir de SliceProvider.onBindSlice(). Chamadas demoradas podem causar problemas de exibição, como oscilações e redimensionamento abrupto.

Se você tiver conteúdo de Slice que não pode ser carregado rapidamente, crie o Slice com conteúdo de marcador e observe no builder que o conteúdo está carregando. Quando o conteúdo estiver pronto para exibição, chame getContentResolver().notifyChange(sliceUri, null) por meio do URI do Slice. Isso resultará em outra chamada para SliceProvider.onBindSlice(), em que você pode criar o Slice novamente com conteúdo novo.

Exemplo de conteúdo atrasado: deslocamento para o trabalho

Na linha "Ride to work" abaixo, a distância até o trabalho é determinada dinamicamente e pode não estar disponível de imediato. O exemplo de código demonstra o uso de um subtítulo nulo como marcador enquanto o conteúdo é carregado:

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

Processar rolagem desativada no Slice

A superfície que apresenta seu modelo Slice pode não ser compatível com a rolagem no modelo. Nesse caso, parte do conteúdo pode não ser exibida.

Por exemplo, considere um Slice que exibe uma lista de redes Wi-Fi:

Se a lista de Wi-Fi for longa e a rolagem estiver desativada, você poderá adicionar um botão Ver mais para que os usuários possam ver todos os itens da lista. Para adicionar esse botão, use addSeeMoreAction(), conforme mostrado no exemplo a seguir:

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

O resultado pode ser visto nesta imagem:

O toque em Ver mais envia seeAllNetworksPendingIntent.

Como alternativa, se você quiser mostrar uma mensagem ou linha personalizada, adicione um 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();
}

A linha ou ação adicionada por esse método é exibida apenas quando uma das seguintes condições é atendida:

  • O apresentador do Slice desativou a rolagem na visualização.
  • Nem todas as linhas podem ser exibidas no espaço disponível.

Combinar modelos

É possível criar um Slice avançado e dinâmico combinando vários tipos de linha. Por exemplo, um Slice pode conter uma linha de cabeçalho, uma grade com uma única imagem e uma grade com duas células de texto.

Veja um Slice com uma linha de cabeçalho e uma grade com três células.