Адаптивный интерфейс и навигация
Чтобы предоставить пользователям максимально удобные возможности навигации, вам следует предоставить пользовательский интерфейс навигации, адаптированный к ширине, высоте и наименьшей ширине устройства пользователя. Возможно, вы захотите использовать нижнюю панель приложения , всегда присутствующий или сворачиваемый ящик навигации , направляющую или, возможно, что-то совершенно новое в зависимости от доступного места на экране и уникального стиля вашего приложения.
![примеры направляющих, панелей навигации и нижней панели приложений](https://developer.android.google.cn/static/images/guide/navigation/nav-ui-elements.png?hl=ru)
Руководство по материальному дизайну архитектуры продукта предоставляет дополнительный контекст и рекомендации по созданию адаптивного пользовательского интерфейса, то есть пользовательского интерфейса, который динамически адаптируется к изменениям окружающей среды. Несколько примеров изменений среды включают корректировку ширины, высоты, ориентации и предпочтений языка пользователя. Эти свойства окружающей среды вместе называются конфигурацией устройства.
Когда одно или несколько из этих свойств изменяются во время выполнения, ОС Android в ответ уничтожает, а затем воссоздает действия и фрагменты вашего приложения . Поэтому лучшее, что вы можете сделать для поддержки адаптивного пользовательского интерфейса на Android, — это убедиться, что вы используете квалификаторы конфигурации ресурсов там, где это необходимо, и избегать использования жестко запрограммированных размеров макета .
Реализация глобальной навигации в адаптивном пользовательском интерфейсе
Реализация глобальной навигации как части адаптивного пользовательского интерфейса начинается с действия, в котором размещается ваш граф навигации. Практический пример можно найти в лаборатории кода навигации . В кодовой лаборатории используется NavigationView
для отображения меню навигации, как показано на рисунке 2. При работе на устройстве, которое отображает ширину не менее 960 пикселей, этот NavigationView
всегда находится на экране.
![кодовая лаборатория навигации использует представление навигации, которое всегда видно, когда ширина устройства составляет не менее 960 dp.](https://developer.android.google.cn/static/images/guide/navigation/omnipresent-navigation-view.png?hl=ru)
NavigationView
для отображения меню навигации. Другие размеры и ориентации устройств динамически переключаются между DrawerLayout
или BottomNavigationView
по мере необходимости.
![BottomnavigationView и DrawerLayout, используемые для меню навигации по мере необходимости в небольших макетах устройств.](https://developer.android.google.cn/static/images/guide/navigation/bottom-or-drawer.png?hl=ru)
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" />
...
Благодаря значениям ресурсов к вашим местам назначения автоматически применяются наиболее подходящие ресурсы при каждом изменении конфигурации.
,Адаптивный интерфейс и навигация
Чтобы предоставить пользователям максимально удобные возможности навигации, вам следует предоставить пользовательский интерфейс навигации, адаптированный к ширине, высоте и наименьшей ширине устройства пользователя. Возможно, вы захотите использовать нижнюю панель приложения , всегда присутствующий или сворачиваемый ящик навигации , направляющую или, возможно, что-то совершенно новое в зависимости от доступного места на экране и уникального стиля вашего приложения.
![примеры направляющих, панелей навигации и нижней панели приложений](https://developer.android.google.cn/static/images/guide/navigation/nav-ui-elements.png?hl=ru)
Руководство по материальному дизайну архитектуры продукта предоставляет дополнительный контекст и рекомендации по созданию адаптивного пользовательского интерфейса, то есть пользовательского интерфейса, который динамически адаптируется к изменениям окружающей среды. Несколько примеров изменений среды включают корректировку ширины, высоты, ориентации и предпочтений языка пользователя. Эти свойства окружающей среды вместе называются конфигурацией устройства.
Когда одно или несколько из этих свойств изменяются во время выполнения, ОС Android в ответ уничтожает, а затем воссоздает действия и фрагменты вашего приложения . Поэтому лучшее, что вы можете сделать для поддержки адаптивного пользовательского интерфейса на Android, — это убедиться, что вы используете квалификаторы конфигурации ресурсов там, где это необходимо, и избегать использования жестко запрограммированных размеров макета .
Реализация глобальной навигации в адаптивном пользовательском интерфейсе
Реализация глобальной навигации как части адаптивного пользовательского интерфейса начинается с действия, в котором размещается ваш граф навигации. Практический пример можно найти в лаборатории кода навигации . В кодовой лаборатории используется NavigationView
для отображения меню навигации, как показано на рисунке 2. При работе на устройстве, которое отображает ширину не менее 960 пикселей, этот NavigationView
всегда находится на экране.
![кодовая лаборатория навигации использует представление навигации, которое всегда видно, когда ширина устройства составляет не менее 960 dp.](https://developer.android.google.cn/static/images/guide/navigation/omnipresent-navigation-view.png?hl=ru)
NavigationView
для отображения меню навигации. Другие размеры и ориентации устройств динамически переключаются между DrawerLayout
или BottomNavigationView
по мере необходимости.
![BottomnavigationView и DrawerLayout, используемые для меню навигации по мере необходимости в небольших макетах устройств.](https://developer.android.google.cn/static/images/guide/navigation/bottom-or-drawer.png?hl=ru)
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" />
...
Благодаря значениям ресурсов к вашим местам назначения автоматически применяются наиболее подходящие ресурсы при каждом изменении конфигурации.