1. Introdução
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.
O codelab Criar um layout de detalhes e listas com incorporação de atividades e Material Design abordou como usar chamadas de API XML ou do Jetpack WindowManager para criar um layout de detalhes e listas.
Este codelab orienta você sobre alguns recursos recém-lançados para incorporação de atividades, que melhoram ainda mais a experiência no app em dispositivos de tela grande. Os recursos incluem expansão do painel, fixação de atividades e escurecimento da tela cheia para caixas de diálogo.
Pré-requisitos
- Já ter feito o codelab Criar um layout de detalhes e listas com incorporação de atividades e Material Design
- Experiência de trabalho no Android Studio, incluindo a configuração de dispositivos virtuais com o Android 15
O que você vai aprender
Como:
- Ativar a expansão do painel
- Implementar a fixação de atividades com uma das janelas divididas
- Usar o escurecimento da tela cheia para caixas de diálogo
O que é necessário
- Versão recente do Android Studio
- Smartphone Android ou emulador com o Android 15
- Tablet grande ou emulador do Android com largura mínima maior que 600 dp
2. Configurar
Baixar o 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-advanced
.
Etapa 3: abrir o projeto do codelab
No Android Studio, abra o projeto em Kotlin ou Java.
A pasta activity-embedding-advanced
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 35 da API ou mais recente, abra o Gerenciador de dispositivos no Android Studio e crie um dos seguintes dispositivos virtuais necessários:
- Smartphone: Pixel 8, nível 35 da API ou mais recente
- Tablet: Pixel Tablet, nível 35 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.
Continuar do codelab anterior
No codelab Criar um layout de detalhes e listas com incorporação de atividades e Material Design, desenvolvemos um aplicativo com uma visualização de detalhes e listas usando a incorporação de atividades com navegação facilitada por uma coluna de navegação e uma barra de navegação na parte de baixo.
- Execute o app em um tablet grande ou no emulador do Pixel no modo retrato. Você vai ver a tela de lista principal e uma barra de navegação na parte de baixo.
- Gire o tablet na horizontal (paisagem). A tela vai se dividir, mostrando a lista de um lado e os detalhes do outro. A barra de navegação na parte de baixo precisa ser substituída por uma coluna de navegação vertical.
Novos recursos com incorporação de atividades
Tudo pronto para melhorar seu layout de painel duplo? Neste codelab, vamos adicionar alguns recursos novos e interessantes para melhorar a experiência dos usuários. Confira o que vamos criar:
- Vamos tornar esses painéis dinâmicos. Vamos implementar a expansão de painéis, permitindo que os usuários redimensionem (ou expandam) os painéis para ter uma visualização personalizada.
- Vamos dar aos usuários o poder de priorizar! Com a fixação de atividades, os usuários podem manter as tarefas mais importantes sempre na tela.
- Precisa se concentrar em uma tarefa específica? Vamos adicionar um recurso de escurecimento da tela cheia para eliminar distrações e permitir que os usuários se concentrem no que é mais importante.
4. Expansão do painel
Ao usar um layout de painel duplo em uma tela grande, em muitos casos, os usuários precisam se concentrar em um dos painéis divididos enquanto mantêm o outro na tela. Por exemplo, lendo artigos em um lado e mantendo uma lista de conversas de chat no outro. É comum que os usuários queiram redimensionar os painéis para se concentrar em uma atividade.
Para esse objetivo, a incorporação de atividades adiciona uma nova API que permite aos usuários mudar a proporção de divisão e personalizar a transição de redimensionamento.
Adicionar dependência
Primeiro, adicione o WindowManager 1.4 ao arquivo build.gradle
.
Observação: alguns dos recursos desta biblioteca funcionam apenas no Android 15 (nível 35 da API) e em versões mais recentes.
build.gradle
implementation 'androidx.window:window:1.4.0-alpha02'
Personalizar o divisor de janela
Crie uma instância de DividerAttributes
e adicione-a a SplitAttributes
. Esse objeto configura o comportamento geral do layout dividido. É possível usar as propriedades de cor, largura e intervalo de arrasto de DividerAttributes
para melhorar a experiência do usuário.
Personalizar o divisor:
- Confira o nível da API Extensions do WindowManager. Como o recurso de expansão do painel está disponível apenas no nível 6 da API e em versões mais recentes, isso também se aplica ao restante dos novos recursos.
- Crie
DividerAttributes
: para estilizar o divisor entre os painéis, crie um objetoDividerAttributes
. Esse objeto permite definir:
- color: muda a cor do divisor para que ele combine com o tema do app ou crie um contraste visual.
- widthDp: ajusta a largura do divisor para melhorar a visibilidade ou ter uma aparência mais sutil.
- Adicione
SplitAttributes
: depois de personalizar o divisor, adicione-o ao objetoDividerAttributes
. - Defina o intervalo de arrasto (opcional): você também pode controlar até onde os usuários podem arrastar o divisor para redimensionar os painéis.
DRAG_RANGE_SYSTEM_DEFAULT
: use esse valor especial para permitir que o sistema determine um intervalo de arrasto adequado com base no tamanho da tela e no formato do dispositivo.- Valor personalizado (entre 0,33 e 0,66): defina seu próprio intervalo de arrasto para limitar até que ponto os usuários podem redimensionar os painéis. Se o usuário arrastar além desse limite, o layout dividido será desativado.
Substitua splitAttributes
pelo código abaixo.
SplitManager.kt
val splitAttributesBuilder: SplitAttributes.Builder = SplitAttributes.Builder()
.setSplitType(SplitAttributes.SplitType.ratio(0.33f))
.setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
if (WindowSdkExtensions.getInstance().extensionVersion >= 6) {
splitAttributesBuilder.setDividerAttributes(
DividerAttributes.DraggableDividerAttributes.Builder()
.setColor(getColor(context, R.color.divider_color))
.setWidthDp(4)
.setDragRange(
DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
.build()
)
}
val splitAttributes: SplitAttributes = splitAttributesBuilder.build()
SplitManager.java
SplitAttributes.Builder splitAttributesBuilder = new SplitAttributes.Builder()
.setSplitType(SplitAttributes.SplitType.ratio(0.33f))
.setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT);
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
splitAttributesBuilder.setDividerAttributes(
new DividerAttributes.DraggableDividerAttributes.Builder()
.setColor(ContextCompat.getColor(context, R.color.divider_color))
.setWidthDp(4)
.setDragRange(DividerAttributes.DragRange.DRAG_RANGE_SYSTEM_DEFAULT)
.build()
);
}
SplitAttributes splitAttributes = splitAttributesBuilder.build();
Crie divider_color.xml
na pasta res/color
com o conteúdo abaixo.
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="#669df6" />
</selector>
Executar
Tudo pronto. Crie e execute o app de exemplo.
Você vai notar a expansão do painel e ele poderá ser arrastado.
Mudar a proporção de divisão em versões mais antigas
Observação importante sobre compatibilidade: o recurso de expansão de painel está disponível apenas nas Extensões do WindowManager 6 ou mais recentes, o que significa que você precisa do Android 15 (nível 35 da API) ou mais recente.
No entanto, ainda é importante oferecer uma boa experiência para os usuários em versões mais antigas do Android.
No Android 14 (nível 34 da API) e em versões anteriores, ainda é possível fornecer ajustes dinâmicos de proporção de divisão usando a classe SplitAttributesCalculator
. Isso oferece uma maneira de manter algum nível de controle do usuário sobre o layout, mesmo sem a expansão do painel.
Quer saber como usar esses recursos da melhor forma? Vamos abordar todas as práticas recomendadas e dicas na seção "Práticas recomendadas".
5. Fixação de atividades
Você já quis manter uma parte da tela dividida fixa enquanto navegava livremente na outra? Imagine ler um artigo longo em um lado e ainda poder interagir com o conteúdo de outro app na outra metade.
É aí que entra a fixação de atividades. Ela permite fixar uma das janelas divididas para que ela permaneça na tela mesmo enquanto você navega na outra. Isso oferece uma experiência de multitarefas mais focada e produtiva para os usuários.
Adicionar o botão de fixação
Primeiro, vamos adicionar um botão em DetailActivity.
. O aplicativo fixa esse DetailActivity
quando os usuários clicam no botão.
Faça as seguintes mudanças em activity_detail.xml
:
- Adicione um ID ao
ConstraintLayout
android:id="@+id/detailActivity"
- Adicione um botão na parte de baixo do layout
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/pinButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pin_this_activity"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/>
- Restrinja a parte de baixo da
TextView
à parte de cima do botão
app:layout_constraintBottom_toTopOf="@id/pinButton"
Remova esta linha em TextView
.
app:layout_constraintBottom_toBottomOf="parent"
Confira o código XML completo do arquivo de layout activity_detail.xml
, incluindo o botão PIN THIS ACTIVITY (FIXAR ESTA ATIVIDADE) que acabamos de adicionar:
<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:id="@+id/detailActivity"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
tools:context=".DetailActivity">
<TextView
android:id="@+id/textViewItemDetail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="36sp"
android:textColor="@color/obsidian"
app:layout_constraintBottom_toTopOf="@id/pinButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/pinButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/pin_this_activity"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textViewItemDetail"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Adicione a string pin_this_activity
ao arquivo res/values/strings.xml
.
<string name="pin_this_activity">PIN THIS ACTIVITY</string>
Conectar o botão de fixação
- Declarar a variável: no arquivo
DetailActivity.kt
, declare uma variável para manter uma referência ao botão PIN THIS ACTIVITY:
DetailActivity.kt
private lateinit var pinButton: Button
DetailActivity.java
private Button pinButton;
- Encontre o botão no layout e adicione um callback
setOnClickListener()
.
DetailActivity.kt / onCreate
pinButton = findViewById(R.id.pinButton)
pinButton.setOnClickListener {
pinActivityStackExample(taskId)
}
DetailActivity.java / onCreate()
Button pinButton = findViewById(R.id.pinButton);
pinButton.setOnClickListener( (view) => {
pinActivityStack(getTaskId());
});
- Crie um novo método com o nome
pinActivityStackExample
na classeDetailActivity
. Vamos implementar a lógica de fixação real aqui.
DetailActivity.kt
private fun pinActivityStackExample(taskId: Int) {
val splitAttributes: SplitAttributes = SplitAttributes.Builder()
.setSplitType(SplitAttributes.SplitType.ratio(0.66f))
.setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
.build()
val pinSplitRule = SplitPinRule.Builder()
.setSticky(true)
.setDefaultSplitAttributes(splitAttributes)
.build()
SplitController.getInstance(applicationContext).pinTopActivityStack(taskId, pinSplitRule)
}
DetailActivity.java
private void pinActivityStackExample(int taskId) {
SplitAttributes splitAttributes = new SplitAttributes.Builder()
.setSplitType(SplitAttributes.SplitType.ratio(0.66f))
.setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
.build();
SplitPinRule pinSplitRule = new SplitPinRule.Builder()
.setSticky(true)
.setDefaultSplitAttributes(splitAttributes)
.build();
SplitController.getInstance(getApplicationContext()).pinTopActivityStack(taskId, pinSplitRule);
}
Observação:
- Só é possível fixar uma atividade por vez. Libere a atividade fixada com
unpinTopActivityStack()
antes de fixar outra.
- Para ativar a expansão do painel ao fixar a atividade, chame
setDividerAttributes()
para a atividade recém-criada
SplitAttributes
também.
Mudanças na navegação de retorno
Com o WindowManager 1.4, o comportamento da navegação de retorno mudou. O evento de retorno é enviado para a última atividade em foco ao usar a navegação por botões.
Navegação com botões:
- Com a navegação com botões, o evento de retorno agora é enviado de forma consistente para a última atividade em foco. Isso simplifica o comportamento de navegação de retorno, tornando-o mais previsível para os usuários.
Navegação por gestos:
- Android 14 (nível 34 da API) e versões anteriores: o gesto de retorno envia o evento para a atividade em que ele ocorreu, o que pode levar a um comportamento inesperado em cenários de tela dividida.
- Android 15 (nível 35 da API) e versões mais recentes:
- Atividades no mesmo app: o gesto de retorno sempre encerra a atividade de cima, independente da direção do deslizar, oferecendo uma experiência mais unificada.
- Atividades de apps diferentes (sobreposição): o evento de retorno vai para a última atividade em foco, semelhante ao comportamento da navegação com botões.
Executar
Crie e execute o app de exemplo.
Fixar a atividade
- Navegue até a tela
DetailActivity
. - Toque no botão PIN THIS ACTIVITY (FIXAR ATIVIDADE).
6. Escurecimento da tela cheia para caixas de diálogo
Embora a incorporação de atividades facilite os layouts de tela dividida, as caixas de diálogo nas versões anteriores escureciam apenas o contêiner da própria atividade. Isso pode criar uma experiência visual desconexa, especialmente quando você quer que a caixa de diálogo seja o foco.
A solução: WindowManager 1.4
- Temos recomendações para você. Com o WindowManager 1.4, as caixas de diálogo agora escurecem toda a janela do app por padrão (
DimAreaBehavior.Companion.ON_TASK
), proporcionando uma sensação mais imersiva e focada. - Precisa do comportamento antigo de volta? Sem problemas. Você ainda pode optar por escurecer apenas o contêiner da atividade usando
ON_ACTIVITY_STACK
.
|
|
Confira como usar o ActivityEmbeddingController
para gerenciar o comportamento de escurecimento da tela cheia:
Observação: o escurecimento da caixa de diálogo em tela cheia está disponível com a versão 5 ou mais recente das extensões do WindowManager.
SplitManager.kt / createSplit()
with(ActivityEmbeddingController.getInstance(context)) {
if (WindowSdkExtensions.getInstance().extensionVersion >= 5) {
setEmbeddingConfiguration(
EmbeddingConfiguration.Builder()
.setDimAreaBehavior(ON_TASK)
.build()
)
}
}
SplitManager.java / createSplit()
ActivityEmbeddingController controller = ActivityEmbeddingController.getInstance(context);
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 5) {
controller.setEmbeddingConfiguration(
new EmbeddingConfiguration.Builder()
.setDimAreaBehavior(EmbeddingConfiguration.DimAreaBehavior.ON_TASK)
.build()
);
}
Para mostrar o recurso de escurecimento da tela cheia, vamos usar uma caixa de diálogo de alerta que pede a confirmação do usuário antes de fixar a atividade. Ao aparecer, essa caixa de diálogo escurece toda a janela do aplicativo, não apenas o contêiner em que a atividade está.
DetailActivity.kt
pinButton.setOnClickListener {
showAlertDialog(taskId)
}
...
private fun showAlertDialog(taskId: Int) {
val builder = AlertDialog.Builder(this)
builder.setTitle(getString(R.string.dialog_title))
builder.setMessage(getString(R.string.dialog_message))
builder.setPositiveButton(getString(R.string.button_yes)) { _, _ ->
if (WindowSdkExtensions.getInstance().extensionVersion >= 6) {
pinActivityStackExample(taskId)
}
}
builder.setNegativeButton(getString(R.string.button_cancel)) { _, _ ->
// Cancel
}
val dialog: AlertDialog = builder.create()
dialog.show()
}
DetailActivity.java
pinButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showAlertDialog(getTaskId());
}
});
...
private void showAlertDialog(int taskId) {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.dialog_title));
builder.setMessage(getString(R.string.dialog_message));
builder.setPositiveButton(getString(R.string.button_yes), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if (WindowSdkExtensions.getInstance().getExtensionVersion() >= 6) {
pinActivityStackExample(taskId);
}
}
});
builder.setNegativeButton(getString(R.string.button_cancel), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
// Cancel
}
});
AlertDialog dialog = builder.create();
dialog.show();
}
Adicione as strings abaixo ao arquivo res/values/strings.xml
.
<!-- Dialog information -->
<string name="dialog_title">Activity Pinning</string>
<string name="dialog_message">Confirm to pin this activity</string>
<string name="button_yes">Yes</string>
<string name="button_cancel">Cancel</string>
Executar
Crie e execute o app de exemplo.
Clique no botão de fixação da atividade:
- Uma caixa de diálogo de alerta vai aparecer, pedindo que você confirme a ação de fixação.
- Observe como toda a tela, incluindo os dois painéis divididos, fica escurecida, dando foco à caixa de diálogo.
7. Práticas recomendadas
Permitir que os usuários desativem o layout de painel duplo
Para facilitar a transição para os novos layouts, vamos permitir que os usuários mudem entre as visualizações de painel duplo e de coluna única. Isso pode ser feito usando a SplitAttributesCalculator
e SharedPreferences
para armazenar as preferências do usuário.
Mudar a proporção de divisão no Android 14 e em versões anteriores
Analisamos a expansão do painel, que é uma ótima maneira de ajustar a proporção de divisão no Android 15 e em versões mais recentes. Mas como podemos oferecer um nível semelhante de flexibilidade aos usuários de versões mais antigas do Android?
Vamos entender como a SplitAttributesCalculator
pode ajudar a fazer isso e garantir uma experiência consistente em uma variedade maior de dispositivos.
Confira abaixo um exemplo:
Criar a tela de configurações
Para começar, vamos criar uma tela de configurações dedicada para a configuração do usuário.
Nesta tela de configurações, vamos incorporar uma chave para ativar ou desativar o recurso de incorporação de atividades para todo o aplicativo. Além disso, vamos incluir uma barra de progresso que permite aos usuários ajustar a proporção de divisão do layout de painel duplo. O valor da proporção de divisão só será aplicado se a chave de incorporação de atividades estiver ativada.
Depois que o usuário define valores em SettingsActivity
, eles são salvos em SharedPreferences
para serem usados mais tarde em outros lugares do aplicativo.
build.gradle
Adicione a dependência de preferência.
implementation 'androidx.preference:preference-ktx:1.2.1' // Kotlin
Ou
implementation 'androidx.preference:preference:1.2.1' // Java
SettingsActivity.kt
package com.example.activity_embedding
import android.os.Bundle
import android.view.MenuItem
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SeekBarPreference
import androidx.preference.SwitchPreferenceCompat
class SettingsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.commit()
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) finishActivity()
return super.onOptionsItemSelected(item)
}
private fun finishActivity() { finish() }
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.root_preferences, rootKey)
findPreference<SwitchPreferenceCompat>("dual_pane")?.setOnPreferenceChangeListener { _, newValue ->
if (newValue as Boolean) {
this.activity?.let {
SharePref(it.applicationContext).setAEFlag(true)
}
} else {
this.activity?.let {
SharePref(it.applicationContext).setAEFlag(false)
}
}
this.activity?.finish()
true
}
val splitRatioPreference: SeekBarPreference? = findPreference("split_ratio")
splitRatioPreference?.setOnPreferenceChangeListener { _, newValue ->
if (newValue is Int) {
this.activity?.let { SharePref(it.applicationContext).setSplitRatio(newValue.toFloat()/100) }
}
true
}
}
}
}
SettingsActivity.java
package com.example.activity_embedding;
import android.os.Bundle;
import android.view.MenuItem;
import androidx.appcompat.app.AppCompatActivity;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.SeekBarPreference;
import androidx.preference.SwitchPreferenceCompat;
public class SettingsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.settings_activity);
if (savedInstanceState == null) {
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.settings, new SettingsFragment())
.commit();
}
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finishActivity();
return true;
}
return super.onOptionsItemSelected(item);
}
private void finishActivity() {
finish();
}
public static class SettingsFragment extends PreferenceFragmentCompat {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.root_preferences, rootKey);
SwitchPreferenceCompat dualPanePreference = findPreference("dual_pane");
if (dualPanePreference != null) {
dualPanePreference.setOnPreferenceChangeListener((preference, newValue) -> {
boolean isDualPane = (Boolean) newValue;
if (getActivity() != null) {
SharePref sharePref = new SharePref(getActivity().getApplicationContext());
sharePref.setAEFlag(isDualPane);
getActivity().finish();
}
return true;
});
}
SeekBarPreference splitRatioPreference = findPreference("split_ratio");
if (splitRatioPreference != null) {
splitRatioPreference.setOnPreferenceChangeListener((preference, newValue) -> {
if (newValue instanceof Integer) {
float splitRatio = ((Integer) newValue) / 100f;
if (getActivity() != null) {
SharePref sharePref = new SharePref(getActivity().getApplicationContext());
sharePref.setSplitRatio(splitRatio);
}
}
return true;
});
}
}
}
}
Adicionar settings_activity.xml
na pasta de layout
settings_activity.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/settings"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
Adicione SettingsActivity
ao arquivo de manifesto.
<activity
android:name=".SettingsActivity"
android:exported="false"
android:label="@string/title_activity_settings" />
Configure regras de divisão para SettingsActivity
.
SplitManager.kt / createSplit()
val settingActivityFilter = ActivityFilter(
ComponentName(context, SettingsActivity::class.java),
null
)
val settingActivityFilterSet = setOf(settingActivityFilter)
val settingActivityRule = ActivityRule.Builder(settingActivityFilterSet)
.setAlwaysExpand(true)
.build()
ruleController.addRule(settingActivityRule)
SplitManager.java / createSplit()
Set<ActivityFilter> settingActivityFilterSet = new HashSet<>();
ActivityFilter settingActivityFilter = new ActivityFilter(
new ComponentName(context, SettingsActivity.class),
null
);
settingActivityFilterSet.add(settingActivityFilter);
ActivityRule settingActivityRule = new ActivityRule.Builder(settingActivityFilterSet)
.setAlwaysExpand(true).build();
ruleController.addRule(settingActivityRule);
Este é o código para salvar as configurações do usuário em SharedPreferences
.
SharedPref.kt
package com.example.activity_embedding
import android.content.Context
import android.content.SharedPreferences
class SharePref(context: Context) {
private val sharedPreferences: SharedPreferences =
context.getSharedPreferences("my_app_preferences", Context.MODE_PRIVATE)
companion object {
private const val AE_FLAG = "is_activity_embedding_enabled"
private const val SPLIT_RATIO = "activity_embedding_split_ratio"
const val DEFAULT_SPLIT_RATIO = 0.3f
}
fun setAEFlag(isEnabled: Boolean) {
sharedPreferences.edit().putBoolean(AE_FLAG, isEnabled).apply()
}
fun getAEFlag(): Boolean = sharedPreferences.getBoolean(AE_FLAG, true)
fun getSplitRatio(): Float = sharedPreferences.getFloat(SPLIT_RATIO, DEFAULT_SPLIT_RATIO)
fun setSplitRatio(ratio: Float) {
sharedPreferences.edit().putFloat(SPLIT_RATIO, ratio).apply()
}
}
SharedPref.java
package com.example.activity_embedding;
import android.content.Context;
import android.content.SharedPreferences;
public class SharePref {
private static final String PREF_NAME = "my_app_preferences";
private static final String AE_FLAG = "is_activity_embedding_enabled";
private static final String SPLIT_RATIO = "activity_embedding_split_ratio";
public static final float DEFAULT_SPLIT_RATIO = 0.3f;
private final SharedPreferences sharedPreferences;
public SharePref(Context context) {
this.sharedPreferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
}
public void setAEFlag(boolean isEnabled) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putBoolean(AE_FLAG, isEnabled);
editor.apply();
}
public boolean getAEFlag() {
return sharedPreferences.getBoolean(AE_FLAG, true);
}
public float getSplitRatio() {
return sharedPreferences.getFloat(SPLIT_RATIO, DEFAULT_SPLIT_RATIO);
}
public void setSplitRatio(float ratio) {
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putFloat(SPLIT_RATIO, ratio);
editor.apply();
}
}
Você também precisa de um layout de tela de preferências em XML. Crie root_preferences.xml
em res/xml com o código abaixo.
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory app:title="@string/split_setting_header">
<SwitchPreferenceCompat
app:key="dual_pane"
app:title="@string/dual_pane_title" />
<SeekBarPreference
app:key="split_ratio"
app:title="@string/split_ratio_title"
android:min="0"
android:max="100"
app:defaultValue="50"
app:showSeekBarValue="true" />
</PreferenceCategory>
</PreferenceScreen>
E adicione o código abaixo ao arquivo res/values/strings.xml
.
<string name="title_activity_settings">SettingsActivity</string>
<string name="split_setting_header">Dual Pane Display</string>
<string name="dual_pane_title">Dual Pane</string>
<string name="split_ratio_title">Split Ratio</string>
Adicionar a SettingsActivity
ao menu
Vamos conectar a SettingsActivity
recém-criada a um destino de navegação para que possa ser facilmente acessada na interface principal do app pelo usuário.
- No arquivo
ListActivity
, declare variáveis para a barra de navegação de baixo e a barra de navegação à esquerda:
ListActivity.kt
private lateinit var navRail: NavigationRailView private lateinit var bottomNav: BottomNavigationView
ListActivity.java
private NavigationRailView navRail; private BottomNavigationView bottomNav;
- No método
onCreate()
daListActivity
, usefindViewById
para conectar essas variáveis às visualizações correspondentes no layout. - Adicione um
OnItemSelectedListener
à barra de navegação de baixo e à coluna de navegação para processar eventos de seleção de itens:
ListActivity.kt / onCreate()
navRail = findViewById(R.id.navigationRailView)
bottomNav = findViewById(R.id.bottomNavigationView)
val menuListener = NavigationBarView.OnItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
true
}
R.id.navigation_dashboard -> {
true
}
R.id.navigation_settings -> {
startActivity(Intent(this, SettingsActivity::class.java))
true
}
else -> false
}
}
navRail.setOnItemSelectedListener(menuListener)
bottomNav.setOnItemSelectedListener(menuListener)
ListActivity.java / onCreate()
NavigationRailView navRail = findViewById(R.id.navigationRailView);
BottomNavigationView bottomNav = findViewById(R.id.bottomNavigationView);
NavigationBarView.OnItemSelectedListener menuListener = new NavigationBarView.OnItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
// Handle navigation_home selection
return true;
case R.id.navigation_dashboard:
// Handle navigation_dashboard selection
return true;
case R.id.navigation_settings:
startActivity(new Intent(ListActivity.this, SettingsActivity.class));
return true;
default:
return false;
}
}
};
navRail.setOnItemSelectedListener(menuListener);
bottomNav.setOnItemSelectedListener(menuListener);
O aplicativo lê as SharedPreferences
e renderiza o app no modo de tela dividida ou SPLIT_TYPE_EXPAND
.
- Quando a configuração da janela muda, o programa confere se a restrição de janela dividida é atendida (se a largura for maior que 840 dp).
- O app verifica o valor de
SharedPreferences
para saber se o usuário ativou a janela dividida para exibição. Caso contrário, ele retornaSplitAttribute
com o tipoSPLIT_TYPE_EXPAND
. - Se a janela dividida estiver ativada, o app vai ler o valor de
SharedPreferences
para receber a proporção da divisão. Isso só funciona quando a versão deWindowSDKExtensions
é anterior a 6, porque a versão 6 já é compatível com a expansão do painel e ignora a configuração da proporção de divisão. Em vez disso, os desenvolvedores podem permitir que os usuários arrastem o divisor na interface.
ListActivity.kt / onCreate()
...
SplitController.getInstance(this).setSplitAttributesCalculator{
params -> params.defaultSplitAttributes
if (params.areDefaultConstraintsSatisfied) {
setWiderScreenNavigation(true)
if (SharePref(this.applicationContext).getAEFlag()) {
if (WindowSdkExtensions.getInstance().extensionVersion < 6) {
// Read a dynamic split ratio from shared preference.
val currentSplit = SharePref(this.applicationContext).getSplitRatio()
if (currentSplit != SharePref.DEFAULT_SPLIT_RATIO) {
return@setSplitAttributesCalculator SplitAttributes.Builder()
.setSplitType(SplitAttributes.SplitType.ratio(SharePref(this.applicationContext).getSplitRatio()))
.setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
.build()
}
}
return@setSplitAttributesCalculator params.defaultSplitAttributes
} else {
SplitAttributes.Builder()
.setSplitType(SPLIT_TYPE_EXPAND)
.build()
}
} else {
setWiderScreenNavigation(false)
SplitAttributes.Builder()
.setSplitType(SPLIT_TYPE_EXPAND)
.build()
}
}
...
ListActivity.java / onCreate()
...
SplitController.getInstance(this).setSplitAttributesCalculator(params -> {
if (params.areDefaultConstraintsSatisfied()) {
setWiderScreenNavigation(true);
SharePref sharedPreference = new SharePref(this.getApplicationContext());
if (sharedPreference.getAEFlag()) {
if (WindowSdkExtensions.getInstance().getExtensionVersion() < 6) {
// Read a dynamic split ratio from shared preference.
float currentSplit = sharedPreference.getSplitRatio();
if (currentSplit != SharePref.DEFAULT_SPLIT_RATIO) {
return new SplitAttributes.Builder()
.setSplitType(SplitAttributes.SplitType.ratio(sharedPreference.getSplitRatio()))
.setLayoutDirection(SplitAttributes.LayoutDirection.LEFT_TO_RIGHT)
.build();
}
}
return params.getDefaultSplitAttributes();
} else {
return new SplitAttributes.Builder()
.setSplitType(SPLIT_TYPE_EXPAND)
.build();
}
} else {
setWiderScreenNavigation(false);
return new SplitAttributes.Builder()
.setSplitType(SPLIT_TYPE_EXPAND)
.build();
}
});
...
Para acionar a SplitAttributesCalculator
após as mudanças de configuração, precisamos invalidar os atributos atuais. Para isso, chamamos invalidateVisibleActivityStacks
()
no ActivityEmbeddingController
;
antes do WindowManager 1.4. O método é chamado
invalidateTopVisibleSplitAttributes
.
ListActivity.kt / onResume()
override fun onResume() {
super.onResume()
ActivityEmbeddingController.getInstance(this).invalidateVisibleActivityStacks()
}
ListActivity.java / onResume()
@Override
public void onResume() {
super.onResume();
ActivityEmbeddingController.getInstance(this).invalidateVisibleActivityStacks();
}
Executar
Crie e execute o app de exemplo.
Conheça as configurações:
- Navegue até a tela de configurações.
- Ative e desative a opção Ativar janela dividida.
- Ajuste o controle deslizante da proporção de divisão (se disponível no seu dispositivo).
Observe as mudanças no layout:
- Em dispositivos com o Android 14 e versões anteriores: o layout precisa mudar entre os modos de painel único e duplo com base na troca, e a proporção de divisão precisa mudar quando você ajustar o controle deslizante.
- Em dispositivos com o Android 15 e versões mais recentes: a expansão de painéis permite redimensionar os painéis de forma dinâmica, independente da configuração do controle deslizante.
8. Parabéns!
Muito bem! Você melhorou seu app com novos recursos usando a incorporação de atividades e o WindowManager. Agora, os usuários vão ter uma experiência mais flexível, intuitiva e envolvente em telas grandes, independente da versão do Android.
9. Saiba mais
- Guia para desenvolvedores: Incorporação de atividades
- Documentação de referência: androidx.window.embedding