Адаптивный интерфейс и навигация
Чтобы предоставить пользователям максимально удобные возможности навигации, вам следует предоставить пользовательский интерфейс навигации, адаптированный к ширине, высоте и наименьшей ширине устройства пользователя. Возможно, вы захотите использовать нижнюю панель приложения , всегда присутствующий или сворачиваемый ящик навигации , направляющую или, возможно, что-то совершенно новое в зависимости от доступного места на экране и уникального стиля вашего приложения.

Руководство по материальному дизайну архитектуры продукта предоставляет дополнительный контекст и рекомендации по созданию адаптивного пользовательского интерфейса, то есть пользовательского интерфейса, который динамически адаптируется к изменениям окружающей среды. Несколько примеров изменений среды включают корректировку ширины, высоты, ориентации и предпочтений языка пользователя. Эти свойства окружающей среды вместе называются конфигурацией устройства.
Когда одно или несколько из этих свойств изменяются во время выполнения, ОС Android в ответ уничтожает, а затем воссоздает действия и фрагменты вашего приложения . Поэтому лучшее, что вы можете сделать для поддержки адаптивного пользовательского интерфейса на Android, — это убедиться, что вы используете квалификаторы конфигурации ресурсов там, где это необходимо, и избегать использования жестко запрограммированных размеров макета .
Реализация глобальной навигации в адаптивном пользовательском интерфейсе
Реализация глобальной навигации как части адаптивного пользовательского интерфейса начинается с действия, в котором размещается ваш граф навигации. Практический пример можно найти в лаборатории кода навигации . В кодовой лаборатории используется NavigationView
для отображения меню навигации, как показано на рисунке 2. При работе на устройстве, которое отображает ширину не менее 960 пикселей, этот NavigationView
всегда находится на экране.

NavigationView
для отображения меню навигации. Другие размеры и ориентации устройств динамически переключаются между DrawerLayout
или BottomNavigationView
по мере необходимости.

BottomNavigationView
и DrawerLayout
для отображения меню навигации на небольших устройствах.Вы можете реализовать такое поведение, создав три разных макета, каждый из которых определяет нужные элементы навигации и иерархию представлений на основе текущей конфигурации устройства.
Конфигурация, к которой применяется каждый макет, определяется структурой каталогов, в которой находится файл макета. Например, файл макета 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
(или доступная ширина >= 960dp) имеет приоритет над -h470dp
(или доступная высота >= 470). Если конфигурация устройства не соответствует ни одному из этих условий, используется ресурс макета по умолчанию ( res/layout/navigation_activity.xml
).
При обработке событий навигации вам необходимо подключать только те события, которые соответствуют виджетам, которые присутствуют в данный момент, как показано в следующем примере.
Котлин
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) ... } ... }
Ява
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. Это значительно повышает акцент на отзывчивости каждого экрана вашего приложения, а не на изменении структуры навигации в зависимости от размера экрана.
Несмотря на то, что интерфейс макета с разделенным представлением можно реализовать с помощью библиотеки навигации, вам следует рассмотреть другие альтернативы .
Названия мест назначения
Если вы указываете имена мест назначения в своем графике с помощью атрибута 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" />
...
Благодаря значениям ресурсов к вашим местам назначения автоматически применяются наиболее подходящие ресурсы при каждом изменении конфигурации.
,Адаптивный интерфейс и навигация
Чтобы предоставить пользователям максимально удобные возможности навигации, вам следует предоставить пользовательский интерфейс навигации, адаптированный к ширине, высоте и наименьшей ширине устройства пользователя. Возможно, вы захотите использовать нижнюю панель приложения , всегда присутствующий или сворачиваемый ящик навигации , направляющую или, возможно, что-то совершенно новое в зависимости от доступного места на экране и уникального стиля вашего приложения.

Руководство по материальному дизайну архитектуры продукта предоставляет дополнительный контекст и рекомендации по созданию адаптивного пользовательского интерфейса, то есть пользовательского интерфейса, который динамически адаптируется к изменениям окружающей среды. Несколько примеров изменений среды включают корректировку ширины, высоты, ориентации и предпочтений языка пользователя. Эти свойства окружающей среды вместе называются конфигурацией устройства.
Когда одно или несколько из этих свойств изменяются во время выполнения, ОС Android в ответ уничтожает, а затем воссоздает действия и фрагменты вашего приложения . Поэтому лучшее, что вы можете сделать для поддержки адаптивного пользовательского интерфейса на Android, — это убедиться, что вы используете квалификаторы конфигурации ресурсов там, где это необходимо, и избегать использования жестко запрограммированных размеров макета .
Реализация глобальной навигации в адаптивном пользовательском интерфейсе
Реализация глобальной навигации как части адаптивного пользовательского интерфейса начинается с действия, в котором размещается ваш граф навигации. Практический пример можно найти в лаборатории кода навигации . В кодовой лаборатории используется NavigationView
для отображения меню навигации, как показано на рисунке 2. При работе на устройстве, которое отображает ширину не менее 960 пикселей, этот NavigationView
всегда находится на экране.

NavigationView
для отображения меню навигации. Другие размеры и ориентации устройств динамически переключаются между DrawerLayout
или BottomNavigationView
по мере необходимости.

BottomNavigationView
и DrawerLayout
для отображения меню навигации на небольших устройствах.Вы можете реализовать такое поведение, создав три разных макета, каждый из которых определяет нужные элементы навигации и иерархию представлений на основе текущей конфигурации устройства.
Конфигурация, к которой применяется каждый макет, определяется структурой каталогов, в которой находится файл макета. Например, файл макета 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
(или доступная ширина >= 960dp) имеет приоритет над -h470dp
(или доступная высота >= 470). Если конфигурация устройства не соответствует ни одному из этих условий, используется ресурс макета по умолчанию ( res/layout/navigation_activity.xml
).
При обработке событий навигации вам необходимо подключать только те события, которые соответствуют виджетам, которые присутствуют в данный момент, как показано в следующем примере.
Котлин
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) ... } ... }
Ява
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. Это значительно повышает акцент на отзывчивости каждого экрана вашего приложения, а не на изменении структуры навигации в зависимости от размера экрана.
Несмотря на то, что интерфейс макета с разделенным представлением можно реализовать с помощью библиотеки навигации, вам следует рассмотреть другие альтернативы .
Названия мест назначения
Если вы указываете имена мест назначения в своем графике с помощью атрибута 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" />
...
Благодаря значениям ресурсов к вашим местам назначения автоматически применяются наиболее подходящие ресурсы при каждом изменении конфигурации.