- PVSM.RU - https://www.pvsm.ru -
Здравствуйте, читатели. Это перевод статьи-документации [1] к новой технологии Navigation для Android-разработчиков. Технология сейчас находится в стадии разработки, но уже доступна для использования и очень даже работает в превью версии Android Studio 3.2 [2] и выше. Я уже опробовал её в действии и могу сказать что меня она впечатлила. Наконец-то осуществление смены экранов перестало быть чем-то сложным, особенно если используется передача данных от одного экрана к другому. Собственно, перевод я делаю для того чтобы больше русскоязычных разработчиков обратило внимание на технологию, и чтобы упростить её изучение.
Если заметите существенные ошибки или неточности, прошу сообщать в комментариях.
Архитектурный компонент Navigation позволяет упростить реализацию навигации между экранами назначения (destinations) в вашем приложении. По умолчанию, Navigation поддерживает фрагменты (Fragments) и активности (Activities) в качестве экранов назначения, но вы также можете добавить поддержку новых типов экранов назначения [3]. Набор экранов назначения называется навигационным графом (navigation graph) приложения.
Помимо экранов назначения на навигационном графе есть соединения между ними, называемые действиями (actions). Рисунок 1 демонстрирует визуальное представление навигационного графа для простого приложения из шести экранов назначения, соединённых пятью действиями.
Рисунок 1. Навигационный граф
Архитектурный компонент Navigation реализован на основе Principles of navigation [4].
Если вы хотите использовать архитектурный компонент Navigation в Android Studio, то вам необходима версия Android Studio 3.2 Canary 14 [2] или выше.
Прежде чем вам будет доступно создание навигационного графа, нужно настроить Navigation для вашего проекта. Для этого проделайте следующие шаги.
Добавьте поддержку Navigation в файле build.gradle (Module: app – Прим. переводчика). Для подробной информации изучите Adding components to your project [5].
Прим. переводчика: Необходимые зависимости файла build.gradle:
dependencies {
def nav_version = "1.0.0-alpha02"
// use -ktx for Kotlin
implementation "android.arch.navigation:navigation-fragment:$nav_version"
// use -ktx for Kotlin
implementation "android.arch.navigation:navigation-ui:$nav_version"
// optional - Test helpers
// use -ktx for Kotlin
androidTestImplementation "android.arch.navigation:navigation-testing:$nav_version"
}
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android">
</navigation>
Navigation Editor доступен по умолчанию только в Canary сборках Android Studio. Чтобы использовать Navigation Editor в Beta, Release Candidate, или Stable сборках перейдите в File > Settings (Android Studio > Preferences для Mac), выберете категорию Experimental, отметьте галочкой Enable Navigation Editor, и перезагрузите Android Studio.
Прим. переводчика: рекомендую независимо от сборки проверить, стоит ли эта галочка.
В Navigation Editor вы можете быстро создавать навигационные графы вместо написания XML. Как показано на рисунке 2, Navigation Editor имеет три раздела:
Рисунок 2. Navigation Editor
Описание разделов:
Первый шаг в создании навигационного графа это определение экранов назначений для вашего приложения. Вы можете создать пустой экран назначения или использовать фрагменты и активности из текущего проекта.
Архитектурный компонент Navigation разработан для приложений, которые имеют одну главную активность (Main Activity – Прим. переводчика) с множеством фрагментов, которые используются как экраны назначения. Главная активность является "хостом" для навигационного графа. В приложении с множеством активностей, каждая из них будет являться хостом для разных навигационных графов. Превращение активности в хост для навигационного графа описано далее в документе.
Чтобы определить экран назначения для вашего приложения, выполните следующие шаги.
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
app:startDestination="@id/blankFragment">
<fragment
android:id="@+id/blankFragment"
android:name="com.example.cashdog.cashdog.BlankFragment"
android:label="Blank"
tools:layout="@layout/fragment_blank" />
</navigation>
XML содержит атрибут startDestination содержащий id пустого экрана назначения (app:startDestination="@+id/fragment"). Для большей информации по стартовому экрану назначения изучите раздел Стартовый экран назначения.
В вашем приложении должно быть больше одного экрана назначения чтобы соединять их. Ниже описан XML для навигационного графа с двумя пустыми экранами назначения:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
app:startDestination="@id/blankFragment">
<fragment
android:id="@+id/blankFragment"
android:name="com.example.cashdog.cashdog.BlankFragment"
android:label="fragment_blank"
tools:layout="@layout/fragment_blank" />
<fragment
android:id="@+id/blankFragment2"
android:name="com.example.cashdog.cashdog.BlankFragment2"
android:label="Blank2"
tools:layout="@layout/fragment_blank_fragment2" />
</navigation>
Экраны назначения соединяются при помощи действий. Чтобы соединить два экрана назначения нужно:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
app:startDestination="@id/blankFragment">
<fragment
android:id="@+id/blankFragment"
android:name="com.example.cashdog.cashdog.BlankFragment"
android:label="fragment_blank"
tools:layout="@layout/fragment_blank" >
<action
android:id="@+id/action_blankFragment_to_blankFragment2"
app:destination="@id/blankFragment2" />
</fragment>
<fragment
android:id="@+id/blankFragment2"
android:name="com.example.cashdog.cashdog.BlankFragment2"
android:label="fragment_blank_fragment2"
tools:layout="@layout/fragment_blank_fragment2" />
</navigation>
В редакторе графа отображается иконка домика сразу за именем первого экрана назначения. Эта иконка обозначает что экран назначения является стартовым в навигационном графе. Вы можете выбрать другой экран назначения в качестве стартового, выполнив следующие шаги:
Активность становится хостом для навигационного графа благодаря пустому элементу NavHost [6], который добавляется в layout активности. NavHost [6] это элемент, наличие которого позволяет менять экраны назначения в том порядке, в котором нужно пользователю вашего приложения.
NavHost [6] в Navigation по умолчанию реализует NavHostFragment [7].
После добавления NavHost, вы должны сопоставить ему ваш навигационный граф, используя атрибут app:navGraph. Этот код демонстрирует как включить в layout NavHostFragment и соединить его с навигационным графом:
?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
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=".MainActivity">
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/my_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true"
/>
</android.support.constraint.ConstraintLayout>
Этот пример содержит атрибут app:defaultNavHost="true". Он отвечает за перехват системной кнопки Back (Прим. переводчика: системная кнопка Back и стрелка Up на верхней панели приложения будут работать одинаково). Вы также можете переопределить AppCompatActivity.onSupportNavigateUp() [8] и вызвать NavController.navigateUp() [9] как показано здесь:
@Override
public boolean onSupportNavigateUp() {
return Navigation.findNavController(this, R.id.nav_host_fragment).navigateUp();
}
override fun onSupportNavigateUp()
= findNavController(R.id.nav_host_fragment).navigateUp()
Переход к экрану назначения выполняется с использованием NavController [10]. Он может быть получен с помощью перегруженного статического метода findNavController():
После получения NavController, используйте его метод navigate() [14] чтобы перейти к экрану назначения. Метод navigate() принимает ID ресурса. Это может быть ID экрана назначения к которому нужно перейти, или это может быть ID действия. Использование ID действия вместо ID экрана назначения позволяет настроить анимацию перехода между экранами. Для более подробной информации читайте раздел Создание анимации перехода между экранами назначения.
Этот код демонстрирует как привязать действие к кнопке:
viewTransactionsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Navigation.findNavController(view).navigate(R.id.viewTransactionsAction);
}
});
viewTransactionsButton.setOnClickListener { view ->
view.findNavController().navigate(R.id.viewTransactionsAction)
}
Android поддерживает обратный стек [15], хранящий последний открытый экран назначения. Первый экран назначения помещается в стек когда пользователь запускает приложение. Каждый вызов метода navigate() помещает новый экран назначения в стек. Нажатие же кнопки Back или Up вызывает методы NavController.navigateUp() [9] и NavController.popBackStack() [16] чтобы извлечь экран назначения из стека.
Для кнопок вы также можете использовать удобный метод Navigation.createNavigateOnClickListener() [17]:
button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.next_fragment, null));
button.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.next_fragment, null))
Вы можете связать действия переходов с Navigation Drawer используя id экрана назначения в качестве id элемента меню (item). Следующий код демонстрирует пример экрана назначения, id которого имеет значение details_page_fragment:
<fragment android:id="@+id/details_page_fragment"
android:label="@string/details"
android:name="com.example.android.myapp.DetailsFragment" />
Используя одинаковый id для экрана назначения и элемента меню происходит автоматическое связывание элемента меню и экрана назначения. Этот код демонстрирует как связать экран назначения с элементом меню (например, это файл menu_nav_drawer.xml):
<item
android:id="@id/details_page_fragment"
android:icon="@drawable/ic_details"
android:title="@string/details" />
Или вот пример для меню с категориями (например menu_overflow.xml):
<item
android:id="@id/details_page_fragment"
android:icon="@drawable/ic_details"
android:title="@string/details"
android:menuCategory:"secondary" />
Также, архитектурный компонент Navigation включает класс NavigationUI [18]. Этот класс имеет несколько статических методов, которые вы можете использовать для связывания элементов меню с экранами назначения. Например, этот код показывает как использовать метод setupWithNavController() [19] чтобы соединить элемент меню с NavigationView [20]:
NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
NavigationUI.setupWithNavController(navigationView, navController);
val navigationView = findViewById<NavigationView>(R.id.nav_view)
navigationView.setupWithNavController(navController)
Необходимо настроить навигационные компоненты меню, используя методы NavigationUI [18] чтобы их состояние оставалось синхронизированным с изменениями в NavController [10].
Вы можете передавать данные между экранами назначениям двумя способами: используя объекты Bundle [21] или type-safe способом с помощью safeargs Gradle plugin. Следуйте этим шагам чтобы передавать данные с помощью объектов Bundle. Если вы используете Gradle, то изучите раздел Передача данных между экранами назначения type-safe способом.
Bundle bundle = new Bundle();
bundle.putString("amount", amount);
Navigation.findNavController(view).navigate(R.id.confirmationAction, bundle);
var bundle = bundleOf("amount" to amount)
view.findNavController().navigate(R.id.confirmationAction, bundle)
В экране назначения, в который передаются данные, используйте метод getArguments() [22] чтобы получить bundle и использовать его содержимое:
TextView tv = view.findViewById(R.id.textViewAmount);
tv.setText(getArguments().getString("amount"));
val tv = view.findViewById(R.id.textViewAmount)
tv.text = arguments.getString("amount")
Архитектурный компонент Navigation имеет плагин Gradle, называемый safeargs. Он генерирует простейшие классы для type-safe доступа к аргументам экранов назначения и действий. Подход safeargs построен на основе использования Bundle, но требует немного дополнительного кода для большей типовой безопасности. Чтобы добавить этот плагин, вставьте строку androidx.navigation.safeargs в build.gradle (в Module: app – Прим. переводчика). Например так:
apply plugin: 'com.android.application'
apply plugin: 'androidx.navigation.safeargs'
android {
//...
}
Прим. переводчика: также, нужно добавить в build.gradle (Project: ProjectName) зависимость classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha02":
buildscript {
repositories {
google()
}
dependencies {
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha02"
}
}
После установки плагина, следуйте этим шагам чтобы использовать type-safe передачу данных:
<fragment
android:id="@+id/confirmationFragment"
android:name="com.example.buybuddy.buybuddy.ConfirmationFragment"
android:label="fragment_confirmation"
tools:layout="@layout/fragment_confirmation">
<argument android:name="amount" android:defaultValue="1" app:type="integer"/>
</fragment>
После того как плагин safeargs сгенерировал код (то есть после создания аргумента с типом – Прим. переводчика), сгенерированы также классы для экрана-отправителя и экрана-получателя.
Прим. переводчика: мне пришлось после создания аргумента нажать кнопку Sync Project with Gradle Files, чтобы классы сгенерировались.
Класс экрана отправителя имеет такое же название как у оригинала, с добавлением слова Directions в конце.
То есть, если у вас есть экран назначения FirstFragment, то сгенерированный класс называется FirstFragmentDirections. Этот класс имеет метод (!), название которого совпадает с ID действия, которое передаёт аргумент. То есть, если ваш экран FirstFragment передаёт аргумент экрану SecondFragment, и если действие, соединяющее их, называется action_first_to_second, то вот так выглядит искомый метод FirstFragmentDirections.action_first_to_second().
Класс отправителя также содержит подкласс, который является типом возвращаемого значения метода, рассмотренного выше. То есть, в нашем случае, тип возвращаемого значения метода action_first_to_second() будет Action_first_to_second.
Класс получателя имеет такое же имя как у оригинала, с добавлением слова Args в конце. Для нашего примера, SecondFragment является принимающей стороной, поэтому для него сгенерируется класс SecondFragmentArgs. Этот класс имеет метод fromBundle() для получения аргументов.
Прим. переводчика: сейчас всё станет понятнее. Этот код я немного видоизменил по сравнению с оригинальным. Изменения касаются только названий, которые для каждого могут быть индивидуальны. Это сделано для упрощения понимания. Здесь в качестве названия аргумента используется имя myArgument, а его тип String.
Этот код демонстрирует как использовать safeargs для передачи аргументов через метод navigate():
@Override
public void onClick(View view) {
FirstFragmentDirections.Action_first_to_second action =
FirstFragmentDirections.action_first_to_second();
action.setMyArgument("My argument value");
Navigation.findNavController(view).navigate(action);
}
override fun onClick(v: View?) {
val action = FirstFragmentDirections.action_first_to_second
action.myArgument = "My argument value"
v.findNavController().navigate(action)
}
Этот код демонстрирует как извлекать аргументы с помощью safeargs:
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
String myArgument = SecondFragmentArgs.fromBundle(getArguments()).getMyArgument();
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val myArgument = SecondFragmentArgs.fromBundle(arguments).myArgument
}
Прим. переводчика: лично мне очень понравился этот метод передачи аргументов. Больше никаких тебе private static final String MY_ARGUMENT_KEY = "MY_ARGuMENT". Очень удобно что теперь для установки и извлечения аргумента используется его индивидуальный (!) getter и setter.
Последовательности экранов назначения могут быть сгруппированы в подграф. Подграф называется "nested graph" (вложенный граф), а граф-родитель "root graph" (корневой граф). Вложенные графы полезны для организации повторного использования частей пользовательского интерфейса вашего приложения, таких как ветвь (последовательность экранов) авторизации.
Равно как и корневой, вложенный граф должен иметь стартовый экран. Вложенный граф инкапсулирует свои экраны назначения. Экраны за пределами вложенного графа, например экраны корневого графа, имеют доступ только к стартовому экрану вложенного графа. На рисунке 6 изображён навигационный граф простейшего приложения для перевода денег. Граф имеет две ветви: для перевода денег, и для просмотра баланса.
Рисунок 6. Навигационный граф приложения для перевода денег
Группировка экранов назначения во вложенный граф:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
app:startDestination="@id/mainFragment">
<fragment
android:id="@+id/mainFragment"
android:name="com.example.cashdog.cashdog.MainFragment"
android:label="fragment_main"
tools:layout="@layout/fragment_main" >
<action
android:id="@+id/action_mainFragment_to_ sendMoneyGraph"
app:destination="@id/sendMoneyGraph" />
<action
android:id="@+id/action_mainFragment_to_viewBalanceFragment"
app:destination="@id/viewBalanceFragment" />
</fragment>
<fragment
android:id="@+id/viewBalanceFragment"
android:name="com.example.cashdog.cashdog.ViewBalanceFragment"
android:label="fragment_view_balance"
tools:layout="@layout/fragment_view_balance" />
<navigation android:id="@+id/sendMoneyGraph" app:startDestination="@id/chooseRecipient">
<fragment
android:id="@+id/chooseRecipient"
android:name="com.example.cashdog.cashdog.ChooseRecipient"
android:label="fragment_choose_recipient"
tools:layout="@layout/fragment_choose_recipient">
<action
android:id="@+id/action_chooseRecipient_to_chooseAmountFragment"
app:destination="@id/chooseAmountFragment" />
</fragment>
<fragment
android:id="@+id/chooseAmountFragment"
android:name="com.example.cashdog.cashdog.ChooseAmountFragment"
android:label="fragment_choose_amount"
tools:layout="@layout/fragment_choose_amount" />
</navigation>
</navigation>
Navigation.findNavController(view).navigate(R.id.action_mainFragment_to_sendMoneyGraph);
view.findNavController(view).navigate(R.id.action_mainFragment_to_sendMoneyGraph)
В Android, диплинк deep link [23] это URI (Адрес, грубо говоря – Прим. переводчика), который указывает на какой-либо экран приложения. Эти URI полезны если вы хотите направить пользователя на какой-то конкретный экран, чтобы он не добирался до него сам.
<deepLink app:uri="https://cashdog.com/sendmoney"/>
Когда пользователь нажимает кнопку Back, после перехода по диплинку, он переходит на предыдущий экран вашего графа. То есть, диплинк открывает экран таким образом, как будто пользователь последовательно добирался до него сам от точки входа в приложение.
Вам нужно внести изменения в manifest.xml чтобы разрешить вашему приложению использование диплинков:
<activity name=".MainActivity">
<nav-graph android:value="@navigation/main_nav" />
</activity>
На этапе сборки, он заменяется необходимыми для разрешения диплинков элементами.
Архитектурный компонент Navigation позволяет легко добавлять переходы между экранами назначения, такие как "постепенное появление" и "постепенный уход".
Добавление перехода:
<fragment
android:id="@+id/specifyAmountFragment"
android:name="com.example.buybuddy.buybuddy.SpecifyAmountFragment"
android:label="fragment_specify_amount"
tools:layout="@layout/fragment_specify_amount">
<action
android:id="@+id/confirmationAction"
app:destination="@id/confirmationFragment"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
</fragment>
Этот пример содержит переходы, которые активируются при открытии экрана (enterAnim и exitAnim) и при его закрытии (popEnterAnim и popExitAnim).
Если вы столкнулись с какими-то проблемами при использовании Navigation Editor, пожалуйста отправьте отчёт [32]. О том, как эффективно составлять отчёты об ошибках смотрите в разделе Report a bug [33].
Этот документ описывает фундаментальные вещи для реализации Navigation в вашем проекте. После его прочтения вас также может заинтересовать:
Что ж, этот перевод стал для меня довольно интересным опытом, особенно учитывая что до этого я по своему желанию не переводил такие объёмы текста, ещё и технического. Я надеюсь что таким образом помогу кому-то разобраться в этой технологии на первых парах. Как минимум, я уже помог себе получше разобраться в некоторых деталях этой новинки в ходе подготовки текста. Так что это однозначно было сделано не зря! Если у вас есть замечания по этому переводу, или вы хотите предложить перевести что-то ещё на эту тему (или любую тему по Android) – прошу в комментарии.
Автор: Princess_York
Источник [34]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/android/285391
Ссылки в тексте:
[1] статьи-документации: https://developer.android.com/topic/libraries/architecture/navigation/navigation-implementing?utm_source=android-studio&utm_medium=studio-assistant
[2] Android Studio 3.2: https://developer.android.com/studio/preview/
[3] добавить поддержку новых типов экранов назначения: https://developer.android.com/topic/libraries/architecture/navigation/navigation-add-new
[4] Principles of navigation: https://developer.android.com/topic/libraries/architecture/navigation/navigation-principles
[5] Adding components to your project: https://developer.android.com/topic/libraries/architecture/adding-components#navigation
[6] NavHost: https://developer.android.com/reference/androidx/navigation/NavHost
[7] NavHostFragment: https://developer.android.com/reference/androidx/navigation/fragment/NavHostFragment
[8] AppCompatActivity.onSupportNavigateUp(): https://developer.android.com/reference/android/support/v7/app/AppCompatActivity
[9] NavController.navigateUp(): https://developer.android.com/reference/androidx/navigation/NavController#navigateUp
[10] NavController: https://developer.android.com/reference/androidx/navigation/NavController
[11] NavHostFragment.findNavController(Fragment): https://developer.android.com/reference/androidx/navigation/fragment/NavHostFragment#findNavController(android.support.v4.app.Fragment)
[12] Navigation.findNavController(Activity, @IdRes int viewId): https://developer.android.com/reference/androidx/navigation/Navigation#findNavController(android.app.Activity,%20int)
[13] Navigation.findNavController(View): https://developer.android.com/reference/androidx/navigation/Navigation#findNavController(android.view.View)
[14] navigate(): https://developer.android.com/reference/androidx/navigation/NavController#navigate(int)
[15] обратный стек: https://developer.android.com/guide/components/activities/tasks-and-back-stack
[16] NavController.popBackStack(): https://developer.android.com/reference/androidx/navigation/NavController#popBackStack
[17] Navigation.createNavigateOnClickListener(): https://developer.android.com/reference/androidx/navigation/Navigation#createNavigateOnClickListener(int)
[18] NavigationUI: https://developer.android.com/reference/androidx/navigation/ui/NavigationUI
[19] setupWithNavController(): https://developer.android.com/reference/androidx/navigation/ui/NavigationUI#setupWithNavController(android.support.design.widget.NavigationView,%20androidx.navigation.NavController)
[20] NavigationView: https://developer.android.com/reference/android/support/design/widget/NavigationView
[21] Bundle: https://developer.android.com/reference/android/os/Bundle
[22] getArguments(): https://developer.android.com/reference/android/app/Fragment#getArguments
[23] deep link: https://developer.android.com/training/app-links/deep-linking
[24] http://www.cashdog.com: http://www.cashdog.com
[25] https://www.cashdog.com: https://www.cashdog.com
[26] http://www.example.com/users/{id: http://www.example.com/users/%7Bid
[27] http://www.example.com/users/4: http://www.example.com/users/4
[28] Верификацию Android App Links: https://developer.android.com/training/app-links/verify-site-associations
[29] Property Animation: https://developer.android.com/guide/topics/graphics/prop-animation
[30] View Animation: https://developer.android.com/guide/topics/graphics/view-animation
[31] Animation Resources: https://developer.android.com/guide/topics/resources/animation-resource
[32] отчёт: https://issuetracker.google.com/issues/new?component=404706
[33] Report a bug: https://developer.android.com/studio/report-bugs
[34] Источник: https://habr.com/post/416025/?utm_source=habrahabr&utm_medium=rss&utm_campaign=416025
Нажмите здесь для печати.