- Диалоговые окна
- Общая информация
- Класс Dialog
- Методы onCreateDialog() и onPrepareDialog()
- Диалоговое окно AlertDialog
- AlertDialog с ссылкой
- Использование Android Search Dialog. Часть 3 — Custom Suggestions
- Теория
- Изменяем конфигурационный файл
- Создаем контент-провайдер
- Создание таблицы подсказок
- Определение типа данных для Intent
- Создание Activity для отображения информации
- Перехват Intent в Activity, отвечающем за поиск
- Navigating to Dialog Destinations
- Navigation component isn’t limited to destinations inside of NavHostFragment
- Introduction
- Donut Tracking
- Start from a Template
- Dialog Destination
- DonutTracker: the App
- Summary
- For More Information
Диалоговые окна
Начиная с Android 3.0, работа с диалоговыми окнами чуть изменилась. С тех пор статья не переписывалась и могла морально устареть.
Общая информация
В некоторых случаях требуется показать диалоговое окно, где пользователю нужно сделать какой-нибудь выбор или показать сообщение об ошибке. Безусловно, можно создать собственное окно, расположить в нем нужные кнопки и обрабатывать их нажатия. Но, в Android уже есть собственные встроенные диалоговые окна, которые гибко настраиваются под задачи. Использование диалоговых окон для простых задач позволяет сократить число классов Activity в приложении, экономя ресурсы памяти. Ведь вам не придётся регистрировать активность в манифесте, думать над компоновкой элементов на экране и так далее.
Диалоговые окна в Android представляют собой полупрозрачные «плавающие» активности, частично перекрывающие родительский экран, из которого их вызвали. Как правило, они затеняют родительскую активность позади себя с помощью фильтров размывания или затемнения. Вы можете установить заголовок с помощью метода setTitle() и содержимое с помощью метода setContentView().
Android поддерживает следующие типы диалоговых окон:
- Dialog — базовый класс для всех типов диалоговых окон;
- AlertDialog — диалоговое окно с кнопками, списком, флажками или переключателями;
- CharacterPickerDialog — диалоговое окно, позволяющее выбрать символ с ударением, связанный с базовым символом;
- ProgressDiaiog — диалоговое окно с индикатором прогресса при помощи компонента ProgressBar. В API 26 признан устаревшим.
- DatePickerDialog — диалоговое окно выбора даты с элементом DatePicker
- TimePickerDialog — диалоговое окно выбора времени с элементом TimePicker
Если ни один из существующих типов диалоговых окон вам не подходит, то можете создать своё собственное диалоговое окно.
Класс Dialog
Класс Dialog является базовым для всех классов диалоговых окон. Поскольку ProgressDialog, TimePickerDialog И DatePickerDialog — расширение класса AlertDialog, они также могут иметь командные кнопки.
Каждое диалоговое окно должно быть определено внутри активности или фрагмента, в которых будет использоваться. Диалоговое окно можно открыть один раз или несколько раз.
Для отображения диалогового окна необходимо вызвать метод showDialog() и передать ему в качестве параметра идентификатор диалога (константа, которую надо объявить в коде программы), который вы хотите отобразить.
Метод dismissDialog() прячет диалоговое окно (но не удаляет), не отображая его на экране. Окно остаётся в пуле диалоговых окон данной активности. При повторном отображении при помощи метода showDialog() будет использована кэшированная версия окна.
Метод removeDialog() удаляет диалоговое окно из пула окон данной активности. При повторном вызове метода showDialog() диалоговое окно придётся создавать снова.
Рассмотрим базовый пример создания диалогового окна на основе класса Dialog. Создайте простейшую разметку для диалогового окна — текстовое поле внутри LinearLayout. В разметку главной активности добавьте кнопку для вызова диалогового окна. В коде для главной активности напишем:
По умолчанию при показе диалогового окна главная активность затемняется. В документации есть константы, позволяющие управлять степенью затемнения:
На эмуляторе я не заметил разницы. В старой версии Android 2.3 был ещё эффект размытия WindowManager.LayoutParams.FLAG_BLUR_BEHIND, который теперь считается устаревшим. Если вы по упрямству всё равно пропишите данный эффект, то получите не эффект размытия, а чёрный фон. Кто знает, может вас устроит данный вариант.
Методы onCreateDialog() и onPrepareDialog()
Метод onCreateDialog() вызывается один раз при создании окна. После начального создания при каждом вызове метода showDialog() будет срабатывать обработчик onPrepareDialog(). Переопределив этот метод, вы можете изменять диалоговое окно при каждом его выводе на экран. Это позволит привнести контекст в любое из отображаемых значений. Если требуется перед каждым вызовом диалогового окна изменять его свойства (например, текстовое сообщение или количество кнопок), то можно реализовать внутри этого метода. В этот метод передают идентификатор диалога и сам объект Dialog, который был создан в методе onCreateDialog().
Так как в одном приложении может быть несколько диалоговых окон, то необходимо заранее определить диалоговое окно, которое будет использоваться в активности. Для этого создаётся идентификатор (константа с целым числом). При вызове метода showDialog() вы передаёте данный идентификатор диалогового окна в качестве параметра. После этого идёт вызов метода onCreateDialog(), который возвращает экземпляр нужного диалогового окна.
Можно создавать диалог без onCreateDialog(), например в обработчике нажатия кнопки вызова диалога, но тогда он не будет присоединён к текущей активности. Чтобы прикрепить его к активности, необходимо вызвать метод setOwnerActivity(), передав ему в качестве параметра текущую активность.
Перейдём к примеру. Если в активности должны вызываться несколько различных диалоговых окон, сначала необходимо определить целочисленный идентификатор для каждого диалога, например:
Эти идентификаторы потом можно использовать в вызове метода showDialog() и в обработчике события onCreateDialog() в операторе switch:
Следует отметить, что методы Activity.onCreateDialog() и Activity.onPrepareDialog() устарели. Используйте DialogFragment.
Диалоговое окно AlertDialog
Примеры создания диалоговых окон типа AlertDialog рассмотрены в этой статье
AlertDialog с ссылкой
Практического смысла возможно не имеет, но можно сделать текст сообщения ссылкой.
Источник
Использование Android Search Dialog. Часть 3 — Custom Suggestions
Это заключительная статья по использованию Android Search Dialog (предыдущие находятся здесь и здесь). В ней я расскажу, как добавить в диалог динамические подсказки к поиску, а также, как интегрировать поиск по вашему приложению в системный Quick Search Box (QSB). Преимущество QSB в том, что с его помощью можно получать информацию из практически любого места в OS.
Теория
Подсказки к поиску создаются при помощи данных вашего приложения, по которым осуществляется поиск. Когда пользователь выбирает одну из них, то Search Manager посылает Intent к Activity, которое отвечает за поиск. Обычно, когда пользователь нажимает иконку поиска в диалоге, то отправляется Intent типа Search, однако, при выборе подсказки в данном случае можно определить другой тип Intent, так чтобы мы могли его перехватить и совершить соответствующие действия, например, создание нового диалога, или вызов Activity для отображения информации и т.д.
Данные поискового запроса переносятся через Intent как и раньше, однако теперь мы будем использовать URI, чтобы определять тип запроса через контент-провайдер.
Снова, нам не нужно производить никаких действий по отрисовке диалога, этим занимается Search Manager, всё, что от нас требуется — представить конфигурационный xml файл.
Итак, когда Search Manager определяет наше Activity как отвечающее за поиск и обеспечивающее подсказки к поиску, то происходит следующая последовательность действий:
- Когда Search Manager получает текст поискового запроса, то он отправляет свой запрос к контент-провайдеру, обеспечивающему подсказки.
- Контент-провайдер возвращает курсор, указывающий на подсказки, которые совпадают с текстом поискового запроса.
- Search Manager отображает подсказки, используя курсор
После того как список подсказок был отображен, может случиться следующее:
- Если пользователь изменяет текст запроса, то все вышеперечисленные шаги повторятся.
- Если пользователь запускает поиск, то подсказки игнорируются, и к Activity отправляется Intent типа Search.
- Если пользователь выбирает подсказку, то к Activity доставляется Intent другого типа (тип определяется в конфигурационном файле), переносящий URI в качестве данных. URI будет использоваться для поиска записи в таблице, соответствующей выбранной подсказке.
Итак, мы модифицируем наше приложение (то которое рассматривалось в части 1) так, чтобы добавлялись динамические подсказки, причем, для отработки механизма, при выборе подсказки будем вызывать новое Activity, которое будет отображать информацию по запросу. Для реализации потребуется:
- Изменить конфигурационный файл диалога, добавив к нему информацию о контент-провайдере и типе Intent, используемом для подсказок
- Создать таблицу в БД SQLite, которая будет предоставлять столбцы, требуемые Search Manager’ом для подсказок
- Создать новый контент-провайдер, имеющий доступ к таблице подсказок, и определить его в манифесте
- Добавить Activity, которое будет отображать информацию при выборе подсказок
Изменяем конфигурационный файл
Напоминаю, что конфигурационный файл (res/xml/searchable.xml) требуется для отображения диалога и его изменения, например, для использования голосового поиска. Чтобы использовать динамические подсказки, необходимо добавить в файл параметр: android:searchSuggestAuthority. Он будет совпадать со строкой авторизации контент-провайдера. Кроме этого добавим параметр android:searchMode=«queryRewriteFromText», его значение указывает на то, что строка поиска в диалоге будет перезаписываться при навигации по подсказкам, например с помощью трекбола. Также добавим параметры, задающие оператор выборки, тип Intent отправляемого при выборе подсказки, и минимальное количество напечатанных символов в диалоге, необходимое для запроса к контент-провайдеру.
Создаем контент-провайдер
По сути наш контент-провайдер ничем не отличается от других. Но нужно сделать так, чтобы для каждой строки из таблицы подсказок выбирались нужные столбцы, те которые требует Search Manager. Мы будем запрашивать данные по подсказкам с помощью метода контент-провайдера query(). Причем вызываться он будет каждый раз, когда пользователь печатает новый символ в диалоге. Таким образом, метод query() должен возвращать курсор на записи в таблице, совпадающие с запросом, и тогда Search Manager сможет отобразить подсказки. Смотрите описание метода в комментариях к коду.
Сам текст запроса будет дописываться к URI, так что с его получением проблем не будет, нужно просто использовать стандартный метод getLastPathSegment().
Создание таблицы подсказок
Когда Search Manager получает курсор, указывающий на записи, то он ожидает определенный набор столбцов для каждой записи. Обязательными являются два: _ID — уникальный идентификатор каждой подсказки, и SUGGEST_COLUMN_TEXT_1 — текст подсказки.
Необязательных столбцов существует много, например используя SUGGEST_COLUMN_ICON_1, вы можете определить для каждой записи иконку, отображаемую с левой стороны подсказки (очень удобно, например, для поиска по контактам).
Определение типа данных для Intent
Так как мы передаем данные по запросу через URI, то нам нужен механизм для определения того, какая подсказка была выбрана. Тут есть два пути. Первый, заключается в том, чтобы определить отдельный столбец SUGGEST_COLUMN_INTENT_DATA, в котором будут уникальные данные для каждой записи, тогда можно получать данные из Intent через getData() или getDataString(). Второй вариант — определить тип данных для всех Intent в конфигурационном файле (res/xml/searchable.xml) а потом дописывать к URI уникальные данные для каждого Intent, используя столбец SUGGEST_COLUMN_INTENT_DATA_ID.
Мы будем использовать второй вариант, причем отдельных столбцов в таблице я не создавал, так как можно просто создать отображение из SUGGEST_COLUMN_INTENT_DATA_ID в rowId таблицы. Добавлю еще, что ради спортивного интереса в SQLite для поиска использовался FTS3, то есть пришлось создавать виртуальную таблицу, для которой нельзя накладывать ограничения на столбцы (constraints), такие как PRIMARY KEY или NULL/NOT NULL. Зато у виртуальных таблиц есть уникальный идентификатор строки, на него и установим отображение. То есть data для Intent будет иметь следующий вид: к URI будет дописываться «/» и rowId строки в таблице.
Создание Activity для отображения информации
Интерфейс находится в res/layout/record_activity.xml. Всё чем занимается Activity — получение данных из Intent, запрос курсора через контент-провайдер и отображение записи в текстовом поле.
Теперь внесем информацию о контент-провайдере и новом Activity в манифест, также, так как у нас теперь два Activity, то укажем то, которое по умолчанию отвечает за поиск.
Перехват Intent в Activity, отвечающем за поиск
После всех вышеперечисленных шагов нужно обработать Intent в главном Activity, которое отвечает за поиск. Так как мы определили тип Intent для подсказок как View, то нужно просто добавить проверку на него. В случае если условие выполнится, то запускается RecordActivity, используя Intent, в данные которого записывается URI + «/» + id подсказки в таблице.
Источник
Navigating to Dialog Destinations
Navigation component isn’t limited to destinations inside of NavHostFragment
This is the second in a series of MAD Skills articles about the Navigation component. These articles are based on content that is also explained in video form, as part of the MAD Skills series, so feel free to consume this material in whichever way you prefer (though the code tends to be easier to copy from text than from a video, which is why we offer this version as well).
If you prefer your content in video form, here’s the thing to watch:
Introduction
In the previous episode, I presented a quick overview of Navigation component, including using the navigation graph.
In this episode, I will explore how to use the API to navigate to dialog destinations. Most navigation takes place between different fragment destinations, which are swapped out inside of the NavHostFragment object in the UI. But it is also possible to navigate to destinations outside of that container, including dialogs. We can do this using the navigation graph tool, just like we do for normal destinations.
Donut Tracking
I have a problem: I like donuts. A lot.
I’d like to know which donuts I’ve enjoyed so that I can get them again — and which ones I didn’t, so that I can avoid them. But I have a bad memory. So how can I keep track of this important data?
I know: I’ll use an app!
Unfortunately, I wasn’t able to find a donut tracking app on the Play Store (can you believe it. ). So I’ll need to write it myself. The app will have a list of donuts I’ve had, along with information that I record about each of them them, like their name, a description, maybe a picture, and definitely a rating.
It’s going to be a pretty simple app consisting of two screens:
- A list of donuts
- A form where I can enter information about a donut, either a new one that I am adding to the list or information about an existing donut in the list which I am editing
For the information-editing screen, I’d like to use a dialog. I want something lightweight that pops up on top of the activity without replacing my whole UI. I know that the Navigation component handles destinations, but those are just fragments that get swapped out inside the single NavHostFragment , right?
Yes… and no. The default behavior with Navigation component swaps out fragments inside NavHostFragment . But Navigation component also handles dialog destinations, which reside outside of the NavHostFragment .
Start from a Template
First I’ll show how to set up the basics of navigation in a new application. Then I’ll show the donut tracking app that I wrote so you can see where it’s all leading. ( I call this the Julia Child trick. In her cooking show many years ago, Ms. Child would start the recipe and then quickly follow that up by pulling out the finished dish, skipping that tedious part in the middle where she wrote all of the code to prep and cook the food).
In Android Studio 3.6 and later, you can select one of the New Project templates to give yourself a head start with the Navigation component. I find this helpful, even when my final UI isn’t going to resemble whatever I start with in the template app, because the templates handle the work of pulling in the proper dependencies and creating the infrastructure code and resources.
Let’s do that now and create a Basic Activity in Android Studio. I walked through some of this in the previous article or video, so check that out for more details and visuals if you’d like. Meanwhile, I’ll skip ahead to the next step.
Dialog Destination
When you look at the navigation graph for our basic activity, you can see that the app has 2 destinations, along with actions to take us between those destinations. These destinations are fragments, which will be swapped in and out inside the NavHostFragment that the template created for us.
This is almost what we need… except the destination we actually want is a dialog in which we will enter details about our donuts. In order to create that destination, let’s first create the dialog class that we need.
First, we’ll create the layout with just a placeholder item in the UI. Create a file called my_dialog.xml in the layout resources directory. In that layout, add a TextView and constrain it to all four sides to center it within that container. The result should look something like this:
Next, create the Fragment which will inflate that layout. In the main package, create a new Kotlin file called MyDialog.kt . In that file, create MyDialog to be a subclass of BottomSheetDialogFragment and have it override onCreateView() to return the view inflated from the layout resource we created earlier:
Now that we have our dialog fragment, we can create a destination that will navigate to it. Go back to the navigation graph and add a destination. In the popup menu, you can see that it knows about MyDialog . Select that.
Observant readers might notice a little IDE bug in the screenshot above. Even though MyDialog is, in fact, a Dialog object, the navigation tool sometimes does not detect that, and adds it as a Fragment destination instead. This is definitely not what we want. It does not always happen (yay, unpredictable outcomes!), but it happened often enough while working on this project that I wanted to highlight it here in case it happens to you. Because… it’s pretty confusing. Fortunately, the workaround is simple, so knowing that it can happen is really the most important thing.
To fix this problem, if you see it, simply go into the XML code for the navigation graph and change the fragment tag to dialog instead. Here’s what my fixed code looks like after I fixed it:
[Aside: I asked the Android Studio team about this issue. It is apparently related to the order in which dependencies are searched internally. They are working on a fix.]
Now that we have a destination for the dialog, we can create an action to take us from the home destination to the dialog:
There’s one more step to make it possible to navigate to this dialog. For that, we’ll need to go into the code for FirstFragment. In that class, there is code (created by the Basic Activity template) which handles a button click by navigating to the SecondFragment destination:
We are simply going to change that to navigate to the dialog instead, by using the appropriate id, which was created for us when we created the destination in the navigation graph:
And that’s it! Now we can run our app and see the results . When we click on the Button, it takes us to the dialog destination, as advertised:
Note that the dialog is showing up a lot smaller than it did in the design tool — that’s because there’s only that little TextView “Placeholder” content to contain and wrap around. But trust me — that’s our dialog.
What we’ve created so far is a very simplified version of what I want for my donut tracking app, just to show the basics of how to create and use a dialog destination. Now let’s take a look at the actual code in the donut app to see what it all looks like in practice.
DonutTracker: the App
Spoiler alert: I’ve already written the DonutTracker app. I’ll take you through the important pieces that show how I use dialog destination navigation.
First, here is the app’s navigation graph:
You can see there’s home destination as before, called donutList . This is the fragment which contains the list of donuts (in a RecyclerView ). I’ve also created a second destination, donutEntryDialogFragment , which is where the user edits donut information.
If we look at the code in DonutList , which is the fragment containing the RecyclerView for that list of data, we can see how the navigation is handled. A click on the FloatingActionButton (FAB) causes navigation to the dialog
Note that I’m using View Binding here to get the reference to the FloatingActionButton , thus the reference to binding.fab .
Elsewhere in that file, we can also see how clicking on one of the items in the RecyclerView navigates us to the dialog to edit the information for that item:
There are a couple of things to note in the code snippets above.
First, the syntax of using the navigate() function here (navigating using a Directions object) is slightly different than what we saw earlier in the Basic Activity we created (where we navigated to an action, specified by R.id.action_FirstFragment_to_myDialog ). This is because we are looking at the code for the final version of the DonutTracker app, which uses SafeArgs . SafeArgs generates the Directions code to make it easier to navigate between destinations with arguments.
Second, there is a difference in the call to navigate() when we navigate from the FAB (where we pass no argument to the Directions object) from when we navigate from one of the donut items in the list (where we call it with donut.id ). This difference allows us to either create a new donut item (when passing no argument) or to edit an existing item(when passing donut.id ). ( Spoiler alert: I’ll cover this topic in an upcoming episode. You can also take a look at the complete code in the meantime.)
Running the app shows how it works. I’ve already pre-populated the app with important donut data, as you can see:
Clicking on the FAB takes us to the dialog where we can enter information about a new donut:
But if we click on one of the existing items (here I’ve clicked on “fundonut” because I obviously needed a better description), that takes us to the same dialog destination, where we can edit the data for the clicked-on item:
Clicking on the DONE button saves the changes to the database and returns to the list, while CANCEL abandons the edits and returns. Note that clicking the Back button also takes us back to the donut list, because Navigation component sets up that back stack for us automatically to do the right thing.
Summary
That’s it for this time. We saw how to quickly create a new app with Navigation component built in, and how to navigate to a dialog destination. In future episodes, we’ll continue working with this truly important app to show other capabilities of Navigation component … and to build a more powerful donut tracking application, of course.
For More Information
For more details on Navigation component, check out the guide Get started with the Navigation component on developer.android.com.
To see the finished DonutTracker app (which contains the code outlined above, but also the code covered in future episodes), check out the GitHub sample.
Finally, to see other content in the MAD Skills series, check out the video playlist in the Android Developers channel on YouTube.
Источник