Processamento de alterações de configuração

Navegação e interface responsivas

Para oferecer a melhor experiência de navegação aos seus usuários, você precisa oferecer uma IU de navegação personalizada para a largura, altura e menor largura do dispositivo do usuário. Use uma barra de apps inferior, uma gaveta de navegação sempre presente ou recolhível, uma coluna ou talvez algo completamente novo com base no espaço de tela disponível e no estilo exclusivo do seu app.

exemplos de uma coluna, gavetas de navegação e uma barra de apps inferior
Figura 1. Exemplos de uma coluna, gavetas de navegação e uma barra inferior de apps.

O guia da arquitetura do produto do Material Design fornece contexto e considerações adicionais para criar uma IU responsiva, ou seja, uma IU que se adapta dinamicamente a alterações ambientais. Alguns exemplos de mudanças ambientais incluem ajustes na largura, altura, orientação e preferência de idioma do usuário. Essas propriedades ambientais são coletivamente denominadas configuração do dispositivo.

Quando uma ou mais dessas propriedades são alteradas no ambiente de execução, o SO Android responde destruindo e recriando as atividades e os fragmentos do app. Portanto, o melhor que você pode fazer para oferecer suporte a uma interface responsiva no Android é garantir que você esteja usando qualificadores de configuração de recursos quando apropriado e evitando o uso de tamanhos de layout codificados.

Como implementar a navegação global em uma interface responsiva

A implementação da navegação global como parte de uma IU responsiva começa com a atividade que está hospedando o gráfico de navegação. Para um exemplo prático, confira o codelab de navegação. O codelab usa um NavigationView para exibir o menu de navegação, como mostrado na Figura 2. Quando executado em um dispositivo que é renderizado com uma largura de pelo menos 960 dp, esse NavigationView está sempre na tela.

o codelab de navegação usa uma visualização de navegação que fica sempre visível
            quando a largura do dispositivo é pelo menos 960 dp.
Figura 2. O codelab de navegação usa um NavigationView para exibir o menu de navegação.

Outros tamanhos e orientações de dispositivo alternam dinamicamente entre DrawerLayout ou BottomNavigationView, conforme necessário.

Uma bottomnavigationview e um drawerlayout, usados para o menu de navegação
conforme necessário em layouts de dispositivo menores
Figura 3. O codelab de navegação usa BottomNavigationView e DrawerLayout para exibir o menu de navegação em dispositivos menores.

É possível implementar esse comportamento criando três layouts diferentes, em que cada um deles define os elementos de navegação e a hierarquia de visualização desejados com base na configuração atual do dispositivo.

A configuração a que cada layout se aplica é determinada pela estrutura de diretórios em que o arquivo de layout é colocado. Por exemplo, o arquivo de layout NavigationView é encontrado no diretório res/layout-w960dp.

<!-- res/layout-w960dp/navigation_activity.xml -->
<RelativeLayout
   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:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.example.android.codelabs.navigation.MainActivity">

   <com.google.android.material.navigation.NavigationView
       android:id="@+id/nav_view"
       android:layout_width="wrap_content"
       android:layout_height="match_parent"
       android:layout_alignParentStart="true"
       app:elevation="0dp"
       app:headerLayout="@layout/nav_view_header"
       app:menu="@menu/nav_drawer_menu" />

   <View
       android:layout_width="1dp"
       android:layout_height="match_parent"
       android:layout_toEndOf="@id/nav_view"
       android:background="?android:attr/listDivider" />

   <androidx.appcompat.widget.Toolbar
       android:id="@+id/toolbar"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:layout_alignParentTop="true"
       android:layout_toEndOf="@id/nav_view"
       android:background="@color/colorPrimary"
       android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />

   <androidx.fragment.app.FragmentContainerView
       android:id="@+id/my_nav_host_fragment"
       android:name="androidx.navigation.fragment.NavHostFragment"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:layout_below="@id/toolbar"
       android:layout_toEndOf="@id/nav_view"
       app:defaultNavHost="true"
       app:navGraph="@navigation/mobile_navigation" />
</RelativeLayout>

A visualização de navegação inferior é encontrada no diretório res/layout-h470dp:

<!-- res/layout-h470dp/navigation_activity.xml -->
<LinearLayout
   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:layout_width="match_parent"
   android:layout_height="match_parent"
   android:orientation="vertical"
   tools:context="com.example.android.codelabs.navigation.MainActivity">

   <androidx.appcompat.widget.Toolbar
       android:id="@+id/toolbar"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:background="@color/colorPrimary"
       android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />

   <androidx.fragment.app.FragmentContainerView
       android:id="@+id/my_nav_host_fragment"
       android:name="androidx.navigation.fragment.NavHostFragment"
       android:layout_width="match_parent"
       android:layout_height="0dp"
       android:layout_weight="1"
       app:defaultNavHost="true"
       app:navGraph="@navigation/mobile_navigation" />

   <com.google.android.material.bottomnavigation.BottomNavigationView
       android:id="@+id/bottom_nav_view"
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       app:menu="@menu/bottom_nav_menu" />
</LinearLayout>

O layout de gaveta é encontrado no diretório res/layout. Use esse diretório para layouts padrão sem qualificadores específicos de configuração:

<!-- res/layout/navigation_activity.xml -->
<androidx.drawerlayout.widget.DrawerLayout
   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/drawer_layout"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   tools:context="com.example.android.codelabs.navigation.MainActivity">

   <LinearLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:orientation="vertical">

       <androidx.appcompat.widget.Toolbar
           android:id="@+id/toolbar"
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:background="@color/colorPrimary"
           android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />

       <androidx.fragment.app.FragmentContainerView
           android:id="@+id/my_nav_host_fragment"
           android:name="androidx.navigation.fragment.NavHostFragment"
           android:layout_width="match_parent"
           android:layout_height="match_parent"
           app:defaultNavHost="true"
           app:navGraph="@navigation/mobile_navigation" />
   </LinearLayout>

   <com.google.android.material.navigation.NavigationView
       android:id="@+id/nav_view"
       android:layout_width="wrap_content"
       android:layout_height="match_parent"
       android:layout_gravity="start"
       app:menu="@menu/nav_drawer_menu" />
</androidx.drawerlayout.widget.DrawerLayout>

O Android segue uma ordem de precedência ao determinar quais recursos serão aplicados. Especificamente para este exemplo, -w960dp (ou largura disponível >= 960 dp) tem precedência sobre -h470dp (ou altura >= 470). Se a configuração do dispositivo não corresponder a nenhuma dessas condições, o recurso de layout padrão (res/layout/navigation_activity.xml) será usado.

Ao processar eventos de navegação, você precisa conectar apenas os eventos que correspondem aos widgets que estão presentes, como mostrado no exemplo a seguir.

Kotlin

class MainActivity : AppCompatActivity() {

   private lateinit var appBarConfiguration : AppBarConfiguration

   override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.navigation_activity)
      val drawerLayout : DrawerLayout? = findViewById(R.id.drawer_layout)
      appBarConfiguration = AppBarConfiguration(
                  setOf(R.id.home_dest, R.id.deeplink_dest),
                  drawerLayout)

      ...

      // Initialize the app bar with the navigation drawer if present.
      // If the drawerLayout is not null here, a Navigation button will be added
      // to the app bar whenever the user is on a top-level destination.
      setupActionBarWithNavController(navController, appBarConfig)

      // Initialize the NavigationView if it is present,
      // so that clicking an item takes
      // the user to the appropriate destination.
      val sideNavView = findViewById<NavigationView>(R.id.nav_view)
      sideNavView?.setupWithNavController(navController)

      // Initialize the BottomNavigationView if it is present,
      // so that clicking an item takes
      // the user to the appropriate destination.
      val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_nav_view)
      bottomNav?.setupWithNavController(navController)

      ...
    }

    ...
}

Java

public class MainActivity extends AppCompatActivity {

   private AppBarConfiguration appBarConfiguration;

   @Override
   protected void onCreate(@Nullable Bundle savedInstanceState) {
       super.onCreate(savedInstanceState);
       setContentView(R.layout.navigation_activity);
       NavHostFragment host = (NavHostFragment) getSupportFragmentManager()
               .findFragmentById(R.id.my_nav_host_fragment);
       NavController navController = host.getNavController();

       DrawerLayout drawerLayout = findViewById(R.id.drawer_layout);
       appBarConfiguration = new AppBarConfiguration.Builder(
               R.id.home_dest, R.id.deeplink_dest)
               .setDrawerLayout(drawerLayout)
               .build();

       // Initialize the app bar with the navigation drawer if present.
       // If the drawerLayout is not null here, a Navigation button will be added to
       // the app bar whenever the user is on a top-level destination.
       NavigationUI.setupActionBarWithNavController(
               this, navController, appBarConfiguration);


       // Initialize the NavigationView if it is present,
       // so that clicking an item takes
       // the user to the appropriate destination.
       NavigationView sideNavView = findViewById(R.id.nav_view);
       if(sideNavView != null) {
           NavigationUI.setupWithNavController(sideNavView, navController);
       }

       // Initialize the BottomNavigationView if it is present,
       // so that clicking an item takes
       // the user to the appropriate destination.
       BottomNavigationView bottomNav = findViewById(R.id.bottom_nav_view);
       if(bottomNav != null) {
           NavigationUI.setupWithNavController(bottomNav, navController);
       }

   }
}

Se a configuração do dispositivo mudar, a menos que explicitamente configurado de outra forma, o Android destruirá a atividade da configuração anterior com as visualizações associadas. Em seguida, a atividade é recriada com recursos projetados para a nova configuração. A atividade que está sendo destruída e recriada, depois conecta automaticamente os elementos de navegação globais adequados em onCreate().

Considere alternativas para layouts de visualização dividida

Layouts de visualização dividida, ou layouts mestres/detalhados, já foram uma maneira muito recomendada e recomendada de projetar para tablets e outros dispositivos de tela grande.

Desde o lançamento dos tablets Android, o ecossistema de dispositivos cresceu bastante. Um fator que influenciou consideravelmente o espaço de design para dispositivos de tela grande foi o lançamento dos modos de várias janelas, especialmente janelas de formato livre totalmente redimensionáveis, como as dos dispositivos ChromeOS. Isso dá uma ênfase significativamente maior para que cada tela do app seja responsiva, em vez de alterar a estrutura de navegação com base no tamanho da tela.

Embora seja possível implementar uma interface de layout de visualização dividida usando a Biblioteca de navegação, considere outras alternativas.

Nomes de destinos

Se você fornecer nomes de destino no gráfico usando o atributo android:label, sempre use valores de recurso para que seu conteúdo ainda possa ser localizado.

<navigation ...>
    <fragment
        android:id="@+id/my_dest"
        android:name="com.example.MyFragment"
        android:label="@string/my_dest_label"
        tools:layout="@layout/my_fragment" />
    ...

Com os valores de recursos, seus destinos têm automaticamente os recursos mais apropriados aplicados sempre que sua configuração é alterada.