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.
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.
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 activity-embedding
.
Etapa 3: abrir o projeto do codelab
No Android Studio, abra o projeto em Kotlin ou Java.
A pasta activity-embedding
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 umaRecyclerView
.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:
- Execute o app de exemplo no tablet grande ou no emulador do Pixel C. A atividade (lista) principal é mostrada:
- Selecione um item da lista para iniciar uma atividade secundária (de detalhes). A atividade de detalhes se sobrepõe à atividade de lista:
- Gire o tablet para a orientação paisagem. A atividade secundária ainda se sobrepõe à atividade principal e ocupa toda a tela:
- Selecione o controle "Voltar" (seta para a esquerda na barra de apps) para retornar à lista.
- 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:
- Gire o tablet para a orientação paisagem. A atividade secundária ainda se sobrepõe à atividade principal e ocupa toda a tela:
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:
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:
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.2.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:
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:
- Clique com o botão direito do mouse (botão secundário) na pasta de origem do app de exemplo,
com.example.activity_embedding
. - Selecione New > Activity > Empty Views Activity.
- Nomeie a atividade como PlaceholderActivity.
- 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.xm
l na pasta res/layout
.
- 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="" />
- Substitua o conteúdo do arquivo de layout
activity_placeholder.xml
na pastares/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>
- Por fim, adicione o seguinte recurso de string ao arquivo de recursos
strings.xml
na pastares/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:
- Clique com o botão direito do mouse (botão secundário) na pasta de origem do app de exemplo,
com.example.activity_embedding
. - Selecione New > Kotlin Class/File ou New > Java Class.
- Nomeie a classe como SplitInitializer.
- Pressione Enter: o Android Studio cria a classe no pacote de apps de exemplo.
- 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:
- Clique com o botão direito do mouse (botão secundário) na pasta de origem do app de exemplo,
com.example.activity_embedding
. - Selecione New > Kotlin Class/File ou New > Java Class.
- Nomeie a classe como SampleApplication.
- Pressione Enter: o Android Studio cria a classe no pacote de apps de exemplo.
- 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:
- Receba uma instância de singleton de
RuleController
. - Use o método complementar
parseRules()
Java estático ou Kotlin deRuleController
para analisar o arquivo XML. - Adicione as regras analisadas ao
RuleController
com o métodosetRules()
.
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:
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:
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:
O resumo é mostrado em tela cheia no modo paisagem, mesmo sendo iniciado em uma divisão:
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:
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:
- Crie um filtro de divisão de pares que identifique
ListActivity
eDetailActivity
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.
- Adicione o filtro a um conjunto:
SplitManager.kt / createSplit()
val filterSet = setOf(splitPairFilter)
SplitManager.java / createSplit()
Set<SplitPairFilter> filterSet = new HashSet<>();
filterSet.add(splitPairFilter);
- 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.
- 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
eDetailActivity
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.
- 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:
- 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.
- Adicione o filtro a um conjunto:
SplitManager.kt / createSplit()
val placeholderActivityFilterSet = setOf(placeholderActivityFilter)
SplitManager.java / createSplit()
Set<ActivityFilter> placeholderActivityFilterSet = new HashSet<>();
placeholderActivityFilterSet.add(placeholderActivityFilter);
- 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.
- 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:
- 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.
- Adicione o filtro a um conjunto:
SplitManager.kt / createSplit()
val summaryActivityFilterSet = setOf(summaryActivityFilter)
SplitManager.java / createSplit()
Set<ActivityFilter> summaryActivityFilterSet = new HashSet<>();
summaryActivityFilterSet.add(summaryActivityFilter);
- 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.
- 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. Navegação do Material Design
As diretrizes do Material Design especificam diferentes componentes de navegação para diferentes tamanhos de tela: uma coluna de navegação para telas maiores ou iguais a 840 dp, uma barra de navegação na parte de baixo para telas menores que 840 dp.
Com a incorporação de atividades, não é possível usar os métodos getCurrentWindowMetrics()
e getMaximumWindowMetrics()
do WindowManager
para determinar a largura da tela. Isso acontece porque as métricas da janela retornadas pelos métodos descrevem o painel de exibição que contém a atividade incorporada que chamou os métodos.
Para ter as dimensões precisas do seu app de incorporação de atividades, use uma calculadora de atributos de divisão e SplitAttributesCalculatorParams.
Exclua as linhas mostradas abaixo se você adicionou elas a uma seção anterior.
main_split_config.xml
<SplitPairRule
. . .
window:splitMaxAspectRatioInPortrait="alwaysAllow" // Delete this line.
window:splitMaxAspectRatioInLandscape="alwaysDisallow" // Delete this line.
. . .>
</SplitPairRule>
<SplitPlaceholderRule
. . .
window:splitMaxAspectRatioInPortrait="alwaysAllow" // Delete this line.
window:splitMaxAspectRatioInLandscape="alwaysDisallow" // Delete this line.
. . .>
<SplitPlaceholderRule/>
Navegação flexível
Para alternar dinamicamente os componentes de navegação com base no tamanho da tela, use uma calculadora SplitAttributes
. A calculadora detecta mudanças na orientação do dispositivo e no tamanho da janela e recalcula as dimensões de exibição de acordo com as modificações. Vamos integrar a calculadora ao SplitController
para acionar mudanças nos componentes de navegação em resposta a atualizações no tamanho da tela.
Criar layout de navegação
Primeiro, crie um menu que vamos usar para preencher o painel e a barra de navegação.
Na pasta res/menu
, crie um novo arquivo de recurso do menu chamado nav_menu.xml
. Substitua o conteúdo do arquivo do menu pelo seguinte:
nav_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/navigation_home"
android:title="Home" />
<item
android:id="@+id/navigation_dashboard"
android:title="Dashboard" />
<item
android:id="@+id/navigation_settings"
android:title="Settings" />
</menu>
Em seguida, adicione uma barra e uma coluna de navegação ao seu layout. Defina a visibilidade como gone
para que fiquem ocultas inicialmente. Vamos torná-las visíveis mais tarde com base nas dimensões do layout.
activity_list.xml
<com.google.android.material.navigationrail.NavigationRailView
android:id="@+id/navigationRailView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:menu="@menu/nav_menu"
android:visibility="gone" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bottomNavigationView"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:menu="@menu/nav_menu"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:visibility="gone" />
Escreva uma função para lidar com a alternância entre a barra e a coluna de navegação.
ListActivity.kt / setWiderScreenNavigation()
private fun setWiderScreenNavigation(useNavRail: Boolean) {
val navRail = findViewById(R.id.navigationRailView)
val bottomNav = findViewById(R.id.bottomNavigationView)
if (useNavRail) {
navRail.visibility = View.VISIBLE
bottomNav.visibility = View.GONE
} else {
navRail.visibility = View.GONE
bottomNav.visibility = View.VISIBLE
}
}
ListActivity.java / setWiderScreenNavigation()
private void setWiderScreenNavigation(boolean useNavRail) {
NavigationRailView navRail = findViewById(R.id.navigationRailView);
BottomNavigationView bottomNav = findViewById(R.id.bottomNavigationView);
if (useNavRail) {
navRail.setVisibility(View.VISIBLE);
bottomNav.setVisibility(View.GONE);
} else {
navRail.setVisibility(View.GONE);
bottomNav.setVisibility(View.VISIBLE);
}
}
Calculadora de atributos de divisão
O SplitController
recebe informações sobre as divisões de atividades ativas no momento e fornece pontos de interação para personalizar as divisões e criar novas.
Nas seções anteriores, definimos os atributos padrão para divisões especificando splitRatio
e outros atributos nas tags <SplitPairRule>
e <SplitPlaceHolderRule>
nos arquivos XML ou usando as APIs SplitPairRule.Builder#setDefaultSplitAttributes()
e SplitPlaceholderRule.Builder#setDefaultSplitAttributes()
.
Os atributos de divisão padrão serão aplicados se o contêiner pai WindowMetrics atender aos requisitos de dimensão da SplitRule, que são minWidthDp, minHeightDp e minSmallestWidthDp.
Vamos definir uma calculadora de atributos de divisão para substituir os atributos padrão. A calculadora atualiza os pares divididos existentes após uma mudança na janela ou no estado do dispositivo, como mudanças na orientação ou no estado da dobra.
Isso permite que os desenvolvedores saibam os estados do dispositivo ou da janela e definam diferentes atributos de divisão em diferentes cenários, incluindo as orientações retrato e paisagem e a postura de mesa.
Ao criar uma calculadora de atributos de divisão, a plataforma transmite um objeto SplitAttributesCalculatorParams
à função setSplitAttributesCalculator()
. A propriedade parentWindowMetrics
fornece métricas de janela do aplicativo.
No código abaixo, a atividade confere se as restrições padrão foram atendidas, ou seja, largura > 840dp e menor largura > 600dp. Quando as condições são satisfeitas, as atividades são incorporadas em um layout de painel duplo e o app usa uma coluna de navegação em vez de uma barra de navegação na parte de baixo. Caso contrário, as atividades vão aparecer em tela inteira com uma barra de navegação na parte de baixo.
ListActivity.kt / setSplitAttributesCalculator()
SplitController.getInstance(this).setSplitAttributesCalculator
params ->
if (params.areDefaultConstraintsSatisfied) {
// When default constraints are satisfied, use the navigation rail.
setWiderScreenNavigation(true)
return@setSplitAttributesCalculator params.defaultSplitAttributes
} else {
// Use the bottom navigation bar in other cases.
setWiderScreenNavigation(false)
// Expand containers if the device is in portrait or the width is less than 840 dp.
SplitAttributes.Builder()
.setSplitType(SPLIT_TYPE_EXPAND)
.build()
}
}
ListActivity.java / setSplitAttributesCalculator()
SplitController.getInstance(this).setSplitAttributesCalculator(params -> {
if (params.areDefaultConstraintsSatisfied()) {
// When default constraints are satisfied, use the navigation rail.
setWiderScreenNavigation(true);
return params.getDefaultSplitAttributes();
} else {
// Use the bottom navigation bar in other cases.
setWiderScreenNavigation(false);
// Expand containers if the device is in portrait or the width is less than 600 dp.
return new SplitAttributes.Builder()
.setSplitType(SplitType.SPLIT_TYPE_EXPAND)
.build();
}
});
Bom trabalho, seu app de incorporação de atividades agora segue as diretrizes de navegação do Material Design.
8. Parabéns!
Muito bem! Você otimizou um app baseado em atividade para um layout de detalhes e listas em telas grandes e adicionou a navegação do Material Design.
Você aprendeu duas maneiras de implementar a incorporação de atividades:
- Usando um arquivo de configuração XML.
- Fazendo chamadas de API do Jetpack.
- Implementando a navegação flexível com incorporação de atividades
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.
9. Saiba mais
- Guia para desenvolvedores: Incorporação de atividades
- Documentação de referência: androidx.window.embedding