IU responsiva y navegación
A fin de brindar la mejor experiencia de navegación posible a los usuarios, deberías proporcionar una IU de navegación que se adapte al ancho, la altura y el ancho mínimo del dispositivo del usuario. Te recomendamos que uses una barra de la app inferior, un panel lateral de navegación que se muestre en todo momento o que se pueda contraer, un riel o tal vez algo completamente nuevo en función del espacio de pantalla disponible y el estilo único de tu app.
La guía de arquitectura de productos de Material Design proporciona más contexto y consideraciones para compilar una IU responsiva (es decir, una IU que se adapte en forma dinámica a los cambios en el entorno). Algunos ejemplos de este tipo de cambios incluyen ajustes en el ancho, la altura, la orientación y la preferencia de idioma del usuario. Estas propiedades del entorno se denominan en forma colectiva la configuración del dispositivo.
Cuando una o más de esas propiedades cambian durante el tiempo de ejecución, el SO Android responde destruyendo y volviendo a crear las actividades y los fragmentos de tu app. Por lo tanto, lo mejor que puedes hacer para admitir una IU responsiva en Android es asegurarte de usar calificadores de configuración de recursos cuando corresponda y evitar el uso de tamaños de diseño hard-coded.
Cómo implementar la navegación global en una IU responsiva
La implementación de la navegación global como parte de una IU responsiva comienza con la actividad que aloja tu gráfico de navegación. Si quieres ver un ejemplo práctico, consulta el Codelab de Navigation.
El codelab usa una NavigationView
para mostrar el menú de navegación, como se muestra en la figura 2. Cuando se ejecuta en un dispositivo que renderiza en un ancho de al menos 960 dp, esta NavigationView
siempre está en pantalla.
Otros tamaños y orientaciones de dispositivos cambian de forma dinámica entre DrawerLayout
o BottomNavigationView
según sea necesario.
Puedes implementar este comportamiento si creas tres diseños diferentes, en los que cada uno defina los elementos de navegación deseados y la jerarquía de vistas según la configuración actual del dispositivo.
La configuración a la que se aplica cada diseño se determina según la estructura de directorios en la que se ubica el archivo de diseño. Por ejemplo, el archivo de diseño NavigationView
se encuentra en el directorio 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>
La vista de navegación inferior se encuentra en el directorio 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>
El diseño del panel lateral se encuentra en el directorio res/layout
. Usa ese directorio para diseños predeterminados sin calificadores específicos según la configuración:
<!-- 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>
Android sigue un orden de prioridad a los efectos de determinar qué recursos aplicar. En particular, en este ejemplo, -w960dp
(o ancho disponible >= 960 dp) tiene prioridad sobre -h470dp
(o altura disponible >= 470). Si la configuración del dispositivo no coincide con ninguna de esas condiciones, se usa el recurso de diseño predeterminado (res/layout/navigation_activity.xml
).
Cuando administres eventos de navegación, deberás conectar solo los eventos que correspondan a los widgets que estén presentes, como se muestra en el siguiente ejemplo.
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); } } }
Si cambia la configuración del dispositivo, a menos que se configure lo contrario de forma explícita, Android destruirá la actividad de la configuración anterior junto con las vistas asociadas. Luego, vuelve a crear la actividad con recursos diseñados para la configuración nueva. La actividad, que se destruye y se vuelve a crear, luego conecta automáticamente los elementos de navegación globales adecuados en onCreate()
.
Considera alternativas a los diseños de vista dividida
Los diseños de vista dividida, o diseños principales o de detalles, solían ser una forma muy popular y recomendada de diseño para tablets y otros dispositivos de pantalla grande.
Desde la presentación de las tablets Android, el ecosistema de dispositivos creció rápidamente. Un factor que influyó de forma considerable en el espacio de diseño para dispositivos con pantallas grandes fue la presentación de modos multiventana, en especial las ventanas de formato libre cuyo tamaño puede cambiarse por completo, como las de los dispositivos ChromeOS. Esto pone un énfasis mucho mayor en lograr que todas las pantallas de tu app sean responsivas, en lugar de cambiar la estructura de navegación según el tamaño de la pantalla.
Si bien es posible implementar una interfaz de diseño de vista dividida usando la biblioteca de Navigation, deberías considerar otras alternativas.
Nombres de destino
Si proporcionas nombres de destino en tu gráfico con el atributo android:label
, asegúrate de usar siempre valores de recursos de forma que aún se pueda ubicar tu contenido.
<navigation ...>
<fragment
android:id="@+id/my_dest"
android:name="com.example.MyFragment"
android:label="@string/my_dest_label"
tools:layout="@layout/my_fragment" />
...
Mediante el uso de los valores de recursos, tus destinos tendrán aplicados automáticamente los recursos más adecuados cada vez que cambie la configuración.