Incorporação de atividades

A incorporação de atividades otimiza apps em dispositivos de tela grande dividindo a janela de tarefa de um app em duas atividades ou duas instâncias.

Figura 1. App Configurações com atividades lado a lado.

Caso seu app seja composto por várias atividades, a incorporação permite melhorar a experiência do usuário em tablets, dobráveis e dispositivos ChromeOS.

A incorporação não requer refatoração do código. Para determinar como o app mostra as atividades (lado a lado ou empilhadas), crie um arquivo de configuração XML ou faça chamadas da API Jetpack WindowManager.

O suporte a telas pequenas é mantido automaticamente. Quando o app está em um dispositivo com uma tela pequena, as atividades são empilhadas uma sobre a outra. Em telas grandes, as atividades são exibidas lado a lado. O sistema determina a apresentação com base na configuração criada e nenhuma lógica de ramificação é necessária.

A incorporação de atividades acomoda mudanças de orientação do dispositivo e funciona perfeitamente em dispositivos dobráveis, empilhando e desempilhando atividades conforme o dispositivo é dobrado e desdobrado.

A incorporação de atividades pode ser realizada na maioria dos dispositivos de tela grande que executam o Android 12L (nível 32 da API) e mais recentes.

Dividir janela de tarefas

A incorporação de atividades divide a janela de tarefas do app em dois contêineres: primário e secundário. Os contêineres guardam as atividades iniciadas pela atividade principal ou por outras atividades que já estão neles.

As atividades são empilhadas no contêiner secundário conforme são iniciadas, e ele é empilhado sobre o contêiner principal em telas pequenas. Portanto, o empilhamento de atividades e a navegação de retorno são consistentes com a ordem das atividades já integradas ao app.

A incorporação permite mostrar atividades de várias maneiras. Seu pode dividir a janela de tarefas iniciando duas atividades lado a lado simultaneamente:

Figura 2. Duas atividades lado a lado.

Ou então, uma atividade que ocupa toda a janela da tarefa pode criar uma divisão iniciando outra lado a lado:

Figura 3. A atividade A inicia a atividade B ao lado dela.

As atividades que já estão em uma divisão e compartilhando uma janela de tarefas podem iniciar outras atividades das seguintes maneiras:

  • Ao lado, por cima da outra atividade:

    Figura 4. A atividade A inicia a atividade C ao lado e por cima da atividade B.
  • Ao lado e com a divisão movida para a lateral, ocultando a atividade principal anterior:

    Figura 5. A atividade B inicia a atividade C ao lado e muda a divisão lateralmente.
  • A atividade é iniciada por cima, ou seja, na mesma pilha de atividades:

    Figura 6. A atividade B inicia a atividade C sem outras sinalizações de intent.
  • A atividade é iniciada em tela cheia na mesma tarefa:

    Figura 7. A atividade A ou a atividade B inicia a atividade C, que é preenchida da janela de tarefas.

Navegação de retorno

Diferentes tipos de aplicativos podem ter diferentes regras de navegação de retorno em um estado da janela de tarefas dividida, dependendo das dependências entre as atividades ou de como os usuários acionam o evento de retorno, por exemplo:

  • Juntos: se as atividades estão relacionadas e uma delas não deve ser mostrada sem o outro, a navegação de retorno pode ser configurada para concluir ambas.
  • Sozinhas: se as atividades são totalmente independentes, a navegação de retorno em uma atividade não afeta o estado de outra na janela de tarefas.

O evento de retorno é enviado para a última atividade em foco ao usar a navegação por botões.

Para navegação com base em gestos:

  • Android 14 (nível 34 da API) e versões anteriores: o evento "Voltar" é enviado à atividade em que o gesto ocorreu. Quando os usuários deslizam do lado esquerdo da tela, o evento de retorno é enviado para a atividade no painel esquerdo da janela dividida. Quando os usuários deslizam do lado direito da tela, o evento de retorno é enviado para a atividade no painel direito.

  • Android 15 (nível 35 da API) e versões mais recentes

    • Ao lidar com várias atividades do mesmo app, o gesto finaliza a atividade principal, independentemente da direção do deslizar, oferecendo uma experiência mais unificada.

    • Em cenários que envolvem duas atividades de apps diferentes (sobreposição), o evento de retorno é direcionado para a última atividade em foco, alinhando-se ao comportamento da navegação por botão.

Layout com vários painéis

A Jetpack WindowManager permite criar uma atividade que incorpora vários painéis. o layout em dispositivos de tela grande com o Android 12L (nível 32 da API) ou mais recente e ativado alguns dispositivos com versões anteriores da plataforma. Os apps que são baseados em várias atividades em vez de fragmentos ou layouts baseados em visualização, como SlidingPaneLayout, podem oferecer uma experiência do usuário melhor em telas grandes sem refatorar o código-fonte.

Um exemplo comum é uma divisão do painel de detalhes e lista. Para garantir uma alta qualidade apresentação, o sistema inicia a atividade de lista e o aplicativo inicia imediatamente a atividade de detalhes. O sistema de transição aguarda até que atividades são desenhadas e depois as exibe juntas. Para o usuário, os dois são iniciadas como uma.

Figura 8. Duas atividades iniciadas ao mesmo tempo em um layout de vários painéis.

Dividir atributos

É possível especificar como a janela da tarefa é proporcional entre os contêineres divididos e como eles são dispostos em relação aos outros.

Para as regras definidas em um arquivo de configuração XML, use os seguintes atributos:

  • splitRatio: define as proporções do contêiner. O valor é um número de ponto flutuante no intervalo aberto (0,0, 1,0).
  • splitLayoutDirection: especifica como os contêineres de divisão são dispostos. estão relacionados. Os valores incluem:
    • ltr: da esquerda para a direita
    • rtl: da direita para a esquerda
    • locale: o uso de ltr ou rtl é determinado pela configuração de localidade

Consulte exemplos na seção Configuração de XML.

Para as regras criadas usando as APIs WindowManager, crie um SplitAttributes objeto com SplitAttributes.Builder e chamar o seguinte builder métodos:

Consulte a seção API WindowManager para conferir exemplos.

Figura 9. Duas divisões de atividade dispostas da esquerda para a direita, mas com proporções de divisão diferentes.

Marcadores de posição

As atividades de marcador de posição são atividades secundárias vazias que ocupam uma área de divisão de atividade. Elas precisam ser substituídas por outra atividade com conteúdo. Por exemplo, uma atividade de marcador de posição pode ocupar o lado secundário de uma divisão de atividades em um layout de detalhes da lista até que um item da lista seja selecionado. Nesse momento, uma atividade contendo as informações detalhadas do item da lista selecionado substitui o marcador.

Por padrão, o sistema mostra marcadores somente quando há espaço suficiente para uma divisão de atividade. Os marcadores de posição são concluídos automaticamente quando o tamanho de exibição alterações em uma largura ou altura muito pequenas para exibir uma divisão. Quando o espaço permitir, o sistema reinicia o marcador com um estado reinicializado.

Figura 10. Dispositivo se dobrando e desdobrando. A atividade do marcador de posição é finalizada e recriada conforme o tamanho da tela muda.

No entanto, o atributo stickyPlaceholder de uma classe SplitPlaceholderRule ou O método setSticky() de SplitPlaceholder.Builder pode substituir o comportamento padrão. Quando o atributo ou método especifica um valor de true, o sistema mostra o marcador de posição como a principal atividade na janela de tarefas quando a tela é redimensionada para uma tela de painel único a partir de uma tela de dois painéis. Confira um exemplo na seção Configuração dividida.

Figura 11. Dispositivo se dobrando e desdobrando. A atividade do marcador de posição é fixa.

Mudanças no tamanho das janelas

Quando as mudanças na configuração do dispositivo reduzem a largura da janela de tarefas para que ela não seja grande o suficiente para um layout de vários painéis (por exemplo, quando um dispositivo dobrável de tela grande dobra do tamanho de tablet para o tamanho de smartphone ou quando a janela do app é redimensionada no modo de várias janelas), as atividades que não são marcadores de posição no painel secundário da janela de tarefas são empilhadas sobre as atividades no painel principal.

As atividades de marcador de posição são exibidas apenas quando há espaço na tela de largura suficiente para uma divisão. Em telas menores, o marcador é dispensado automaticamente. Quando a área de exibição se torna grande o suficiente, o marcador é recriado. Consulte a seção Marcadores de posição.

O empilhamento da atividade é possível porque o WindowManager organiza as atividades na ordem z no painel secundário acima das atividades no painel principal.

Várias atividades no painel secundário

A atividade B inicia a atividade C sem outras flags de intent:

Divisão de atividade contendo as atividades A, B e C com a C empilhada sobre a
          B.

Resultando na seguinte ordem z de atividades na mesma tarefa:

Pilha de atividade secundária contendo a atividade C sobre a B.
          A pilha secundária é empilhada sobre a pilha de atividades primária
          que contém a atividade A.

Portanto, em uma janela de tarefas menor, o aplicativo diminui para uma única atividade com C no topo da pilha:

Janela pequena mostrando apenas a atividade C.

Voltar na janela menor é navegar pelas atividades empilhadas umas sobre as outras.

Se a configuração da janela de tarefas for restaurada para um tamanho maior que possa acomodar vários painéis, as atividades são exibidas lado a lado.

Divisões empilhadas

A atividade B inicia a atividade C na lateral e muda a divisão ao lado:

Janela de tarefas mostrando as atividades A e B, depois as atividades B e C.

O resultado é a seguinte ordem z das atividades na mesma tarefa:

As atividades A, B e C em uma única pilha. As atividades são empilhadas
          na seguinte ordem, de cima para baixo: C, B, A.

Em uma janela de tarefas menor, o aplicativo diminui para uma única atividade com C na parte de cima:

Janela pequena mostrando apenas a atividade C.

Orientação de retrato fixo

A configuração do manifesto android:screenOrientation permite que os apps restrinjam atividades para a orientação retrato ou paisagem. Para melhorar a experiência do usuário em dispositivos de tela grande, como tablets e dobráveis, os fabricantes de dispositivos (OEMs) podem ignorar as solicitações de orientação da tela e colocar o app com efeito letterbox em orientação de retrato em telas no modo paisagem ou vice-versa.

Figura 12. Atividades com efeito letterbox: retrato fixo em um dispositivo no modo paisagem (à esquerda) e paisagem fixa em dispositivo no modo retrato (à direita).

Da mesma forma, quando a incorporação de atividades está ativada, os OEMs podem personalizar os dispositivos para atividades com efeito letterbox no modo retrato fixo na orientação paisagem em telas grandes (largura ≥ 600 dp). Quando uma atividade no modo retrato fixo inicia uma segunda, o dispositivo pode exibir as duas atividades lado a lado em uma tela de dois painéis.

Figura 13. A atividade A no modo retrato fixo inicia a atividade B ao lado dela.

Sempre adicione a android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED ao arquivo de manifesto do aplicativo para informar aos dispositivos que seu aplicativo é compatível Incorporação de atividades (consulte a Configuração de divisão ). Os dispositivos personalizados pelo OEM podem determinar se as atividades no modo retrato fixo vão ter efeito letterbox.

Configurar divisões

As regras de divisão configuram divisões de atividade. Você define regras de divisão em um XML arquivo de configuração ou criando a API Jetpack WindowManager. chamadas.

Em ambos os casos, seu app precisa acessar a biblioteca WindowManager e informar no sistema em que o app implementou a incorporação de atividades.

Faça o seguinte:

  1. Adicionar a dependência da biblioteca WindowManager mais recente ao nível do módulo do app build.gradle, por exemplo:

    implementation 'androidx.window:window:1.1.0-beta02'

    A biblioteca WindowManager fornece todos os componentes necessários para a atividade e incorporações.

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

    Adicionar a propriedade android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED para o <application> do arquivo de manifesto do app e configure o como verdadeiro, por exemplo:

    <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>
    

    Na versão 1.1.0-alpha06 do WindowManager e mais recentes, as divisões de incorporação de atividades são desativadas, a menos que a propriedade seja adicionada ao manifesto e definida como verdadeira.

    Além disso, os fabricantes de dispositivos usam essa configuração para ativar recursos personalizados para apps com suporte à incorporação de atividades. Por exemplo, os dispositivos podem usar o efeito letterbox em uma atividade somente retrato em telas no modo paisagem para orientar a atividade na transição para um layout de dois painéis quando uma segunda atividade é iniciada. Consulte Orientação fixa no modo retrato.

Configuração de XML

Para criar uma implementação de incorporação de atividades baseada em XML, conclua o seguintes etapas:

  1. Crie um arquivo de recurso XML que faça o seguinte:

    • Definir atividades que compartilham uma divisão.
    • Configurar as opções de divisão.
    • Cria um marcador de posição para o contêiner secundário de dividir quando o conteúdo não está disponível
    • Especificar as atividades que nunca devem fazer parte de uma divisão.

    Exemplo:

    <!-- main_split_config.xml -->
    
    <resources
        xmlns:window="http://schemas.android.com/apk/res-auto">
    
        <!-- Define a split for the named activities. -->
        <SplitPairRule
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:finishPrimaryWithSecondary="never"
            window:finishSecondaryWithPrimary="always"
            window:clearTop="false">
            <SplitPairFilter
                window:primaryActivityName=".ListActivity"
                window:secondaryActivityName=".DetailActivity"/>
        </SplitPairRule>
    
        <!-- Specify a placeholder for the secondary container when content is
             not available. -->
        <SplitPlaceholderRule
            window:placeholderActivityName=".PlaceholderActivity"
            window:splitRatio="0.33"
            window:splitLayoutDirection="locale"
            window:splitMinWidthDp="840"
            window:splitMaxAspectRatioInPortrait="alwaysAllow"
            window:stickyPlaceholder="false">
            <ActivityFilter
                window:activityName=".ListActivity"/>
        </SplitPlaceholderRule>
    
        <!-- Define activities that should never be part of a split. Note: Takes
             precedence over other split rules for the activity named in the
             rule. -->
        <ActivityRule
            window:alwaysExpand="true">
            <ActivityFilter
                window:activityName=".ExpandedActivity"/>
        </ActivityRule>
    
    </resources>
    
  2. Crie um inicializador.

    O componente RuleController da WindowManager analisa o arquivo de configuração XML e disponibiliza as regras para o sistema. Um Jetpack A biblioteca Startup Initializer disponibiliza o arquivo XML para RuleController na inicialização do app para que as regras entrem em vigor atividades sejam iniciadas.

    Para criar um inicializador, faça o seguinte:

    1. Adicione a dependência da biblioteca Jetpack Startup mais recente ao arquivo build.gradle no nível do módulo, por exemplo:

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

    2. Crie uma classe que implemente a interface Initializer.

      O inicializador disponibiliza as regras de divisão para RuleController ao Transmitir o ID do arquivo de configuração XML (main_split_config.xml) para o método RuleController.parseRules().

      Kotlin

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

      Java

      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();
           }
      }
  3. Crie um provedor de conteúdo para as definições da regra.

    Adicionar androidx.startup.InitializationProvider ao arquivo de manifesto do app como um <provider>. Inclua uma referência à implementação do inicializador RuleController, 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>
    

    O InitializationProvider descobre e inicializa o SplitInitializer antes o método onCreate() do app é chamado. Como resultado, as regras de divisão efeito quando a atividade principal do app é iniciada.

API WindowManager

Você pode implementar a incorporação de atividades de forma programática com algumas APIs chamadas. Faça as chamadas no método onCreate() de uma subclasse de Application para garantir que as regras entrem em vigor antes de qualquer atividade. lançamento.

Para criar uma divisão de atividade de forma programática, faça o seguinte:

  1. Crie uma regra de divisão:

    1. Crie um SplitPairFilter que identifique as atividades que compartilham a divisão:

      Kotlin

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

      Java

      SplitPairFilter splitPairFilter = new SplitPairFilter(
         new ComponentName(this, ListActivity.class),
         new ComponentName(this, DetailActivity.class),
         null
      );
    2. Adicione o filtro a um conjunto:

      Kotlin

      val filterSet = setOf(splitPairFilter)

      Java

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

      Kotlin

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

      Java

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

      O 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 área de exibição disponível alocada para o contêiner principal. O contêiner secundário ocupa o restante da área de exibição disponível.
      • setLayoutDirection(): especifica como os contêineres de atividade são dispostos em relação aos outros, sendo o contêiner principal primeiro.
    4. Crie um objeto SplitPairRule:

      Kotlin

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

      Java

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

      O 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.
      • setDefaultSplitAttributes(): aplica atributos de layout ao regra de firewall.
      • setMinWidthDp(): define a largura mínima de exibição (em pixels 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, seja qual for a orientação do dispositivo.
      • setMaxAspectRatioInPortrait(): define a proporção máxima de exibição (altura:largura) na orientação retrato em que as divisões de atividade são mostradas. Se a proporção de uma tela no modo retrato ultrapassar o máximo permitido, as divisões serão desativadas independente da largura da tela. Observação:o valor padrão é 1,4, que é resulta em atividades ocupando toda a janela de tarefas no modo retrato orientação na maioria dos tablets. Consulte também SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT e setMaxAspectRatioInLandscape(). O valor padrão para paisagem é ALWAYS_ALLOW.
      • setFinishPrimaryWithSecondary(): define 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 Finalizar atividades.
      • setFinishSecondaryWithPrimary(): define como a conclusão de todos os processos. atividades no contêiner principal afeta as atividades no contêiner secundário. ALWAYS indica que o sistema sempre precisa concluir as atividades no contêiner secundário quando todas as atividades no contêiner principal forem concluídas. Consulte Finalizar atividades.
      • setClearTop(): especifica se todas as atividades no contêiner secundário são concluídas quando uma nova atividade é iniciada no contêiner. Um valor false especifica que novas atividades são empilhados sobre as atividades que já estão no contêiner secundário.
    5. Acesse a instância de singleton do RuleController da WindowManager. e adicione a regra:

      Kotlin

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

      Java

        RuleController ruleController = RuleController.getInstance(this);
        ruleController.addRule(splitPairRule);
        
  2. Crie um marcador de posição para o contêiner secundário quando conteúdo indisponível:

    1. Crie um ActivityFilter que identifique a atividade com que o marcador de posição compartilha uma divisão de janela de tarefas:

      Kotlin

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

      Java

      ActivityFilter placeholderActivityFilter = new ActivityFilter(
          new ComponentName(this, ListActivity.class),
          null
      );
    2. Adicione o filtro a um conjunto:

      Kotlin

      val placeholderActivityFilterSet = setOf(placeholderActivityFilter)

      Java

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

      Kotlin

      val splitPlaceholderRule = SplitPlaceholderRule.Builder(
            placeholderActivityFilterSet,
            Intent(context, PlaceholderActivity::class.java)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build()

      Java

      SplitPlaceholderRule splitPlaceholderRule = new SplitPlaceholderRule.Builder(
            placeholderActivityFilterSet,
            new Intent(context, PlaceholderActivity.class)
          ).setDefaultSplitAttributes(splitAttributes)
           .setMinWidthDp(840)
           .setMinSmallestWidthDp(600)
           .setMaxAspectRatioInPortrait(EmbeddingAspectRatio.ratio(1.5f))
           .setFinishPrimaryWithPlaceholder(SplitRule.FinishBehavior.ALWAYS)
           .setSticky(false)
           .build();

      O 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 o início da atividade do marcador de posição.
      • setDefaultSplitAttributes(): aplica atributos de layout à regra.
      • setMinWidthDp(): define a largura mínima de exibição (em pixels 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, seja qual for a orientação do dispositivo.
      • setMaxAspectRatioInPortrait(): Define a proporção máxima de exibição (altura:largura) no modo retrato orientação para a qual as divisões de atividade são mostradas. Observação: o valor padrão é 1,4, o que resulta em atividades preenchendo a janela de tarefas na orientação retrato na maioria dos tablets. Consulte também SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT e setMaxAspectRatioInLandscape(). O valor padrão para o modo paisagem é ALWAYS_ALLOW.
      • 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 deve sempre finalizar as atividades no contêiner principal quando o marcador é concluído (consulte Finalizar atividades).
      • setSticky(): determina se a atividade do marcador de posição aparece na parte de cima da pilha em telas pequenas quando o marcador é mostrado pela primeira vez em uma divisão com largura mínima suficiente.
    4. Adicione a regra ao RuleController do WindowManager:

      Kotlin

      ruleController.addRule(splitPlaceholderRule)

      Java

      ruleController.addRule(splitPlaceholderRule);
  3. Especifique as atividades que nunca devem fazer parte de uma divisão:

    1. Crie um ActivityFilter que identifique uma atividade que precisa sempre ocupam toda a área de exibição da tarefa:

      Kotlin

      val expandedActivityFilter = ActivityFilter(
        ComponentName(this, ExpandedActivity::class.java),
        null
      )

      Java

      ActivityFilter expandedActivityFilter = new ActivityFilter(
        new ComponentName(this, ExpandedActivity.class),
        null
      );
    2. Adicione o filtro a um conjunto:

      Kotlin

      val expandedActivityFilterSet = setOf(expandedActivityFilter)

      Java

      Set<ActivityFilter> expandedActivityFilterSet = new HashSet<>();
      expandedActivityFilterSet.add(expandedActivityFilter);
    3. Crie uma interface ActivityRule:

      Kotlin

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

      Java

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

      O ActivityRule.Builder cria e configura a regra:

      • expandedActivityFilterSet: contém filtros de atividade que determinar quando aplicar a regra identificando as atividades que você que queremos excluir das divisões.
      • setAlwaysExpand(): especifica se a atividade vai ser preenchida. toda a janela de tarefas.
    4. Adicione a regra ao RuleController do WindowManager:

      Kotlin

      ruleController.addRule(activityRule)

      Java

      ruleController.addRule(activityRule);

Incorporação entre aplicativos

No Android 13 (nível 33 da API) e versões mais recentes, os apps podem incorporar atividades de outros apps. A incorporação de atividades entre aplicativos, ou entre UIDs, permite a integração visual de atividades de vários apps Android. O sistema exibe uma atividade do aplicativo host e uma atividade incorporada do outro app na tela lado a lado ou nas partes de cima e de baixo, como em um único app a incorporação de atividades.

Por exemplo, o app Configurações pode incorporar a atividade do seletor de plano de fundo da o app Plano de fundo:

Figura 14. App Configurações (menu à esquerda) com seletor de plano de fundo como atividade incorporada (à direita).

Modelo de confiança

Os processos do host que incorporam atividades de outros aplicativos podem redefinir a apresentação das atividades incorporadas, incluindo tamanho, posição, corte e transparência Hosts maliciosos podem usar esse recurso para enganar os usuários e criar clickjacking (link em inglês) ou outros ataques de correção da interface.

Para evitar o uso indevido da incorporação de atividades entre apps, o Android exige que os apps ativem esse recurso. Os apps podem designar hosts como confiáveis ou não confiáveis.

Hosts confiáveis

Para permitir que outros aplicativos incorporem e controlem totalmente a apresentação de atividades do seu app, especifique o certificado SHA-256 do aplicativo host no atributo android:knownActivityEmbeddingCerts dos elementos <activity> ou <application> do arquivo de manifesto do app.

Defina o valor de android:knownActivityEmbeddingCerts como uma string:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@string/known_host_certificate_digest"
    ... />

Se preferir, para especificar vários certificados, use uma matriz de strings:

<activity
    android:name=".MyEmbeddableActivity"
    android:knownActivityEmbeddingCerts="@array/known_host_certificate_digests"
    ... />

que faz referência a um recurso como este:

<resources>
    <string-array name="known_host_certificate_digests">
      <item>cert1</item>
      <item>cert2</item>
      ...
    </string-array>
</resources>

Os proprietários de apps podem receber um resumo de certificado SHA executando a tarefa signingReport do Gradle. A síntese do certificado é a impressão digital SHA-256 sem os dois-pontos de separação. Para mais informações, consulte Executar um relatório de assinatura e Como autenticar seu cliente.

Hosts não confiáveis

Para permitir que qualquer app incorpore as atividades do seu app e controle a apresentação dele, especifique o atributo android:allowUntrustedActivityEmbedding nos elementos <activity> ou <application> no manifesto do app, por exemplo:

<activity
    android:name=".MyEmbeddableActivity"
    android:allowUntrustedActivityEmbedding="true"
    ... />

O valor padrão do atributo é falso, o que impede a incorporação de atividades entre apps.

Autenticação personalizada

Para reduzir os riscos da incorporação não confiável de atividades, crie um mecanismo de autenticação que verifica a identidade do host. Se você conhecer o host certificados, use a biblioteca androidx.security.app.authenticator para se autenticar. Se o host for autenticado depois de incorporar sua atividade, você vai poder mostrar o conteúdo real. Caso contrário, informe ao usuário que a ação não foi permitida e bloqueie o conteúdo.

Use o método ActivityEmbeddingController#isActivityEmbedded() da biblioteca WindowManager do Jetpack para verificar se um host está incorporando sua atividade, por exemplo:

Kotlin

fun isActivityEmbedded(activity: Activity): Boolean {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity)
}

Java

boolean isActivityEmbedded(Activity activity) {
    return ActivityEmbeddingController.getInstance(this).isActivityEmbedded(activity);
}

Restrição de tamanho mínimo

O sistema Android aplica a altura e a largura mínimas especificadas no elemento <layout> do manifesto do app a atividades incorporadas. Se um aplicativo não especificarem altura e largura mínimas, os valores padrão do sistema serão aplicados (sw220dp).

Se o host tentar redimensionar o contêiner incorporado para um tamanho menor que o mínimo, ele vai ser expandido para ocupar todos os limites da tarefa.

<activity-alias>

Para que a incorporação de atividades confiáveis ou não confiáveis funcione com o elemento <activity-alias>, android:knownActivityEmbeddingCerts ou android:allowUntrustedActivityEmbedding precisa ser aplicado à atividade de destino em vez do alias. A política que verifica a segurança no servidor do sistema com base nas sinalizações definidas no destino, não no alias.

Hospedar aplicativo

Os aplicativos host implementam a incorporação de atividades entre apps da mesma maneira que implementam a incorporação de atividades em apps únicos. Os objetos SplitPairRule e SplitPairFilter ou ActivityRule e ActivityFilter especificam atividades incorporadas e divisões da janela de tarefas. As regras de divisão são definidas estaticamente em XML ou durante a execução usando chamadas da API WindowManager do Jetpack.

Se um aplicativo host tentar incorporar uma atividade que não tenha ativado a incorporação entre apps, ela vai ocupar todos os limites da tarefa. Como resultado, os aplicativos host precisam saber se as atividades de destino permitem a incorporação entre apps.

Se uma atividade incorporada iniciar uma nova atividade na mesma tarefa que não ativou a incorporação entre apps, ela vai ocupar todos os limites da tarefa em vez de sobrepor a atividade no contêiner incorporado.

Um aplicativo host pode incorporar as próprias atividades sem restrições, desde que as atividades são iniciadas na mesma tarefa.

Exemplos de divisão

Dividir da tela cheia

Figura 15. A atividade A inicia a atividade B ao lado dela.

Nenhuma refatoração é necessária. É possível definir a configuração da divisão de forma estática ou no momento da execução e, em seguida, chamar Context#startActivity() sem outros parâmetros.

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Dividir por padrão

Quando a página de destino de um aplicativo é projetada para ser dividida em dois contêineres em telas grandes, a experiência do usuário é melhor quando ambas as atividades são criadas e apresentadas de forma simultânea. No entanto, o conteúdo pode não estar disponível para o contêiner secundário da divisão até que o usuário interaja com a atividade no contêiner principal, por exemplo, quando o usuário seleciona um item em um menu de navegação. Uma atividade de marcador de posição pode preencher o espaço vazio até que o conteúdo possa ser mostrado no contêiner secundário da divisão. Consulte a seção Marcadores de posição.

Figura 16. Divisão criada pela abertura de duas atividades ao mesmo tempo. Uma atividade é um marcador de posição.

Para criar uma divisão com um marcador, crie um e o associe à atividade principal:

<SplitPlaceholderRule
    window:placeholderActivityName=".PlaceholderActivity">
    <ActivityFilter
        window:activityName=".MainActivity"/>
</SplitPlaceholderRule>

Quando um app recebe uma intent, a atividade de destino pode ser mostrada como a parte secundária de uma divisão de atividades. Por exemplo, um pedido para mostrar uma tela de detalhes com informações sobre um item de uma lista; Em telas pequenas, os detalhes são mostrados na janela de tarefas completa. Em dispositivos maiores, ao lado da lista.

Figura 17. A atividade de detalhes de links diretos aparece sozinha em uma tela pequena e junto com uma atividade de lista em uma tela grande.

O pedido de inicialização precisa ser encaminhado para a atividade principal, e a atividade de detalhes de destino precisa ser iniciada em uma divisão. O sistema escolhe automaticamente a apresentação correta (empilhada ou lado a lado) com base na largura de tela disponível.

Kotlin

override fun onCreate(savedInstanceState Bundle?) {
    . . .
    RuleController.getInstance(this)
        .addRule(SplitPairRule.Builder(filterSet).build())
    startActivity(Intent(this, DetailActivity::class.java))
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    . . .
    RuleController.getInstance(this)
        .addRule(new SplitPairRule.Builder(filterSet).build());
    startActivity(new Intent(this, DetailActivity.class));
}

O destino do link direto pode ser a única atividade que deve estar disponível para o usuário na backstack, então recomendamos não dispensar a atividade de detalhes e deixando apenas a atividade principal:

Tela grande com atividade de lista e detalhes lado a lado.
          A navegação de retorno não consegue dispensar a atividade de detalhes e deixar a atividade de lista
          na tela.

Tela pequena apenas com atividade de detalhes. A navegação de retorno não consegue
          dispensar a atividade de detalhes e revelar a atividade da lista.

Em vez disso, é possível finalizar as duas atividades ao mesmo tempo usando o atributo finishPrimaryWithSecondary:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".ListActivity"
        window:secondaryActivityName=".DetailActivity"/>
</SplitPairRule>

Consulte a seção Atributos de configuração.

Várias atividades em contêineres divididos

O empilhamento de várias atividades em um contêiner dividido permite que os usuários acessem conteúdo localizado em partes diferentes. Por exemplo, com uma divisão de detalhes e listas, o usuário pode precisar entrar em uma seção de subdetalhes, mas mantenha a atividade principal no lugar:

Figura 18. Atividade aberta no lugar do painel secundário da janela de tarefas.

Kotlin

class DetailActivity {
    . . .
    fun onOpenSubDetail() {
      startActivity(Intent(this, SubDetailActivity::class.java))
    }
}

Java

public class DetailActivity {
    . . .
    void onOpenSubDetail() {
        startActivity(new Intent(this, SubDetailActivity.class));
    }
}

A atividade de subdetalhes é posicionada acima da atividade detalhada, que fica ocultada:

O usuário pode voltar ao nível de detalhes anterior navegando de volta pela pilha:

Figura 19. Atividade removida da parte de cima da pilha.

Empilhar atividades umas sobre as outras é o comportamento padrão quando elas são iniciados de uma atividade no mesmo contêiner secundário. Atividades iniciadas do contêiner principal em uma divisão ativa também acabam no contêiner secundário, na parte de cima da pilha de atividades.

Atividades em uma nova tarefa

Quando as atividades em uma janela de tarefas dividida iniciam atividades em uma nova tarefa, a nova tarefa é separada da tarefa que inclui a divisão e preenche a janela toda. A tela "Recentes" mostra duas tarefas: uma na divisão e a nova tarefa.

Figura 20. Inicie a atividade C em uma nova tarefa da atividade B.

Substituir atividades

As atividades podem ser substituídas na pilha de contêineres secundária. por exemplo, quando a atividade principal é usada para navegação de nível superior e a atividade secundária é um destino selecionado. Cada seleção da navegação de nível superior deve iniciar uma nova atividade no contêiner secundário e remover a atividade; ou atividades que estavam lá anteriormente.

Figura 21. A atividade de navegação de nível superior no painel principal substitui as atividades de destino no painel secundário.

Se o app não finalizar a atividade no contêiner secundário quando a seleção de navegação mudar, a navegação de retorno poderá ficar confusa quando a divisão for recolhida (quando o dispositivo estiver dobrado). Por exemplo, se você tiver um menu na painel primário e telas A e B empilhadas no painel secundário, quando o usuário dobra o smartphone, B fica em cima de A e A está em cima do menu. Ao navegar de volta de B, o usuário vê A em vez do menu.

Nesses casos, é necessário remover a tela A da backstack.

O comportamento padrão ao iniciar na lateral em um novo contêiner sobre uma divisão atual é colocar os novos contêineres secundários na parte superior e manter os antigos na backstack. É possível configurar as divisões para limpar os contêineres secundários anteriores com clearTop e iniciar novas atividades normalmente.

<SplitPairRule
    window:clearTop="true">
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenA"/>
    <SplitPairFilter
        window:primaryActivityName=".Menu"
        window:secondaryActivityName=".ScreenB"/>
</SplitPairRule>

Kotlin

class MenuActivity {
    . . .
    fun onMenuItemSelected(selectedMenuItem: Int) {
        startActivity(Intent(this, classForItem(selectedMenuItem)))
    }
}

Java

public class MenuActivity {
    . . .
    void onMenuItemSelected(int selectedMenuItem) {
        startActivity(new Intent(this, classForItem(selectedMenuItem)));
    }
}

Como alternativa, use a mesma atividade secundária e envie novas intents da atividade principal (menu) que sejam resolvidas para a mesma instância, mas acionem uma atualização de estado ou de interface no contêiner secundário.

Várias divisões

Os apps podem oferecer navegação profunda em vários níveis iniciando outras atividades ao lado.

Quando uma atividade em um contêiner secundário inicia uma nova atividade ao lado, uma uma nova divisão é criada sobre a divisão existente.

Figura 22. A atividade B inicia a atividade C na lateral.

A backstack contém todas as atividades que foram abertas anteriormente para que os usuários possam navegar para a divisão A/B após a finalização de C.

Atividades A, B e C em uma pilha. As atividades são empilhadas na
          seguinte ordem, de cima para baixo: C, B, A.

Para criar uma nova divisão, inicie a nova atividade na lateral pelo contêiner secundário existente. Declarar as configurações para as divisões A/B e B/C e iniciar a atividade C normalmente em B:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
    <SplitPairFilter
        window:primaryActivityName=".B"
        window:secondaryActivityName=".C"/>
</SplitPairRule>

Kotlin

class B {
    . . .
    fun onOpenC() {
        startActivity(Intent(this, C::class.java))
    }
}

Java

public class B {
    . . .
    void onOpenC() {
        startActivity(new Intent(this, C.class));
    }
}

Reagir a mudanças de estado da divisão

Atividades diferentes em um app podem ter elementos da interface que executam a mesma função. Por exemplo, um controle que abre uma janela contendo as configurações da conta.

Figura 23. Atividades diferentes com elementos da interface funcionalmente idênticos.

Se duas atividades que têm um elemento da interface em comum estão divididas, é redundante e pode ser confuso mostrar o elemento nas duas atividades.

Figura 24. Elementos de IU duplicados na divisão de atividade.

Para saber quando as atividades estão em uma divisão, verifique o fluxo de SplitController.splitInfoList ou registre um listener com SplitControllerCallbackAdapter e confira as mudanças no estado da divisão. Em seguida, ajuste a interface de forma adequada:

Kotlin

val layout = layoutInflater.inflate(R.layout.activity_main, null)
val view = layout.findViewById<View>(R.id.infoButton)
lifecycleScope.launch {
    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
        splitController.splitInfoList(this@SplitDeviceActivity) // The activity instance.
            .collect { list ->
                view.visibility = if (list.isEmpty()) View.VISIBLE else View.GONE
            }
    }
}

Java

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    . . .
    new SplitControllerCallbackAdapter(SplitController.getInstance(this))
        .addSplitListener(
            this,
            Runnable::run,
            splitInfoList -> {
                View layout = getLayoutInflater().inflate(R.layout.activity_main, null);
                layout.findViewById(R.id.infoButton).setVisibility(
                    splitInfoList.isEmpty() ? View.VISIBLE : View.GONE);
            });
}

As corrotinas podem ser iniciadas em qualquer estado do ciclo de vida, mas normalmente são iniciadas em O estado STARTED (link em inglês) para economizar recursos. Consulte Usar corrotinas do Kotlin com com reconhecimento de ciclo de vida para mais informações).

É possível fazer callbacks em qualquer estado do ciclo de vida, inclusive quando uma atividade é interrompida. Os listeners geralmente precisam ficar registrados em onStart() e não registrados em onStop().

Tela cheia modal

Algumas atividades impedem que os usuários interajam com o aplicativo até que uma ação especificada seja realizada. Por exemplo, uma atividade de tela de login, uma tela de confirmação de política ou uma mensagem de erro. As atividades modais precisam ser impedidas de aparecer em uma divisão.

Uma atividade pode ser forçada a sempre preencher a janela de tarefas usando a configuração de expansão:

<ActivityRule
    window:alwaysExpand="true">
    <ActivityFilter
        window:activityName=".FullWidthActivity"/>
</ActivityRule>

Finalizar atividades

Os usuários podem finalizar atividades em qualquer lado da divisão deslizando a partir da borda da tela:

Figura 25. Faça o gesto de deslizar para finalizar a atividade B.
Figura 26. Faça o gesto de deslizar para finalizar a atividade A.

Se o dispositivo estiver configurado para usar o botão "Voltar" em vez da navegação por gestos, a entrada é enviada para a atividade em foco, ou seja, a atividade que foi tocada ou iniciada por último.

O efeito que a finalização de todas as atividades em um contêiner tem sobre os elementos opostos depende da configuração de divisão.

Atributos de configuração

É possível especificar atributos de regra de par dividido para configurar como a conclusão de todas as atividades em um lado da divisão afeta as atividades do outro lado da divisão. Os atributos são:

  • window:finishPrimaryWithSecondary — Como a conclusão de todas as atividades em o contêiner secundário afeta as atividades no contêiner principal
  • window:finishSecondaryWithPrimary — Como a conclusão de todas as atividades em o contêiner principal afeta as atividades no contêiner secundário

Os valores possíveis dos atributos incluem:

  • always: sempre conclui as atividades no contêiner associado
  • never: nunca conclui as atividades no contêiner associado
  • adjacent: conclui as atividades no contêiner associado quando os dois contêineres são exibidos adjacentes, mas não quando os dois contêineres são empilhados

Exemplo:

<SplitPairRule
    &lt;!-- Do not finish primary container activities when all secondary container activities finish. --&gt;
    window:finishPrimaryWithSecondary="never"
    &lt;!-- Finish secondary container activities when all primary container activities finish. --&gt;
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Configuração padrão

Quando todas as atividades em um contêiner de uma divisão terminarem, o contêiner restante vai ocupar a janela inteira:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Divisão contendo as atividades A e B. A atividade A é concluída, deixando B
          ocupar a janela inteira.

Divisão contendo as atividades A e B. A atividade B é concluída, deixando A
          ocupar a janela inteira.

Finalizar atividades em conjunto

Finalize as atividades no contêiner principal de forma automática quando todas as atividades no contêiner secundário forem concluídas:

<SplitPairRule
    window:finishPrimaryWithSecondary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Divisão contendo as atividades A e B. A conclusão de B, que também
          finaliza A, deixa a janela de tarefas vazia.

Divisão contendo as atividades A e B. A atividade A é concluída, deixando a atividade B sozinha
          na janela de tarefas.

Finalize as atividades no contêiner secundário automaticamente quando todas as atividades no contêiner principal forem concluídas:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Divisão contendo as atividades A e B. A conclusão de A, que também
          finaliza B, deixa a janela de tarefas vazia.

Divisão contendo as atividades A e B. A atividade B é concluída, deixando a atividade A sozinha
          na janela de tarefas.

Finalizar atividades em conjunto quando todas as atividades na atividade principal ou fim do contêiner secundário:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Divisão contendo as atividades A e B. A conclusão de A, que também
          finaliza B, deixa a janela de tarefas vazia.

Divisão contendo as atividades A e B. A conclusão de B, que também
          finaliza A, deixa a janela de tarefas vazia.

Finalizar várias atividades em contêineres

Se várias atividades forem empilhadas em um contêiner dividido, finalizar uma atividade na parte de baixo da pilha não conclui automaticamente as atividades na parte de cima.

Por exemplo, se duas atividades estiverem no contêiner secundário, C em cima de B:

A pilha de atividades secundária contendo a atividade C empilhada sobre B
          é colocada sobre a pilha de atividades primária que contém a atividade
          A.

e a configuração da divisão é definida pela configuração das atividades A e B:

<SplitPairRule>
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

A conclusão da atividade de cima mantém a divisão.

Divisão com a atividade A no contêiner principal e as atividades B e C no
          secundário, a atividade C empilhada sobre B. C é finalizada, deixando A e B na
          divisão de atividades.

Concluir a atividade de baixo (raiz) do contêiner secundário não remove as atividades acima dele, e assim, retém a divisão.

Divisão com a atividade A no contêiner principal e as atividades B e C no
          secundário, a atividade C empilhada sobre B. A atividade B é concluída e deixa A e C na
          divisão de atividades.

Todas as outras regras para finalizar as atividades em conjunto, por exemplo, a conclusão da atividade secundária com a primária, também são executadas:

<SplitPairRule
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Divisão com a atividade A no contêiner principal, e as atividades B e C no
          contêiner secundário, a atividade C empilhada sobre B. A é concluída, também
          finalizando B e C.

E quando a divisão estiver configurada para finalizar as partes primária e secundária juntas:

<SplitPairRule
    window:finishPrimaryWithSecondary="always"
    window:finishSecondaryWithPrimary="always">
    <SplitPairFilter
        window:primaryActivityName=".A"
        window:secondaryActivityName=".B"/>
</SplitPairRule>

Divisão com a atividade A no contêiner principal e as atividades B e C no
          secundário, a atividade C empilhada sobre B. C é finalizada, deixando A e B na
          divisão de atividades.

Divisão com a atividade A no contêiner principal e as atividades B e C no
          secundário, a atividade C empilhada sobre B. A atividade B é concluída e deixa A e C na
          divisão de atividades.

Divisão com a atividade A no contêiner principal e as atividades B e C no
          secundário, a atividade C empilhada sobre B. A é concluída, também finalizando B e
          C.

Mudar propriedades de divisão no momento da execução

Não é possível mudar as propriedades de uma divisão ativa e visível. Mudar as regras de divisão afeta a inicialização de outras atividades e novos contêineres, mas não as divisões existentes e ativas.

Para mudar as propriedades de divisões ativas, conclua a atividade lateral ou as atividades na divisão e reinicie para a lateral novamente com uma nova configuração.

Propriedades dinâmicas de divisão

O Android 15 (nível 35 da API) e versões mais recentes, com suporte do WindowManager 1.4 do Jetpack e versões mais recentes, oferecem recursos dinâmicos que permitem a configurável de divisões de incorporação de atividades, incluindo:

  • Expansão de painel:um divisor interativo e arrastável permite que os usuários redimensionar os painéis em uma apresentação dividida.
  • Fixação de atividades:os usuários podem fixar o conteúdo em um contêiner. isolar a navegação no contêiner da navegação no outro contêiner;
  • Escurecimento da caixa de diálogo em tela cheia:ao exibir uma caixa de diálogo, os apps podem especificar se deve escurecer toda a janela de tarefas ou apenas o contêiner que abriu a caixa de diálogo.

Expansão do painel

A expansão do painel permite que os usuários ajustem a quantidade de espaço de tela alocada para as duas atividades em um layout de painel duplo.

Para personalizar a aparência do divisor de janelas e definir a do intervalo arrastável, faça o seguinte:

  1. Crie uma instância de DividerAttributes

  2. Personalize os atributos do divisor:

    • color:a cor do separador do painel arrastável.

    • widthDp: a largura do separador do painel que pode ser arrastado. Definir como WIDTH_SYSTEM_DEFAULT para permitir que o sistema determine o divisor largura.

    • Intervalo de arrastar:a porcentagem mínima da tela que cada painel pode ocuparem. Pode variar de 0,33 a 0,66. Defina como DRAG_RANGE_SYSTEM_DEFAULT para permitir que o sistema determine o intervalo de arrasto.

Kotlin

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

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

Fixação de atividades

A fixação de atividades permite que os usuários fixem uma das janelas divididas para que a atividade permaneça como está enquanto os usuários navegam na outra janela. A fixação de atividades oferece uma experiência de multitarefas aprimorada.

Para ativar a fixação de atividades no app, faça o seguinte:

  1. Adicione um botão ao arquivo de layout da atividade que você quer fixar, por exemplo, a atividade de detalhes de um layout de detalhes e listas:

    <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>
    
  2. No método onCreate() da atividade, defina um listener "onclick" na botão:

    Kotlin

    pinButton = findViewById(R.id.pinButton)
    pinButton.setOnClickListener {
        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)
    }

    Java

    Button pinButton = findViewById(R.id.pinButton);
    pinButton.setOnClickListener( (view) => {
        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(getTaskId(), pinSplitRule);
    });

Escurecer a tela cheia

As atividades normalmente escurecem as telas para chamar a atenção para uma caixa de diálogo. Na incorporação de atividades, os dois painéis da tela de painéis duplos precisam ficar escuros, não apenas o painel que contém a atividade que abriu a caixa de diálogo, para uma experiência de IU unificada.

Com a WindowManager 1.4 e versões mais recentes, toda a janela do app escurece por padrão quando uma A caixa de diálogo é aberta. Consulte EmbeddingConfiguration.DimAreaBehavior.ON_TASK.

Para escurecer apenas o contêiner da atividade que abriu a caixa de diálogo, use EmbeddingConfiguration.DimAreaBehavior.ON_ACTIVITY_STACK.

Extrair uma atividade de uma divisão para tela cheia

Crie uma nova configuração que mostre a tela cheia com a atividade lateral e, em seguida, reinicie a atividade com uma intent que é resolvida na mesma instância.

Conferir o suporte para divisão no momento da execução

A incorporação de atividades pode ser realizada no Android 12L (nível 32 da API) e versões mais recentes. No entanto, ela também está disponível em alguns dispositivos com versões anteriores da plataforma. Para verificar em para a disponibilidade do recurso, use o propriedade SplitController.splitSupportStatus ou SplitController.getSplitSupportStatus():

Kotlin

if (SplitController.getInstance(this).splitSupportStatus ==
     SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
     // Device supports split activity features.
}

Java

if (SplitController.getInstance(this).getSplitSupportStatus() ==
     SplitController.SplitSupportStatus.SPLIT_AVAILABLE) {
     // Device supports split activity features.
}

Se as divisões não estiverem disponíveis, as atividades serão iniciadas na parte de cima da pilha, seguindo o modelo de incorporação que não é de atividade.

Impedir a substituição do sistema

Os fabricantes de dispositivos Android (fabricantes de equipamentos originais, ou OEMs) podem implementar a incorporação de atividades como uma função do sistema do dispositivo. O sistema especifica regras de divisão para apps com várias atividades e substitui o comportamento de janelamento. A substituição do sistema força os apps com várias atividades a entrar em um modo de incorporação definido pelo sistema.

A incorporação de atividades do sistema pode melhorar a apresentação do app com o uso de vários painéis layouts, como list-detail, sem nenhuma mudança no app. No entanto, a incorporação de atividades do sistema também pode causar layouts de app incorretos, bugs ou entra em conflito com a incorporação de atividades implementada pelo app.

Seu app pode impedir ou permitir a incorporação de atividades do sistema definindo um property no arquivo de manifesto do app, por exemplo:

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

O nome da propriedade é definido no Jetpack WindowManager WindowProperties. objeto. Defina o valor como false se o app implementar a incorporação de atividades ou se você quiser impedir que o sistema use as regras da incorporação. Defina como true para permitir o uso da incorporação de atividades definida pelo sistema.

Limitações, restrições e advertências

  • Apenas o app host da tarefa, que é identificado como o proprietário da atividade raiz, pode organizar e incorporar outras atividades à tarefa. Se as atividades que oferecem suporte a divisões e incorporação forem executadas em uma tarefa que pertença a um aplicativo diferente, as incorporações e divisões não funcionarão para essas atividades.
  • As atividades só podem ser organizadas em uma tarefa. Iniciar uma atividade em uma nova tarefa sempre a coloca em uma nova janela expandida fora das divisões existentes.
  • Apenas atividades no mesmo processo podem ser organizadas e colocadas em uma divisão. O O callback SplitInfo só informa atividades que pertencem ao mesmo processo, já que não há como saber sobre atividades em diferentes processos de negócios.
  • Cada par ou regra de atividade única se aplica apenas para atividades iniciadas após a regra ser registrada. Atualmente, não há como atualizar as divisões existentes ou as propriedades visuais delas.
  • A configuração do filtro de par dividido precisa corresponder às intents usadas ao iniciar completamente as atividades. A correspondência ocorre no momento em que uma nova atividade é iniciada do processo do aplicativo. Por isso, ela pode não saber sobre os nomes de componentes que são resolvidos mais tarde no processo do sistema ao usar intents implícitas. Se o nome de um componente não for conhecido no momento da inicialização, um caractere curinga pode ser usado ("*/*") e a filtragem pode ser realizada com base na ação da intent.
  • No momento, não há como mover atividades entre contêineres ou para dentro e fora de divisões após serem criadas. As divisões são criadas pela biblioteca WindowManager apenas quando novas atividades com regras correspondentes são iniciadas, e as divisões são destruídas quando a última atividade em um contêiner de divisão é finalizada.
  • As atividades podem ser reiniciadas quando a configuração é alterada. Portanto, quando uma divisão é criada ou removida e os limites de atividades mudam, a atividade pode passar pela destruição completa da instância anterior e criação da nova. Como resultado, os desenvolvedores de apps precisam ter cuidado, por exemplo, com a inicialização de novas atividades usando callbacks do ciclo de vida.
  • Os dispositivos precisam incluir a interface de extensões de janela para oferecer suporte às atividades e incorporações. Quase todos os dispositivos de tela grande que executam o Android 12L (nível da API) 32) ou posteriores incluam a interface. No entanto, alguns dispositivos de tela grande que não são capazes de executar várias atividades não incluem a interface de extensões de janela. Se um dispositivo de tela grande não for compatível com o recurso de várias janelas , é possível que ele não ofereça suporte à incorporação de atividades.

Outros recursos