Criar um layout de detalhes e listas com incorporação de atividades

1. Introdução

Telas grandes permitem que você crie interfaces e layouts de apps que melhoram a experiência do usuário e aumentam a produtividade. No entanto, se o app tiver sido projetado para telas pequenas de smartphones não dobráveis, ele provavelmente não aproveitará o espaço de exibição extra oferecido por tablets, dobráveis e dispositivos ChromeOS.

Atualizar um app para aproveitar ao máximo as telas grandes pode ser demorado e caro, principalmente para apps legados baseados em várias atividades.

A incorporação de atividades, apresentada no Android 12L (nível 32 da API), permite que apps baseados em atividade mostrem várias atividades ao mesmo tempo em telas grandes para criar layouts de dois painéis, como detalhes e listas. Não é necessária nenhuma recodificação em Kotlin ou Java. Adicione algumas dependências, crie um arquivo de configuração XML, implemente um inicializador e faça algumas adições ao manifesto do app. Ou, se você preferir trabalhar no código, basta adicionar algumas chamadas da API WindowManager do Jetpack ao método onCreate() da atividade principal do app.

Pré-requisitos

Para concluir este codelab, você precisará de experiência em:

  • criação de apps Android;
  • trabalho com atividades;
  • gravação de XML;
  • trabalho no Android Studio, incluindo a configuração de dispositivos virtuais.

O que você vai criar

Neste codelab, você vai atualizar um app baseado em atividade para oferecer suporte a um layout dinâmico de dois painéis parecido com o SlidingPaneLayout. Em telas pequenas, o app sobrepõe (empilha) atividades na janela de tarefas.

Atividades A, B e C empilhadas na janela de tarefas.

Em telas grandes, o app mostra duas atividades simultaneamente, lado a lado ou na parte de cima e de baixo, com base nas suas especificações.

7533496502081c0c.png

O que você vai aprender

Duas maneiras de implementar a incorporação de atividades:

  • Com um arquivo de configuração XML
  • Usando chamadas da API WindowManager do Jetpack

O que é necessário

  • Versão recente do Android Studio
  • Smartphone ou emulador do Android
  • Tablet pequeno ou emulador do Android
  • Tablet grande ou emulador do Android

2. Configurar

Fazer o download do app de exemplo

Etapa 1: clonar o repositório

Clone o repositório Git de codelabs em tela grande:

git clone https://github.com/android/large-screen-codelabs

Ou faça o download e desarquive o arquivo ZIP dos codelabs em tela grande:

Etapa 2: inspecionar os arquivos de origem do codelab

Navegue até a pasta de incorporação de atividades.

Etapa 3: abrir o projeto do codelab

No Android Studio, abra o projeto em Kotlin ou Java.

Lista de arquivos da pasta de atividades no repositório e no arquivo ZIP.

A pasta de incorporação de atividades no repositório e no arquivo ZIP contém dois projetos do Android Studio: um em Kotlin e outro em Java. Abra o projeto desejado. Os snippets do codelab são fornecidos nas duas linguagens.

Criar dispositivos virtuais

Se você não tiver um smartphone Android, um tablet pequeno ou um tablet grande no nível 32 da API ou mais recente, abra o Gerenciador de dispositivos no Android Studio e crie um dos seguintes dispositivos virtuais necessários:

  • Smartphone: Pixel 6, nível 32 da API ou mais recente.
  • Tablet pequeno: 7 WSVGA (tablet), nível 32 da API ou mais recente.
  • Tablet grande: Pixel C, nível 32 da API ou mais recente.

3. Executar o app

O app de exemplo mostra uma lista de itens. Quando o usuário seleciona um item, o app exibe informações sobre o item.

O app consiste em três atividades:

  • ListActivity: contém uma lista de itens em uma RecyclerView.
  • DetailActivity: mostra informações sobre um item da lista quando ele é selecionado.
  • SummaryActivity: mostra um resumo das informações quando o item Resumo é selecionado.

Comportamento sem incorporação de atividades

Execute o app de exemplo para conferir como ele se comporta sem a incorporação de atividades:

  1. Execute o app de exemplo no tablet grande ou no emulador do Pixel C. A atividade (lista) principal é mostrada:

Tablet grande com o app de exemplo em execução na orientação retrato. Liste as atividades em tela cheia.

  1. Selecione um item da lista para iniciar uma atividade secundária (de detalhes). A atividade de detalhes se sobrepõe à atividade de lista:

Tablet grande com o app de exemplo em execução na orientação retrato. Atividade de detalhes em tela cheia.

  1. Gire o tablet para a orientação paisagem. A atividade secundária ainda se sobrepõe à atividade principal e ocupa toda a tela:

Tablet grande com o app de exemplo em execução na orientação paisagem. Atividade de detalhes em tela cheia.

  1. Selecione o controle "Voltar" (seta para a esquerda na barra de apps) para retornar à lista.
  2. Selecione o último item da lista, Resumo, para iniciar uma atividade de resumo como uma atividade secundária. A atividade de resumo se sobrepõe à atividade de lista:

Tablet grande com o app de exemplo em execução na orientação retrato. Atividade de resumo em tela cheia.

  1. Gire o tablet para a orientação paisagem. A atividade secundária ainda se sobrepõe à atividade principal e ocupa toda a tela:

Tablet grande com o app de exemplo em execução na orientação paisagem. Atividade de resumo em tela cheia.

Comportamento com a incorporação de atividades

Depois de concluir este codelab, a orientação paisagem mostrará as atividades de lista e detalhes lado a lado em um layout de detalhes e listas:

Tablet grande com o app de exemplo em execução na orientação paisagem. Atividades de lista e detalhes no layout de detalhes e listas.

No entanto, você vai configurar o resumo para ser mostrado em tela cheia, mesmo que a atividade seja iniciada dentro de uma divisão. O resumo se sobrepõe à divisão:

Tablet grande com o app de exemplo em execução na orientação paisagem. Atividade de resumo em tela cheia.

4. Segundo plano

A incorporação de atividades divide a janela de tarefas do app em dois contêineres: primário e secundário. Qualquer atividade pode iniciar uma divisão iniciando outra atividade. A atividade inicial ocupa o contêiner principal; a atividade iniciada, o secundário.

A atividade principal pode iniciar outras atividades no contêiner secundário. As atividades nos dois contêineres podem iniciar atividades nos respectivos contêineres. Cada contêiner pode ter uma pilha de atividades. Para saber mais, consulte o guia para desenvolvedores Incorporação de atividades.

Você pode configurar seu app para oferecer suporte à incorporação de atividades criando um arquivo de configuração XML ou fazendo chamadas da API WindowManager do Jetpack. Vamos começar com a abordagem de configuração XML.

5. Configuração de XML

Os contêineres e divisões de incorporação de atividades são criados e gerenciados pela biblioteca WindowManager do Jetpack com base em regras de divisão criadas em um arquivo de configuração XML.

Adicionar a dependência WindowManager

Ative o app de exemplo para acessar a biblioteca WindowManager adicionando a dependência da biblioteca ao arquivo build.gradle no nível do módulo do app, por exemplo:

build.gradle

 implementation 'androidx.window:window:1.1.0'

Informar ao sistema

Informe ao sistema que seu app implementou a incorporação de atividades.

Adicione a propriedade android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED ao elemento <application> do arquivo de manifesto do app e defina o valor como true:

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application>
        <property
            android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
            android:value="true" />
    </application>
</manifest>

Os fabricantes de dispositivos (OEMs) usam essa configuração para ativar recursos personalizados em apps com suporte à incorporação de atividades. Por exemplo, os dispositivos podem ativar o efeito letterbox em atividades do modo retrato (consulte android:screenOrientation) em telas no modo paisagem para orientar as atividades em uma transição tranquila para um layout de dois painéis de incorporação de atividades:

Incorporação de atividades com o app do modo retrato na tela no modo paisagem. A atividade A com efeito letterbox do modo retrato inicia a atividade B incorporada.

Criar um arquivo de configuração

Crie um arquivo de recurso XML com o nome main_split_config.xml na pasta res/xml do app com resources como elemento raiz.

Mude o namespace do XML para:

main_split_config.xml

xmlns:window="http://schemas.android.com/apk/res-auto"

Regra de divisão de pares

Adicione a seguinte regra de divisão ao arquivo de configuração:

main_split_config.xml

<!-- Define a split for the named activity pair. -->
<SplitPairRule
    window:splitRatio="0.33"
    window:splitMinWidthDp="840"
    window:finishPrimaryWithSecondary="never"
    window:finishSecondaryWithPrimary="always">
  <SplitPairFilter
      window:primaryActivityName=".ListActivity"
      window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>

A regra faz o seguinte:

  • Configura as opções de divisão para atividades que compartilham uma divisão:
  • splitRatio: especifica quanto espaço da janela de tarefas é ocupado pela atividade principal (33%), deixando o espaço restante para a atividade secundária.
  • splitMinWidthDp: especifica a largura mínima de exibição (840) necessária para que as duas atividades apareçam na tela ao mesmo tempo. As unidades são pixels independentes de tela (dp).
  • finishPrimaryWithSecondary: especifica se as atividades no contêiner de divisão principal são concluídas (nunca) quando todas as atividades no contêiner secundário são concluídas.
  • finishSecondaryWithPrimary: especifica se as atividades no contêiner de divisão secundário são concluídas (sempre) quando todas as atividades no contêiner principal são concluídas.
  • Inclui um filtro de divisão que define as atividades que compartilham uma divisão de janela de tarefas. A atividade principal é ListActivity; a secundária é DetailActivity.

Regra de marcador de posição

Uma atividade de marcador de posição ocupa o contêiner secundário de uma divisão de atividade quando não há conteúdo disponível para esse contêiner, por exemplo, quando uma divisão de detalhes e listas é aberta, mas um item da lista ainda não foi selecionado. Para saber mais, consulte Marcadores de posição no guia para desenvolvedores Incorporação de atividades.

Adicione a seguinte regra de marcador de posição ao arquivo de configuração:

main_split_config.xml

<!-- Automatically launch a placeholder for the detail activity. -->
<SplitPlaceholderRule
    window:placeholderActivityName=".PlaceholderActivity"
    window:splitRatio="0.33"
    window:splitMinWidthDp="840"
    window:finishPrimaryWithPlaceholder="always"
    window:stickyPlaceholder="false">
  <ActivityFilter
      window:activityName=".ListActivity"/>
</SplitPlaceholderRule>

A regra faz o seguinte:

  • Identifica a atividade do marcador de posição, PlaceholderActivity (criaremos essa atividade na próxima etapa).
  • Configura as opções para o marcador de posição:
  • splitRatio: especifica quanto espaço da janela de tarefas é ocupada pela atividade principal (33%), deixando o espaço restante para o marcador de posição. Normalmente, esse valor precisa corresponder à proporção da regra de divisão de pares com que o marcador de posição está associado.
  • splitMinWidthDp: especifica a largura mínima de exibição (840) necessária para que o marcador de posição apareça na tela com a atividade principal. Normalmente, esse valor precisa corresponder à largura mínima da regra de divisão de pares com que o marcador de posição está associado. As unidades são pixels independentes de tela (dp).
  • finishPrimaryWithPlaceholder: especifica se as atividades no contêiner de divisão principal são concluídas (sempre) quando o marcador de posição é concluído.
  • stickyPlaceholder: indica se o marcador de posição precisa permanecer na tela (falso) como a atividade superior quando a tela é redimensionada para um painel único de uma exibição de dois painéis, por exemplo, quando um dispositivo dobrável está dobrado.
  • Inclui um filtro de atividade que especifica a atividade (ListActivity) com que o marcador de posição compartilha uma divisão da janela de tarefas.

O marcador de posição representa a atividade secundária da regra de divisão de pares cuja atividade principal é igual à atividade no filtro de atividade do marcador de posição. Consulte "Regra de divisão de pares" na seção "Configuração de XML" deste codelab.

Regra da atividade

As regras de atividade são de uso geral. As atividades que você quer que ocupem toda a janela de tarefas, ou seja, que nunca fazem parte de uma divisão, podem ser especificadas com uma regra de atividade. Para saber mais, consulte Modal de tela cheia no guia para desenvolvedores Incorporação de atividades.

Faremos a atividade de resumo preencher toda a janela de tarefas, sobrepondo a divisão. A navegação de retorno volta para a divisão.

Adicione a seguinte regra de atividade ao arquivo de configuração:

main_split_config.xml

<!-- Activities that should never be in a split. -->
<ActivityRule
    window:alwaysExpand="true">
  <ActivityFilter
      window:activityName=".SummaryActivity"/>
</ActivityRule>

A regra faz o seguinte:

  • Identifica a atividade que vai ser mostrada em tela cheia (SummaryActivity).
  • Configura as opções para a atividade:
  • alwaysExpand: especifica se a atividade precisa ou não ser expandida para preencher todo o espaço de exibição disponível.

Arquivo de origem

O arquivo de configuração XML concluído ficará assim:

main_split_config.xml

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:window="http://schemas.android.com/apk/res-auto">

    <!-- Define a split for the named activity pair. -->
    <SplitPairRule
        window:splitRatio="0.33"
        window:splitMinWidthDp="840"
        window:finishPrimaryWithSecondary="never"
        window:finishSecondaryWithPrimary="always">
      <SplitPairFilter
          window:primaryActivityName=".ListActivity"
          window:secondaryActivityName=".DetailActivity"/>
    </SplitPairRule>

    <!-- Automatically launch a placeholder for the detail activity. -->
    <SplitPlaceholderRule
        window:placeholderActivityName=".PlaceholderActivity"
        window:splitRatio="0.33"
        window:splitMinWidthDp="840"
        window:finishPrimaryWithPlaceholder="always"
        window:stickyPlaceholder="false">
      <ActivityFilter
          window:activityName=".ListActivity"/>
    </SplitPlaceholderRule>

    <!-- Activities that should never be in a split. -->
    <ActivityRule
        window:alwaysExpand="true">
      <ActivityFilter
          window:activityName=".SummaryActivity"/>
    </ActivityRule>

</resources>

Criar uma atividade de marcador de posição

Você precisa criar uma nova atividade para servir como o marcador de posição especificado no arquivo de configuração XML. A atividade pode ser muito simples, apenas algo que indica aos usuários que o conteúdo será mostrado aqui.

Crie a atividade na pasta de origem principal do app de exemplo.

No Android Studio, faça o seguinte:

  1. Clique com o botão direito do mouse (botão secundário) na pasta de origem do app de exemplo, com.example.activity_embedding.
  2. Selecione New > Activity > Empty Views Activity.
  3. Nomeie a atividade como PlaceholderActivity.
  4. Selecione Finish.

O Android Studio cria a atividade no pacote de apps de exemplo, adiciona a atividade ao arquivo de manifesto do app e cria um arquivo de recurso de layout chamado activity_placeholder.xml na pasta res/layout.

  1. No arquivo AndroidManifest.xml do app de exemplo, defina o rótulo da atividade do marcador de posição como uma string vazia:

AndroidManifest.xml

<activity
    android:name=".PlaceholderActivity"
    android:exported="false"
    android:label="" />
  1. Substitua o conteúdo do arquivo de layout activity_placeholder.xml na pasta res/layout pelo seguinte:

activity_placeholder.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:background="@color/gray"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".PlaceholderActivity">

  <TextView
      android:id="@+id/textViewPlaceholder"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/placeholder_text"
      android:textSize="36sp"
      android:textColor="@color/obsidian"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
  1. Por fim, adicione o seguinte recurso de string ao arquivo de recursos strings.xml na pasta res/values:

strings.xml

<string name="placeholder_text">Placeholder</string>

Criar um inicializador

O componente RuleController do WindowManager analisa as regras definidas no arquivo de configuração XML e as disponibiliza para o sistema.

Uma biblioteca Startup do Jetpack Initializer permite que o RuleController acesse o arquivo de configuração.

A biblioteca Startup executa a inicialização do componente na inicialização do app. A inicialização precisa ocorrer antes de qualquer atividade começar para que RuleController tenha acesso às regras de divisão e possa aplicá-las, se necessário.

Adicionar a dependência da biblioteca Startup

Para ativar a funcionalidade de inicialização, adicione a dependência da biblioteca Startup ao arquivo build.gradle no nível do módulo do app de exemplo, por exemplo:

build.gradle

implementation 'androidx.startup:startup-runtime:1.1.1'

Implementar um inicializador para RuleController

Crie uma implementação da interface Initializer da Startup.

No Android Studio, faça o seguinte:

  1. Clique com o botão direito do mouse (botão secundário) na pasta de origem do app de exemplo.
  2. Selecione New > Kotlin Class/File ou New > Java Class.
  3. Nomeie a classe como SplitInitializer.
  4. Pressione Enter: o Android Studio cria a classe no pacote de apps de exemplo.
  5. Substitua o conteúdo do arquivo da classe pelo seguinte:

SplitInitializer.kt

package com.example.activity_embedding

import android.content.Context
import androidx.startup.Initializer
import androidx.window.embedding.RuleController

class SplitInitializer : Initializer<RuleController> {

  override fun create(context: Context): RuleController {
    return RuleController.getInstance(context).apply {
      setRules(RuleController.parseRules(context, R.xml.main_split_config))
    }
  }

  override fun dependencies(): List<Class<out Initializer<*>>> {
    return emptyList()
  }
}

SplitInitializer.java

package com.example.activity_embedding;

import android.content.Context;
import androidx.annotation.NonNull;
import androidx.startup.Initializer;
import androidx.window.embedding.RuleController;
import java.util.Collections;
import java.util.List;

public class SplitInitializer implements Initializer<RuleController> {

   @NonNull
   @Override
   public RuleController create(@NonNull Context context) {
      RuleController ruleController = RuleController.getInstance(context);
      ruleController.setRules(
          RuleController.parseRules(context, R.xml.main_split_config)
      );
      return ruleController;
   }

   @NonNull
   @Override
   public List<Class<? extends Initializer<?>>> dependencies() {
       return Collections.emptyList();
   }
}

O inicializador disponibiliza as regras de divisão para o componente RuleController transmitindo o ID do arquivo de recurso XML que contém as definições de (main_split_config) para o método parseRules() do componente. O método setRules() adiciona as regras analisadas ao RuleController.

Criar um provedor de inicialização

Um provedor invoca o processo de inicialização das regras de divisão.

Adicione androidx.startup.InitializationProvider ao elemento <application> do arquivo de manifesto do app de exemplo como um provedor e refira a SplitInitializer:

AndroidManifest.xml

<provider android:name="androidx.startup.InitializationProvider"
    android:authorities="${applicationId}.androidx-startup"
    android:exported="false"
    tools:node="merge">
    <!-- Make SplitInitializer discoverable by InitializationProvider. -->
    <meta-data android:name="${applicationId}.SplitInitializer"
        android:value="androidx.startup" />
</provider>

InitializationProvider inicializa o SplitInitializer, que, por sua vez, invoca os métodos RuleController que analisam o arquivo de configuração XML (main_split_config.xml) e adicionam as regras ao RuleController. Consulte "Implementar um inicializador no RuleController" acima.

O InitializationProvider descobre e inicializa o SplitInitializer antes da execução do método onCreate() do app. Assim, as regras de divisão entrarão em vigor quando a atividade do app principal for iniciada.

Arquivo de origem

Confira o manifesto do app completo:

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

  <application
      android:allowBackup="true"
      android:dataExtractionRules="@xml/data_extraction_rules"
      android:fullBackupContent="@xml/backup_rules"
      android:icon="@mipmap/ic_launcher"
      android:label="@string/app_name"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:supportsRtl="true"
      android:theme="@style/Theme.Activity_Embedding"
      tools:targetApi="32">
    <activity
        android:name=".ListActivity"
        android:exported="true">
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
      </intent-filter>
    </activity>
    <activity
        android:name=".DetailActivity"
        android:exported="false"
        android:label="" />
    <activity
        android:name=".SummaryActivity"
        android:exported="false"
        android:label="" />
    <activity
        android:name=".PlaceholderActivity"
        android:exported="false"
        android:label="" />
    <property
        android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
        android:value="true" />
    <provider
        android:name="androidx.startup.InitializationProvider"
        android:authorities="${applicationId}.androidx-startup"
        android:exported="false"
        tools:node="merge">
      <!-- Make SplitInitializer discoverable by InitializationProvider. -->
      <meta-data
          android:name="${applicationId}.SplitInitializer"
          android:value="androidx.startup" />
    </provider>
  </application>

</manifest>

Atalho de inicialização

Se você não tiver problemas ao misturar a configuração XML com as APIs do WindowManager, poderá eliminar o inicializador da biblioteca Startup e o provedor de manifesto para uma implementação muito mais simples.

Depois de criar o arquivo de configuração XML, faça o seguinte:

Etapa 1: criar uma subclasse de Application

A subclasse do aplicativo será a primeira classe instanciada quando o processo do app for criado. Adicione as regras de divisão ao RuleController no método onCreate() da subclasse para garantir que elas sejam aplicadas antes do início de qualquer atividade.

No Android Studio, faça o seguinte:

  1. Clique com o botão direito do mouse (botão secundário) na pasta de origem do app de exemplo.
  2. Selecione New > Kotlin Class/File ou New > Java Class.
  3. Nomeie a classe como SampleApplication.
  4. Pressione Enter: o Android Studio cria a classe no pacote de apps de exemplo.
  5. Estenda a classe no supertipo Application.

SampleApplication.kt

package com.example.activity_embedding

import android.app.Application

/**
 * Initializer for activity embedding split rules.
 */
class SampleApplication : Application() {

}

SampleApplication.java

package com.example.activity_embedding;

import android.app.Application;

/**
 * Initializer for activity embedding split rules.
 */
public class SampleApplication extends Application {

}

Etapa 2: inicializar o RuleController

Adicione as regras de divisão do arquivo de configuração XML ao RuleController no método onCreate() da subclasse do seu aplicativo.

Para adicionar as regras ao RuleController, faça o seguinte:

  1. Receba uma instância de singleton de RuleController.
  2. Use o método complementar parseRules() Java estático ou Kotlin de RuleController para analisar o arquivo XML.
  3. Adicione as regras analisadas ao RuleController com o método setRules().

SampleApplication.kt

override fun onCreate() {
  super.onCreate()
  RuleController.getInstance(this)
    .setRules(RuleController.parseRules(this, R.xml.main_split_config))
}

SampleApplication.java

@Override
public void onCreate() {
  super.onCreate();
  RuleController.getInstance(this)
    .setRules(RuleController.parseRules(this, R.xml.main_split_config));
}

Etapa 3: adicionar o nome da subclasse ao manifesto

Adicione o nome da subclasse ao elemento <application> do manifesto do app:

AndroidManifest.xml

<application
    android:name=".SampleApplication"
    . . .

Executar

Crie e execute o app de exemplo.

Em um smartphone não dobrável, as atividades são sempre empilhadas, mesmo na orientação paisagem:

Atividade de detalhes (secundária) empilhada sobre a atividade de lista (principal) no smartphone na orientação retrato. Atividade de detalhes (secundária) empilhada sobre a atividade de lista (principal) no smartphone na orientação paisagem.

No Android 13 (nível 33 da API) e versões anteriores, a incorporação de atividades não é ativada em smartphones não dobráveis, independente das especificações de largura mínima da divisão.

O suporte para a incorporação de atividades em smartphones não dobráveis em níveis mais altos da API depende da ativação desse recurso por parte do fabricante do dispositivo.

Em um tablet pequeno ou no emulador 7 WSVGA (tablet), as duas atividades são empilhadas na orientação retrato, mas aparecem lado a lado na orientação paisagem:

Atividades de lista e detalhes empilhadas na orientação retrato em um tablet pequeno. Atividades de lista e detalhes lado a lado na orientação paisagem em um tablet pequeno.

Em um tablet grande ou no emulador Pixel C, as atividades são empilhadas na orientação retrato (consulte "Proporção" abaixo), mas aparecem lado a lado na orientação paisagem:

Atividades de lista e detalhes empilhadas na orientação retrato em um tablet grande. Atividades de lista e detalhes lado a lado na orientação paisagem em um tablet grande.

O resumo é mostrado em tela cheia no modo paisagem, mesmo sendo iniciado em uma divisão:

Atividade de resumo sobreposta à divisão no modo paisagem em um tablet grande.

Proporção

As divisões de atividade são controladas pela proporção da tela, além da largura mínima da divisão. Os atributos splitMaxAspectRatioInPortrait e splitMaxAspectRatioInLandscape especificam a proporção máxima de exibição (altura:largura) em que as divisões de atividade são mostradas. Os atributos representam as propriedades maxAspectRatioInPortrait e maxAspectRatioInLandscape de SplitRule.

Se a proporção de uma tela exceder o valor em qualquer orientação, as divisões serão desativadas, independente da largura da tela. O valor padrão para a orientação retrato é 1,4 (consulte SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT), que impede que telas altas e estreitas incluam divisões. Por padrão, as divisões são sempre permitidas na orientação paisagem (consulte SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT).

O emulador PIxel C tem uma largura de exibição na orientação retrato de 900 dp, que é mais larga do que a configuração splitMinWidthDp no arquivo de configuração XML do app de exemplo, de modo que o emulador precisa mostrar uma divisão de atividade. Mas a proporção do Pixel C no modo retrato é maior que 1,4, o que impede que as divisões de atividades sejam mostradas na orientação retrato.

É possível definir a proporção máxima para telas no modo retrato e paisagem no arquivo de configuração XML nos elementos SplitPairRule e SplitPlaceholderRule. Por exemplo:

main_split_config.xml

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:window="http://schemas.android.com/apk/res/android">

  <!-- Define a split for the named activity pair. -->
  <SplitPairRule
      . . .
      window:splitMaxAspectRatioInPortrait="alwaysAllow"
      window:splitMaxAspectRatioInLandscape="alwaysDisallow"
      . . .
 </SplitPairRule>

  <SplitPlaceholderRule
      . . .
      window:splitMaxAspectRatioInPortrait="alwaysAllow"
      window:splitMaxAspectRatioInLandscape="alwaysDisallow"
      . . .
  </SplitPlaceholderRule>

</resources>

Em um tablet grande com largura de tela no modo retrato maior ou igual a 840 dp ou no emulador Pixel C, as atividades ficam lado a lado na orientação retrato, mas empilhadas na orientação paisagem:

Atividades de lista e detalhes lado a lado na orientação retrato em um tablet grande. Atividades de lista e detalhes empilhadas na orientação paisagem em um tablet grande.

Atividade complementar

Tente definir a proporção no app de exemplo, como mostrado acima, para as orientações retrato e paisagem. Teste as configurações com o tablet grande (se a largura na orientação retrato for 840 dp ou maior) ou no emulador Pixel C. Você verá uma divisão de atividade na orientação retrato, mas não no modo paisagem.

Determine a proporção do modo retrato do tablet grande (a proporção do Pixel C é ligeiramente maior que 1,4). Defina splitMaxAspectRatioInPortrait como valores maiores e menores do que a proporção. Execute o app e observe os resultados.

6. API WindowManager

Você pode ativar a incorporação de atividades inteiramente no código com um único método chamado de dentro do método onCreate() da atividade que inicia a divisão. Se preferir trabalhar no código em vez de usar XML, este é o caminho a seguir.

Adicionar a dependência WindowManager

Seu app precisa de acesso à biblioteca WindowManager, esteja você criando uma implementação baseada em XML ou usando chamadas de API. Consulte a seção "Configuração de XML" deste codelab para saber como adicionar a dependência WindowManager ao seu app.

Informar ao sistema

Independente de você usar um arquivo de configuração XML ou chamadas de API WindowManager, seu app precisa notificar o sistema que a incorporação de atividades foi implementada. Consulte a seção "Configuração de XML" deste codelab para saber como informar o sistema sobre a implementação.

Criar uma classe para gerenciar divisões

Nesta seção do codelab, você implementará uma divisão de atividade inteiramente em um único método de objeto estático ou complementar que será chamado na atividade principal do app de exemplo, ListActivity.

Crie uma classe chamada SplitManager com o método createSplit que inclua um parâmetro context (algumas chamadas de API exigem o parâmetro):

SplitManager.kt

class SplitManager {

    companion object {

        fun createSplit(context: Context) {
        }
}

SplitManager.java

class SplitManager {

    static void createSplit(Context context) {
    }
}

Chame o método onCreate() na subclasse da classe Application.

Para detalhes sobre por que e como criar subclasses de Application, consulte "Atalho de inicialização" na seção "Configuração de XML" deste codelab.

SampleApplication.kt

package com.example.activity_embedding

import android.app.Application

/**
 * Initializer for activity embedding split rules.
 */
class SampleApplication : Application() {

  override fun onCreate() {
    super.onCreate()
    SplitManager.createSplit(this)
  }
}

SampleApplication.java

package com.example.activity_embedding;

import android.app.Application;

/**
 * Initializer for activity embedding split rules.
 */
public class SampleApplication extends Application {

  @Override
  public void onCreate() {
    super.onCreate();
    SplitManager.createSplit(this);
  }
}

Criar uma regra de divisão

APIs necessárias:

SplitPairRule define uma regra de divisão para um par de atividades.

SplitPairRule.Builder cria uma SplitPairRule. O builder usa um conjunto de objetos SplitPairFilter como argumento. Os filtros especificam quando a regra precisa ser aplicada.

Você registra a regra com uma instância de singleton do componente RuleController, que disponibiliza as regras de divisão para o sistema.

Para criar uma regra de divisão, faça o seguinte:

  1. Crie um filtro de divisão de pares que identifique ListActivity e DetailActivity como as atividades que compartilham uma divisão:

SplitManager.kt / createSplit()

val splitPairFilter = SplitPairFilter(
    ComponentName(context, ListActivity::class.java),
    ComponentName(context, DetailActivity::class.java),
    null
)

SplitManager.java / createSplit()

SplitPairFilter splitPairFilter = new SplitPairFilter(
    new ComponentName(context, ListActivity.class),
    new ComponentName(context, DetailActivity.class),
    null
);

O filtro pode incluir uma ação da intent (terceiro parâmetro) para a inicialização da atividade secundária. Se você incluir uma ação da intent, o filtro verificará a ação junto com o nome da atividade. Para atividades no seu próprio app, você provavelmente não filtrará a ação da intent e, portanto, o argumento pode ser nulo.

  1. Adicione o filtro a um conjunto:

SplitManager.kt / createSplit()

val filterSet = setOf(splitPairFilter)

SplitManager.java / createSplit()

Set<SplitPairFilter> filterSet = new HashSet<>();
filterSet.add(splitPairFilter);
  1. Crie atributos de layout para a divisão:

SplitManager.kt / createSplit()

val splitAttributes: SplitAttributes = SplitAttributes.Builder()
      .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
      .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
      .build()

SplitManager.java / createSplit()

SplitAttributes splitAttributes = new SplitAttributes.Builder()
  .setSplitType(SplitAttributes.SplitType.ratio(0.33f))
  .setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
  .build();

SplitAttributes.Builder cria um objeto que contém atributos de layout:

  • setSplitType: define como a área de exibição disponível é alocada para cada contêiner de atividade. O tipo de divisão de proporção especifica a proporção da tela ocupada pelo contêiner principal. O contêiner secundário ocupa a área de exibição restante.
  • setLayoutDirection: especifica como os contêineres de atividade são dispostos em relação aos outros, sendo o contêiner principal primeiro.
  1. Crie uma regra de divisão de pares:

SplitManager.kt / createSplit()

val splitPairRule = SplitPairRule.Builder(filterSet)
      .setDefaultSplitAttributes(splitAttributes)
      .setMinWidthDp(840)
      .setMinSmallestWidthDp(600)
      .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
      .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
      .setClearTop(false)
      .build()

SplitManager.java / createSplit()

SplitPairRule splitPairRule = new SplitPairRule.Builder(filterSet)
  .setDefaultSplitAttributes(splitAttributes)
  .setMinWidthDp(840)
  .setMinSmallestWidthDp(600)
  .setFinishPrimaryWithSecondary(SplitRule.FinishBehavior.NEVER)
  .setFinishSecondaryWithPrimary(SplitRule.FinishBehavior.ALWAYS)
  .setClearTop(false)
  .build();

SplitPairRule.Builder cria e configura a regra:

  • filterSet: contém filtros de pares divididos que determinam quando aplicar a regra identificando atividades com a mesma divisão. No app de exemplo, ListActivity e DetailActivity são especificadas em um filtro de divisão de pares (consulte as etapas anteriores).
  • setDefaultSplitAttributes: aplica atributos de layout à regra.
  • setMinWidthDp: define a largura mínima de exibição (em pixels i de densidade independente, dp) que permite uma divisão.
  • setMinSmallestWidthDp: define o valor mínimo (em dp) que a menor das duas dimensões de tela precisa ter para permitir uma divisão, independente da orientação do dispositivo.
  • setFinishPrimaryWithSecondary: como a conclusão de todas as atividades no contêiner secundário afeta as atividades no contêiner principal. NEVER indica que o sistema não pode finalizar as atividades principais quando todas as atividades no contêiner secundário forem concluídas. Consulte Concluir atividades.
  • setFinishSecondaryWithPrimary: define como a conclusão de todas as atividades no contêiner principal afeta as atividades no contêiner secundário. ALWAYS indica que o sistema precisa concluir as atividades no contêiner secundário quando todas as atividades no contêiner principal forem concluídas. Consulte Concluir atividades.
  • setClearTop: especifica se todas as atividades no contêiner secundário são concluídas quando uma nova atividade é iniciada no contêiner. "False" especifica que novas atividades são empilhadas sobre as que já estão no contêiner secundário.
  1. Consiga a instância de singleton do WindowManager RuleController e adicione a regra:

SplitManager.kt / createSplit()

val ruleController = RuleController.getInstance(context)
ruleController.addRule(splitPairRule)

SplitManager.java / createSplit()

RuleController ruleController = RuleController.getInstance(context);
ruleController.addRule(splitPairRule);

Criar uma regra de marcador de posição

APIs necessárias:

SplitPlaceholderRule define uma regra para uma atividade que ocupa o contêiner secundário quando nenhum conteúdo está disponível para esse contêiner. Para criar uma atividade de marcador de posição, consulte "Criar uma atividade de marcador de posição" na seção "Configuração de XML" deste codelab. Para saber mais, consulte Marcadores de posição no guia para desenvolvedores Incorporação de atividades.

SplitPlaceholderRule.Builder cria uma SplitPlaceholderRule. O builder usa um conjunto de objetos ActivityFilter como argumento. Os objetos especificam atividades com as quais a regra de marcador de posição está associada. Se o filtro corresponder a uma atividade iniciada, o sistema aplicará a regra de marcador de posição.

Você registra a regra com o componente RuleController.

Para criar uma regra de divisão de marcador de posição, faça o seguinte:

  1. Crie um ActivityFilter:

SplitManager.kt / createSplit()

val placeholderActivityFilter = ActivityFilter(
    ComponentName(context, ListActivity::class.java),
    null
)

SplitManager.java / createSplit()

ActivityFilter placeholderActivityFilter = new ActivityFilter(
    new ComponentName(context, ListActivity.class),
    null
);

O filtro associa a regra à atividade principal do app de exemplo, ListActivity. Assim, quando nenhum conteúdo detalhado estiver disponível no layout de detalhes e listas, o marcador de posição preenche a área de detalhes.

O filtro pode incluir uma ação da intent (segundo parâmetro) para a inicialização da atividade associada (ListActivity). Se você incluir uma ação da intent, o filtro verificará a ação junto com o nome da atividade. Para atividades no seu próprio app, você provavelmente não filtrará a ação da intent e, portanto, o argumento pode ser nulo.

  1. Adicione o filtro a um conjunto:

SplitManager.kt / createSplit()

val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

SplitManager.java / createSplit()

Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
placeholderActivityFilterSet.add(placeholderActivityFilter);
  1. Crie uma SplitPlaceholderRule:

SplitManager.kt / createSplit()

val splitPlaceholderRule = SplitPlaceholderRule.Builder(
      placeholderActivityFilterSet,
      Intent(context, PlaceholderActivity::class.java)
    ).setDefaultSplitAttributes(splitAttributes)
     .setMinWidthDp(840)
     .setMinSmallestWidthDp(600)
     .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
     .build()

SplitManager.java / createSplit()

SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder(
  placeholderActivityFilterSet,
  new Intent(context, PlaceholderActivity.class)
).setDefaultSplitAttributes(splitAttributes)
 .setMinWidthDp(840)
 .setMinSmallestWidthDp(600)
 .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
 .build();

SplitPlaceholderRule.Builder cria e configura a regra:

  • placeholderActivityFilterSet: contém filtros de atividade que determinam quando aplicar a regra identificando atividades associadas a ela.
  • Intent: especifica a inicialização da atividade do marcador de posição.
  • setDefaultSplitAttributes: aplica atributos de layout à regra.
  • setMinWidthDp: define a largura mínima de exibição (em pixels i de densidade independente, dp) que permite uma divisão.
  • setMinSmallestWidthDp: define o valor mínimo (em dp) que a menor das duas dimensões de tela precisa ter para permitir uma divisão, independente da orientação do dispositivo.
  • setFinishPrimaryWithPlaceholder: define como o término da atividade do marcador de posição afeta as atividades no contêiner principal. ALWAYS indica que o sistema sempre precisa concluir as atividades no contêiner principal quando o marcador de posição é concluído. Consulte Concluir atividades.
  1. Adicione a regra ao RuleController do WindowManager:

SplitManager.kt / createSplit()

ruleController.addRule(splitPlaceholderRule)

SplitManager.java / createSplit()

ruleController.addRule(splitPlaceholderRule);

Criar uma regra de atividade

APIs necessárias:

A ActivityRule pode ser usada para definir uma regra para uma atividade que ocupa toda a janela de tarefas, como uma caixa de diálogo modal. Para saber mais, consulte Modal de tela cheia no guia para desenvolvedores Incorporação de atividades.

SplitPlaceholderRule.Builder cria uma SplitPlaceholderRule. O builder usa um conjunto de objetos ActivityFilter como argumento. Os objetos especificam atividades com as quais a regra de marcador de posição está associada. Se o filtro corresponder a uma atividade iniciada, o sistema aplicará a regra de marcador de posição.

Você registra a regra com o componente RuleController.

Para criar uma regra de atividade, faça o seguinte:

  1. Crie um ActivityFilter:

SplitManager.kt / createSplit()

val summaryActivityFilter = ActivityFilter(
    ComponentName(context, SummaryActivity::class.java),
    null
)

SplitManager.java / createSplit()

ActivityFilter summaryActivityFilter = new ActivityFilter(
    new ComponentName(context, SummaryActivity.class),
    null
);

O filtro especifica a atividade à qual a regra se aplica, SummaryActivity.

O filtro pode incluir uma ação da intent (segundo parâmetro) para a inicialização da atividade associada (SummaryActivity). Se você incluir uma ação da intent, o filtro verificará a ação junto com o nome da atividade. Para atividades no seu próprio app, você provavelmente não filtrará a ação da intent e, portanto, o argumento pode ser nulo.

  1. Adicione o filtro a um conjunto:

SplitManager.kt / createSplit()

val summaryActivityFilterSet = setOf(summaryActivityFilter)

SplitManager.java / createSplit()

Set<ActivityFilter> summaryActivityFilterSet = new HashSet<>();
summaryActivityFilterSet.add(summaryActivityFilter);
  1. Crie uma ActivityRule:

SplitManager.kt / createSplit()

val activityRule = ActivityRule.Builder(summaryActivityFilterSet)
      .setAlwaysExpand(true)
      .build()

SplitManager.java / createSplit()

ActivityRule activityRule = new ActivityRule.Builder(
    summaryActivityFilterSet
).setAlwaysExpand(true)
 .build();

ActivityRule.Builder cria e configura a regra:

  • summaryActivityFilterSet: contém filtros de atividade que determinam quando aplicar a regra identificando as atividades que você quer excluir das divisões.
  • setAlwaysExpand: especifica se a atividade precisa ou não ser expandida para preencher todo o espaço de exibição disponível.
  1. Adicione a regra ao RuleController do WindowManager:

SplitManager.kt / createSplit()

ruleController.addRule(activityRule)

SplitManager.java / createSplit()

ruleController.addRule(activityRule);

Executar

Crie e execute o app de exemplo.

O aplicativo precisa se comportar da mesma forma que quando é personalizado usando um arquivo de configuração XML.

Consulte "Executar" na seção "Configuração de XML" deste codelab.

Atividade complementar

Defina a proporção no app de exemplo usando os métodos setMaxAspectRatioInPortrait e setMaxAspectRatioInLandscape de SplitPairRule.Builder e SplitPlaceholderRule.Builder. Especifique valores com as propriedades e os métodos da classe EmbeddingAspectRatio, por exemplo:

SplitPairRule.Builder(filterSet)
  . . .
  .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
  . . .
.build()

Teste as configurações com o tablet grande ou o emulador Pixel C.

Determine a proporção do modo retrato do tablet grande (a proporção do Pixel C é ligeiramente maior que 1,4). Defina a proporção máxima no modo retrato como valores maiores e menores do que a do tablet ou do Pixel C. Teste as propriedades ALWAYS_ALLOW e ALWAYS_DISALLOW.

Execute o app e observe os resultados.

Para saber mais, consulte "Proporção" na seção "Configuração de XML" deste codelab.

7. Parabéns!

Muito bem! Você otimizou um app baseado em atividade para um layout de detalhes e listas em telas grandes.

Você aprendeu duas maneiras de implementar a incorporação de atividades:

  • Usando um arquivo de configuração XML.
  • Fazendo chamadas de API do Jetpack.

E você não reprogramou nenhum código-fonte em Kotlin ou Java do app.

Você já pode otimizar seus apps de produção para telas grandes com a incorporação de atividades.

8. Saiba mais