Для работы с навигацией между экранами в Android-приложении применяется библиотека Navigation Component. Она входит в набор Android Jetpack и помогает реализовать навигацию, от простых нажатий на кнопки до более сложных, таких как панели приложений (action bars) и панель навигации (navigation drawers).
Библиотека предоставляет ряд преимуществ:
В качестве примера будет использоваться готовый проект, в котором уже есть все необходимые экраны, их макеты и исходные коды. Необходимо будет лишь разобраться с навигацией между экранами.
Тестовое приложение представляет собой мини-викторину. Оно будет иметь несколько экранов:
Стартовый проект с приложением содержит все эти экраны, но не содержит связей между ними, их создание будет рассмотрено далее. Каждое описанный экран — это фрагмент. Фрагмент — особый компонент системы Android. Прежде чем начать разбираться с навигацией по фрагментам, нужно разобраться с тем, что это такое.
Фрагмент (класс Fragment
) предоставляет часть пользовательского интерфейса в активности (Activity
). Активности могут содержать как несколько фрагментов внутри себя, так и один фрагмент. Обычно несколько фрагментов в одной активности располагают при разработке приложения для планшетов, когда для меню навигации и для основного контента выделяются отдельные фрагменты, располагающиеся на экране одновременно.
Фрагменты имеют свой отдельный жизненный цикл и самостоятельно отдельно от активностей занимаются обработкой событий ввода.
Обычно фрагменты имеют собственные макеты (layouts), описывающие элементы пользовательского интерфейса. Если в активностях инициализация и установка макетов выполняется в методе onCreate()
, то в фрагментах — в методе onCreateView()
общем для всех фрагментов. Вместо метода setContentView()
для установки макета здесь используется метод inflate()
, принимающий на вход ссылку на ресурс с макетом, объект container
, являющийся родительским видом и булево значение, определяющее нужно ли новый макет привязать к корневому (в большинстве случаев используется false
).
class ExampleFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.example_fragment, container, false)
}
}
Для добавления фрагмента в активность необходимо описать тег <fragment>
в макете активности.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:name="com.example.news.ExampleFragment"
android:id="@+id/example_fragment"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
Существует подклассы фрагментов: ListFragment
, DialogFragment
, PreferenceFragment
, WebViewFragment
и др., упрощающие реализацию и работу с конкретными типами окон: списками, диалоговыми окнами, настройками и веб-содержимым, соответственно.
Именно фрагменты чаще используются в качестве компонентов, описывающих экраны приложения, располагаясь в одной или паре активностей, в зависимости от задач приложения. Фрагменты позволяют настраивать боковые панели навигации (navigation drawer) и ActionBar-меню. Фрагменты упрощают вопрос навигации и передачи данных между экранами приложения. Также создание фрагментов — наиболее выгодно с точки зрения траты ресурсов системы, чем создание активностей.
Перейдем к вопросам реализации навигации между экранами приложения.
Прежде всего необходимо выделить три основные принципа навигации:
Сперва необходимо скачать и собрать стартовый проект trivia-starter-code. Стартовый проект уже содержит почти все необходимые фрагменты (кроме одного), все необходимые макеты, создавать их не нужно. Необходимо сосредоточиться исключительно на навигации.
Единственный фрагмент, которого не хватает в стартовом проекте — это фрагмент с начальным экраном приложения TitleFragment
. Для его создания необходимо выполнить следующие шаги:
TitleFragment
.Далее необходимо заменить код, сгенерированный автоматически для настройки макета фрагмента (return TextView(activity).apply
), на использование метода DataBindingUtil.inflate()
. Это позволит нам и инициализировать макет для фрагмента и использовать в дальнейшем Data Binding.
Определение метода onCreateView()
должно выглядеть следующим образом:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
val binding = DataBindingUtil.inflate<FragmentTitleBinding>(
inflater, R.layout.fragment_title, container, false)
return binding.root
}
И в конце необходимо поместить фрагмент с начальным экраном на MainActivity
, чтобы его можно было отобразить. В файл activity_main.xml
:
<fragment
android:id="@+id/titleFragment"
android:name="com.example.android.navigation.TitleFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
После запуска приложения на экране отобразится содержимое TitleFragment
— начальный экран с логотипом и кнопкой "Play". По нажатию на кнопку ничего не выполняется.
Для реализации непосредственно навигации между экранами необходимо добавить зависимость от библиотеки Navigation Component, и создать файл с графом навигации.
1. Добавление зависимости Navigation Components:
Для добавления зависимости необходимо сперва добавить переменную version_navigation
в блок ext
в Gradle-файл проекта. Далее эта переменная будет использоваться для добавления зависимости конкретной версии, избавляя от дублирования номера версии в нескольких местах.
buildscript {
ext {
...
version_navigation = '2.3.2'
...
}
}
Добавление непосредственно зависимостей в Gradle-файл модуля app
:
dependencies {
...
implementation "androidx.navigation:navigation-fragment-ktx:$version_navigation"
implementation "androidx.navigation:navigation-ui-ktx:$version_navigation"
}
2. Добавление графа навигации:
Граф навигации — файл с описанием экранов приложения и связей между ними в формате XML.
Для создания файла навигации необходимо кликнуть правой кнопкой на модуль app
и выбрать New -> Android resource file.
В качестве имени файла ресурса необходимо указать navigation.xml
, а также выбрать тип Navigation
.
Когда файл будет создан, его можно будет найти в каталоге res/navigation
.
3. Замена TitleFragment
на NavHostFragment
в активности:
Для реализации навигации также необходимо добавить NavHostragment
— контейнер, который будет информацию о пунктах назначения на графе навигации. Класс NavHostragment
является стандартным. Чтобы его использовать, необходимо в описании макета activity_main.xml
заменить использование TitleFragment
на NavHostFragment
.
<fragment
android:id="@+id/navHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="@navigation/navigation"
app:defaultNavHost="true" />
Параметр name
ссылается на полное имя класса NavHostFragment
.
Параметр navGraph
ссылается на ресурс с описанием графа навигации, откуда класс NavHostFragment
будет брать информацию о связях между фрагментами с экранами.
Параметр defaultNavHost
определяет, что этот контейнер навигации является контейнером по умолчанию и именно к нему будет применено использование системной кнопки "Назад".
4. Добавление фрагментов на граф навигации:
Для редактирования навигации используется редактор навигации. Он так же как и редактор макетов имеет два режима (две вкладки): Design и Code. Первый позволяет использовать элементы интерфейса IDE для редактирования графа навигации, второй предоставляет доступ к исходному XML-файлу графа. Кроме этого еще есть вкладка Split, которая делит рабочую область на две части: в одной код, во второй — графическое представление.
Для добавления фрагмента на граф навигации необходимо в режиме Design необходимо нажать на кнопку +
в верхнем левом углу и среди макетов выбрать необходимый, например, fragment_title
. Миниатюра фрагмента появится в рабочей зоне графа навигации.
Далее необходимо сделать TitleFragment
стартовым. Для этого необходимо нажать на кнопку Assign start destination, когда фрагмент выбран, либо указать id
фрагмента в качестве атрибута Start Destination (app:startDestination
в XML-коде) для самого графа навигации.
Если запустить приложение, то будет отображен TitleFragment
, находящийся внутри NavHostFragment
, который в свою очередь располагается внутри одной единственной активности.
Далее необходимо добавить еще один фрагмент, чтобы установить переход от начального экрана к следующему. Добавим на граф навигации fragment_game
. Миниатюра макета также появляется в рабочей зоне графа навигации.
Для установки перехода необходимо протянуть связь от фрагмента TitleFragment
(индикатора в виде "кружочка") к GameFragment
, и установить обработчик нажатию на кнопку "Play" на TitleFragment
. По нажатию на эту кнопку должен выполниться переход к основному экрану игры GameFragment
.
Реализация обработчика будет выглядеть следующим образом:
binding.playButton.setOnClickListener {
Navigation.findNavController(it).navigate(R.id.action_titleFragment_to_gameFragment)
}
Статический метод Navigation.findNavController()
служит для получения экземпляра класса NavController
, который является контроллером для графа навигации, предоставляет различную информацию о графе навигации и позволяет взаимодейстовать с ним. Так NavController
имеет метод navigate()
, выполняющий переход от одного экрана к другому по уникальному идентификатору, который генерируется автоматически при создании связи. Например, идентификатор action_titleFragment_to_gameFragment
означает переход от titleFragment
к gameFragment
(эти идентификаторы описаны в элементах <fragment>
на графе навигации).
Помимо начального и основного игрового экрана приложение имеет еще экран победы GameWonFragment
и экран поражения GameOverFragment
. Их также необходимо добавить на граф навигации, установить переход к ним от GameFragment
. Затем в классе GameFragment
в методе onCreateView()
необходимо добавить код для выполнения перехода к фрагментам в зависимости от определенных условий (места для вставки кода выделены комментариями).
// We've won! Navigate to the gameWonFragment.
view.findNavController().navigate(R.id.action_gameFragment_to_gameWonFragment)
// Game over! A wrong answer sends us to the gameOverFragment.
view.findNavController().navigate(R.id.action_gameFragment_to_gameOverFragment)
Теперь после запуска приложения и успешного прохождения теста будет выполнен переход к GameWonFragment
, а в случае ошибки GameOverFragment
.
Каждый новый открытый фрагмент помещается на стек фрагментов. Например, если пользователь прошел по всем экранам приложения от начального до экрана "Gave Over", то стек фрагментов содержит TitleFragment
, GameFragment
и GameOverFragment
. Последовательность именно такая.
Если пользователь на экране "Game Over" нажмет на системную кнопку "Назад", то вернется к экрану с игрой, где будет написан последний вопрос, а это является некорректным поведением. Для пользователя логичным было бы попасть на начальный экран, а для этого необходимо удалить фрагмент GameFragment
из стека фрагментов. Такое поведение можно реализовать следующим образом.
1. Настройка поведения системной кнопки "Назад":
Чтобы нажатие на системную кнопку "Назад" выполняло переход от GameOverFragment
к TitleFragment
, необходимо настроить атрибут Pop Behavior. Для этого нужно выбрать уже созданную связь между GameFragment
и GameOverFragment
, на панели атрибутов выбрать Pop Behavior -> popUpTo -> gameFragment и установить галочку popUpToInclusive. Настройка "поведения" определяет к какому фрагменту необходимо вернуться назад по выбранной связи (все фрагменты выше выбранного на стеке будут удалены), а галочка popUpToInclusive говорит, что сам фрагмент GameFragment
тоже нужно удалить со стека (это вернет пользователя на фрагмент TitleFragment
).
Аналогичное поведение необходимо реализовать и возврата с фрагмента GameWonFragment
к TitleFragment
.
2. Добавление обработчика для кнопок на GameOverFragment
и GameWonFragment
:
На экранах "Game Over" и "Congratulations" есть кнопки "Try Again" и "Next Match" соответственно. Эти кнопки должны позволять пользователю начать игру заново, т.е. возвращать пользователя к предыдущему экрану с вопросами GameFragment
.
Для этого необходимо протянуть новую связь от GameOverFragment
к GameFragment
и на панели атрибутов установить Pop To -> titleFragment. Здесь указывается возврат к titleFragment для того, чтобы в любой момент, когда показан фрагмент GameOverFragment
можно было вернуться к TitleFragment
. Иначе, на стеке может казаться множество GameOverFragment
и по кнопке "Back" возврат будет происходить именно к ним.
Затем необходимо перейти в класс GameOverFragment
и установить обработчик кнопки "Try Again".
binding.tryAgainButton.setOnClickListener {
it.findNavController().navigate(R.id.action_gameOverFragment_to_gameFragment)
}
Аналогично необходимо создать связь между GameWonFragment
и GameFragment
, настроить Pop Behavior и установить обработчик нажатия на кнопку "Match Again".
3. Добавление кнопки "Назад" на Action Bar:
Кроме системной кнопки "Назад" Android поддерживает еще и кнопку "Назад", отображающуюся на Action Bar — панели сверху приложения. Для ее добавления необходимо внести изменения в MainActivity
. Необходимо получить экземпляр NavController
и использовать его для установки Action Bar:
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
...
navController = this.findNavController(R.id.navHostFragment)
NavigationUI.setupActionBarWithNavController(this, navController)
}
Далее необходимо переопределить метод активности onSupportNavigateUp()
:
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp()
}
Метод onSupportNavigateUp()
определяет, что именно должно быть выполнено при навигации с помощью кнопки "Назад". В данном случае это навигация по стеку с помощью экземпляра NavController
.
Если запустить приложение, можно убедиться, что кнопка "Назад" на Action Bar отображается и работает она аналогично системной кнопке "Назад.
Action Bar может содержать собственное меню. Обычно меню помечается кнопкой в виде трех вертикальных точек.
В случае приложения "Trivia" меню будет содержать один пункт "About", открывающий отдельный экран с информацией о приложении. Фрагмент AboutFragment
уже создан.
1. Добавление фрагмента AboutFragment
на граф навигации:
Для добавления возможности перехода к экрану "About" необходимо сперва добавить его на граф навигации.
2. Создание нового ресурса menu
:
Конфигурация меню описывается в специальных ресурсах menu
. Для его добавления необходимо кликнуть правой кнопкой по модулю app
, выбрать New -> Android Resource File. Далее необходимо указать тип Menu и имя файла, например, overflow_menu и кликнуть "Ok". В результате будет создан файл overflow_menu.xml
и помещен в каталог res/menu
.
В меню необходимо добавить новый элемент Menu Item.
На панели атрибутов указать:
aboutFragment
— совпадает с id
фрагмента, к которому необходимо перейти при выборе данного пункта меню (ВАЖНО, чтобы эти идентификаторы совпадали),@string/about
— ссылка на строковый ресурс.3. Добавление меню на Action Bar фрагмента TitleFragment
:
Для добавления меню на Action Bar фрагмента TitleFragment
необходимо сперва добавить вызов метода setHasOptionsMenu(true)
внутри метода onCreateView()
. Вызов метода определяет, что данный фрагмент в принципе может отображать меню.
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
...
setHasOptionsMenu(true)
return binding.root
}
Для инициализации и установки меню из файла ресурса необходимо переопределить метод фрагмента onCreateOptionsMenu()
и описать в нем загрузку меню из файла overflow_menu
.
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.overflow_menu, menu)
}
4. Реализация обработчика нажатия на элемент меню:
Чтобы нажатие на элемент меню "About" имело эффект, необходимо реализовать обработчик нажатия.
Для этого требуется переопределить метод фрагмента onOptionsItemSelected()
.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return NavigationUI.onNavDestinationSelected(item, requireView().findNavController())
|| super.onOptionsItemSelected(item)
}
Метод NavigationUI.onNavDestinationSelected()
выполняет навигацию к фрагменту по идентификатору меню. Именно для этого идентификаторы фрагмента и меню, по которому к фрагменту нужно перейти, важно указывать одинаковыми.
По сути фрагмент AboutFragment
имеет в графе навигации идентификатор aboutFragment
. Добавленный пункт меню "About" имеет аналогичный идентификатор и метод NavigationUI.onNavDestinationSelected()
, принимающий на вход элемент меню (MenuItem
) и экземпляр NavController
сопоставляет эти идентификаторы и выполняет переход, если это возможно. Если переход невозможен, то метод вернет false
и будет вызвана родительская реализация метода super.onOptionsItemSelected(item)
.
Иногда перед разработчиком может встать задача передачи данных между экранами приложения и для этого в Android есть удобный механизм. SafeArgs — это Gradle-плагин, позволяющий реализовать передачу данных между фрагментами.
Далее будет рассмотрено добавление SafeArgs для простейшей задачи — отображения текстового сообщения об общем числе вопросов и числе правильных ответов на GameWonFragment
.
1. Добавление зависимости SafeArgs:
Сперва необходимо добавить зависимость от библиотеки SafeArgs в Gradle-файл проекта (не модуля app
).
dependencies {
...
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$version_navigation"
}
Затем необходимо включить плагин в Gradle-файл модуля app
.
apply plugin: 'androidx.navigation.safeargs'
После включения плагина необходимо очистить и пересобрать проект, чтобы компилятор сгенерировал "Directions"-классы, содержащие информацию о навигации между фрагментами (по типу идентификаторов action_gameFragment_to_gameWonFragment
), а также позволяющие организовать передачу данных между фрагментами.
2. Использование "Directions"-классов:
После того, как "Directions"-классы сгенерированы, можно заменить использование ссылок на идентификаторы навигации на использование этих классов.
// GameFragment: Using directions to navigate to the GameWonFragment
view.findNavController().navigate(GameFragmentDirections.actionGameFragmentToGameWonFragment())
// GameFragment: Using directions to navigate to the GameOverFragment
view.findNavController().navigate(GameFragmentDirections.actionGameFragmentToGameOverFragment())
По такому же принципу выполняется замена в остальных местах.
3. Добавление аргументов в редакторе навигации:
Далее необходимо описать для GameWonFragment
данные, которые он должен получать. В данном случае — это общее число вопросом numQuestions
и число правильных ответов numCorrect
. Оба числа целочисленные (integer
).
Для добавления описания параметров необходимо перейти в редактор навигации, выбрать gameWonFragment
и добавить аргументы в разделе "Arguments" панели "Attributes".
После этого сборка проекта будет невозможно, поскольку вызов GameFragmentDirections.actionGameFragmentToGameWonFragment()
будет требовать входных параметров, которые только что были описаны.
4. Передача данных фрагменту:
Чтобы передать данные фрагменту, необходимо передать их в вызов GameFragmentDirections.actionGameFragmentToGameWonFragment()
.
// GameFragment: Adding the parameters to the Action
view.findNavController().navigate(
GameFragmentDirections.actionGameFragmentToGameWonFragment(numQuestions, questionIndex))
Параметры numQuestions
и questionIndex
определяют общее число вопросов и индекс текущего вопроса, а в случае с выигрышем — это и число правильных ответов.
Теперь проект успешно собирается, данные передаются, но никак не принимаются и не обрабатываются.
5. Обработка полученных данных:
Переданные в GameWonFragment
данные отобразим на экране с помощью Toast
-сообщения. Для этого необходимо воспользоваться вызовом GameWonFragmentArgs.fromBundle(arguments)
для получения передаваемых данных.
"Args"-класс GameWonFragmentArgs
генерируется, так же как и "Directions"-классы, автоматически. Он предоставляет доступ к хранилищу Bundle
куда сохраняются передаваемые данные.
Далее для получения конкретных значений необходимо обратиться к данным по именам ранее переданных переменных.
// GameWonFragment.onCreate
val args = GameWonFragmentArgs.fromBundle(requireArguments())
Toast.makeText(context, "NumCorrect: ${args.numCorrect}, NumQuestions: ${args.numQuestions}",
Toast.LENGTH_LONG).show()
Таким образом при открытии GameWonFragment
на экране будет отображено Toast
-сообщение с полученными numCorrect
и numQuestions
.
Еще одним из основных элементов для навигации между экранами является боковое меню, также оно называется Navigation Drawer.
Далее будут рассмотрены шаги по добавлению бокового меню.
1. Добавление зависимости от библиотеки Material:
Navigation Drawer является частью библиотеки Material, поэтому ее необходимо включить в Gradle-файл:
implementation "com.google.android.material:material:$version_material"
2. Добавление XML-файла бокового меню:
Далее необходимо добавить XML-файл ресурса с описанием элементов бокового меню, аналогично тому, как это было сделано для ActioBar-меню. Файл будет называться navdrawer_menu.xml
и будет содержать один элемент "About".
Элемент меню должен иметь следующие атрибуты:
aboutFragment
— совпадает с идентификатором фрагмента, который должен быть открыт,@string/about
— ссылка на строковый ресурс,@drawable/android
— ссылка на ресурс с изображением, ресурс уже содержится в проекте.3. Добавление бокового меню на макет MainActivity
:
Для возможность использования Navigation Draver необходимо добавить компонент DrawerLayout
в активность и поместить внутрь этого компонента весь контент активности.
<androidx.drawerlayout.widget.DrawerLayout
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<fragment
android:id="@+id/navHostFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="@navigation/navigation"
app:defaultNavHost="true" />
</LinearLayout>
</androidx.drawerlayout.widget.DrawerLayout>
И затем добавить компонент NavigationView
сразу после определения LineatLayout
, внутри которого находится фрагмент.
<com.google.android.material.navigation.NavigationView
android:id="@+id/navView"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/nav_header"
app:menu="@menu/navdrawer_menu" />
Параметр headerLayout
определяет какой макет использовать в качестве заглавной части меню.
В параметре menu
задается ссылка на ресурс с меню, которое необходимо загрузить в NavigationView
.
4. Инициализация и настройка Navigation Drawer:
Для инициализации и настройки работы Navigation Drawer необходимо сперва создать поле drawerLayout
и инициализировать его в методе onCreate()
:
class MainActivity : AppCompatActivity() {
private lateinit var drawerLayout: DrawerLayout
override fun onCreate(savedInstanceState: Bundle?) {
...
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
drawerLayout = binding.drawerLayout
...
}
}
Для отображения кнопки бокового меню на Action Bar необходимо добавить экземпляр drawerLayout
в качестве третьего параметра вызова NavigationUI.setupActionBarWithNavController()
:
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
Для добавления возможности навигации, т.е. перехода к экрану "About" при нажатии на соответствующие элемент бокового меню, необходимо добавить вызов NavigationUI.setupWithNavController()
с передачей NavController
и элемента NavigationView
в качестве параметров:
NavigationUI.setupWithNavController(binding.navView, navController)
Для того, чтобы корректно обрабатывалось и открытие бокового меню и кнопка "Назад", которая отображается при переходе к следующим за начальным экранам, необходимо добавит в обработчике onSupportNavigateUp()
в вызов NavigationUI.navigateUp()
параметры navController
и drawerLayout
.
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(navController, drawerLayout)
}
В этом случае drawerLayout
будет использоваться, если пользователь находится на последнем фрагменте в стеке. А navController
будет использоваться для навигации в остальных случаях.
Интент — это объект, содержащий в себе запрос на выполнение некоторого действия.
Интент может включать в себя следующие компоненты:
Наиболее распространённый сценарий использования интентов — запуск другой активности. При этом это может быть как запуск активности собственного приложения, так и стороннего, например, открытие галереи, приложения почты или камеры и т.д.
Существует два вида интентов: явные (explicit) и неявные (implicit).
Explicit-интенты используются для перехода к активности в рамках собственного приложения. Они требуют указания полного имени класса для выполнения действия
val intent = Intent(MainActivity.this, SecondActivity::class.java)
startActivity(intent)
Первый параметр в конструкторе класса Intent
— ссылка на контекст (в данном случае на активность, из которой выполняется вызов).
Второй параметр — полное имя класса второй активности, которую необходимо открыть.
Также при переходе к другой активности можно поместить в интент некоторые данные.
val intent = Intent(MainActivity.this, SecondActivity::class.java)
intent.putExtra("KEY", "VALUE")
startActivity(intent)
Для получения значения из интента внутри SecondActivity
необходимо вызвать:
val value = getIntent().extras["KEY"]
Implicit-интенты используются для открытия сторонних активностей сторонних приложений и выполнения каких-либо действий.
Примеры: открытие приложения почты для написания нового письма и передача в него определенного текста; открытие приложения камеры для создания снимка и получения его в рамках приложения; открытие браузера с сайтом по определенной ссылке.
Для создания implicit-интента необходимо указать три параметра:
Примеры констант ACTION:
ACTION_CALL
— инициализирует обращение по телефону.ACTION_SEARCH
— запускает активность для выполнения поиска.ACTION_SEND
— запускает активность экран для отправки данных, указанных в поле data интента. Например, диалог для выбора приложения, в которое необходимо использовать в качестве "отправителя".ACTION_VIEW
— Наиболее распространённое общее действие. Используется для отображения какой-либо активности. Например, для открытия браузера.Примеры констант CATEGORY:
BROWSABLE
— активность может быть вызвана браузером, чтобы отобразить "ссылочные"" данные, например, изображение или почтовое сообщение.LAUNCHER
— активность может быть главной активностью, использующейся для запуска приложения в системе. Такую категорию имеет главная активность каждого приложения в Android.HOME
— активность отображает Home Screen, домашний экран, который пользователь видит после включения устройства и загрузки системы, или когда нажимает клавишу "HOME".Пример ниже демонстрирует создание интента для открытия ссылки и запуск активности, которая подойдет для выполнения действия данного интента. При наличии нескольких браузеров, пользователю будет предложено выбрать один из них.
val address: Uri = Uri.parse("http://developer.android.com")
val intent = Intent(Intent.ACTION_VIEW, address)
startActivity(intent)
Следующий пример демонстрирует создание интента для отправки некоторого текста.
В качестве действия (action) указывается ACTION_SEND
, что при вызове startAction()
позволит показать пользователю окно с возможностью выбора приложения для отправки сообщения (почтовые клиенты или мессенджеры).
Сообщение сохраняется в интенте под ключом EXTRA_TEXT
. Это стандартный ключ системы Android, именно он используется приложениями, которые могут принимать сообщения извне для дальнейшей отправки (EXTRA_TEXT).
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.setType("text/plain")
shareIntent.putExtra(Intent.EXTRA_TEXT, "This is a text to send via any sender")
startActivity(shareIntent)
Для демонстрации работы implicit-интентов будет добавлен функционал "Поделиться" с возможностью отправки данных, через сторонние приложения типа почтового клиента или мессенджера. В качестве сообщения будет использоваться добавленное ранее сообщения о количестве правильных ответов и общем числе вопросов.
Кнопка "Share" будет добавлена в ActionBar-меню GameWonFragment
. В проекте уже есть файл winner_menu.xml
, содержащий описание меню.
1. Настройка отображения меню на GameWonFragment
:
// GameWonFragment.onCreate
binding.nextMatchButton.setOnClickListener { view: View ->
view.findNavController().navigate(
GameWonFragmentDirections.actionGameWonFragmentToGameFragment())
}
setHasOptionsMenu(true)
2. Добавление метода для создания интента:
Для создания интента добавим метод getShareIntent()
. Метод будет получать экземпляр Bundle
с переданными данными (numCorrect
и numQuestions
) и создавать интент с действием ACTION_SEND
, текстовым типом данных и строкой с самими данными.
// Creating our Share Intent
private fun getShareIntent() : Intent {
val args = GameWonFragmentArgs.fromBundle(requireArguments())
val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.setType("text/plain")
.putExtra(Intent.EXTRA_TEXT, getString(R.string.share_success_text,
args.numCorrect, args.numQuestions))
return shareIntent
}
3. Обработка нажатия на меню "Share":
Для обработки нажатия необходимо переопределить метод onOptionsItemSelected
и по нажатию на пункт меню "Share" вызывать системную активность, передавая ей созданный интент.
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.share -> startActivity(getShareIntent())
}
return super.onOptionsItemSelected(item)
}
После запуска приложения можно убедиться, что по нажатию на пункт меню "Share" запускается системная активность, предлагающая выбрать приложение для отправки текстовых данных.
Если убрать установку типа "text/plain" для интента и запустить приложение снова, то после нажатия на пункт меню "Share" приложение упадет с сообщением о том, что активность для данного действия не найдена.
4. Добавление проверки на доступность активности:
Для того, чтобы решить проблему с падением, необходимо добавить проверку на доступность существующей активности для созданного интента. Если активности необходимой для интента нет в системе, то пункт меню "Share" логично будет скрыть.
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
super.onCreateOptionsMenu(menu, inflater)
inflater.inflate(R.menu.winner_menu, menu)
// check if the activity resolves
if (null == getShareIntent().resolveActivity(requireActivity().packageManager)) {
// hide the menu item if it doesn't resolve
menu.findItem(R.id.share).isVisible = false
}
}
Если запустить приложение, можно убедиться, что пункт меню "Share" не отображается на экране GameWonFragment
.