Адаптивный интерфейс и навигация
Чтобы пользователям было удобно, интерфейс навигации должен быть адаптирован к ширине, высоте и минимальной ширине экрана устройства. Вы можете использовать нижнюю панель приложения, постоянно видимую или сворачиваемую панель навигации, боковую панель или что-то совершенно новое, основанное на доступном пространстве экрана и уникальном стиле вашего приложения.
В руководстве по архитектуре продуктов Material Design вы найдете дополнительную информацию и рекомендации по созданию адаптивного интерфейса, который динамически подстраивается под изменения среды. Примеры изменений среды: корректировка ширины, высоты, ориентации и языковых настроек пользователя. Эти свойства среды в совокупности называются конфигурацией устройства.
Когда одно или несколько из этих свойств изменяются во время выполнения, ОС Android реагирует на это, уничтожая, а затем воссоздавая действия и фрагменты вашего приложения. Поэтому лучшее, что вы можете сделать для поддержки адаптивного интерфейса на Android, – это использовать квалификаторы конфигурации ресурсов там, где это необходимо, и избегать использования размеров макета, заданных в коде.
Реализация глобальной навигации в адаптивном пользовательском интерфейсе
Реализация глобальной навигации как части адаптивного интерфейса начинается с действия, в котором размещается граф навигации. Чтобы посмотреть пример, ознакомьтесь с практической работой по навигации.
В практической работе для отображения меню навигации используется NavigationView, как показано на рисунке 2. При запуске на устройстве, которое отображает ширину не менее 960 dp, этот NavigationView всегда находится на экране.
NavigationView.На устройствах других размеров и ориентаций по мере необходимости динамически переключаются макеты DrawerLayout и BottomNavigationView.
BottomNavigationView и DrawerLayout, чтобы показывать меню навигации на устройствах с небольшим экраном.Для этого можно создать три разных макета, в каждом из которых будут определены нужные элементы навигации и иерархия View на основе текущей конфигурации устройства.
Конфигурация, к которой применяется каждый макет, определяется структурой каталога, в котором размещен файл макета. Например, файл макета NavigationView находится в каталоге 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>
Нижняя панель навигации находится в каталоге 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>
Макет бокового меню находится в каталоге res/layout. Используйте этот каталог для макетов по умолчанию без модификаторов, связанных с конфигурацией:
<!-- 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 следует порядку приоритета. В этом примере -w960dp (или доступная ширина >= 960 dp) имеет приоритет над -h470dp (или доступная высота >= 470). Если конфигурация устройства не соответствует ни одному из этих условий, используется ресурс разметки по умолчанию (res/layout/navigation_activity.xml).
При обработке событий навигации вам нужно подключить только те события, которые соответствуют виджетам, присутствующим в данный момент, как показано в следующем примере.
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); } } }
Если конфигурация устройства меняется, то, если явно не настроено иное, Android уничтожает операцию из предыдущей конфигурации вместе со связанными с ней представлениями. Затем оно воссоздает действие с ресурсами, предназначенными для новой конфигурации. Действие, которое уничтожается и создается заново, а затем автоматически подключает нужные элементы глобальной навигации в onCreate().
Рассмотрите альтернативы макетам с разделенным экраном
Макеты с разделенным экраном, или макеты с главным и дополнительным окнами, когда-то были очень популярным и рекомендуемым способом проектирования для планшетов и других устройств с большим экраном.
С момента появления планшетов Android экосистема устройств быстро росла. Одним из факторов, который значительно повлиял на дизайн устройств с большими экранами, стало появление многооконных режимов, в частности окон свободной формы с полностью изменяемым размером, например на устройствах с ChromeOS. Это означает, что каждый экран вашего приложения должен быть адаптивным, а не менять структуру навигации в зависимости от размера экрана.
Хотя с помощью библиотеки Navigation можно реализовать интерфейс с разделенным экраном, рекомендуем рассмотреть другие варианты.
Названия пунктов назначения
Если вы указываете названия пунктов назначения в графе с помощью атрибута android:label, всегда используйте значения ресурсов, чтобы ваш контент можно было локализовать.
<navigation ...>
<fragment
android:id="@+id/my_dest"
android:name="com.example.MyFragment"
android:label="@string/my_dest_label"
tools:layout="@layout/my_fragment" />
...
Благодаря значениям ресурсов к вашим целевым сервисам автоматически применяются наиболее подходящие ресурсы при каждом изменении конфигурации.