1. Введение
Для Android версии 10 или более поздней версии жесты навигации поддерживаются как новый режим. Это позволит вашему приложению использовать весь экран и обеспечить более захватывающий опыт отображения. Когда пользователь проводит пальцем вверх от нижнего края экрана, он попадает на главный экран Android. Когда они проводят пальцем внутрь от левого или правого края, пользователь переходит на предыдущий экран.
С помощью этих двух жестов ваше приложение может использовать все пространство экрана в нижней части экрана. Однако если ваше приложение использует жесты или имеет элементы управления в системных областях жестов, это может привести к конфликту с общесистемными жестами.
Цель этой лаборатории кода — научить вас использовать вставки, чтобы избежать конфликтов жестов. Кроме того, эта лаборатория призвана научить вас использовать API исключения жестов для элементов управления, таких как маркеры перетаскивания, которые должны находиться в зонах жестов.
Что вы узнаете
- Как использовать вставленные прослушиватели для представлений
- Как использовать API исключения жестов
- Как ведет себя режим погружения, когда жесты активны
Цель этой лаборатории кода — сделать ваше приложение совместимым с системными жестами. Ненужные концепции и блоки кода замалчиваются и предоставляются для копирования и вставки.
Что ты построишь
Универсальный музыкальный проигрыватель Android (UAMP) — это пример приложения музыкального проигрывателя для Android, написанного на Kotlin. Вы настроите UAMP для навигации с помощью жестов.
- Используйте вставки, чтобы переместить элементы управления из областей жестов.
- Используйте API исключения жестов, чтобы отказаться от жеста назад для конфликтующих элементов управления.
- Используйте свои сборки, чтобы исследовать изменения поведения в режиме погружения с помощью навигации по жестам.
Что вам понадобится
- Устройство или эмулятор под управлением Android 10 или более поздней версии.
- Android-студия
2. Обзор приложения
Универсальный музыкальный проигрыватель Android (UAMP) — это пример приложения музыкального проигрывателя для Android, написанного на Kotlin. Он поддерживает такие функции, как фоновое воспроизведение, управление фокусом звука, интеграцию с Ассистентом и несколько платформ, таких как Wear, TV и Auto.
Рисунок 1. Поток в UAMP.
UAMP загружает музыкальный каталог с удаленного сервера и позволяет пользователю просматривать альбомы и песни. Пользователь нажимает на песню, и она воспроизводится через подключенные динамики или наушники. Приложение не предназначено для работы с системными жестами. Поэтому, когда вы запускаете UAMP на устройстве под управлением Android 10 или более поздней версии, изначально возникают некоторые проблемы.
3. Настройте
Чтобы получить пример приложения, клонируйте репозиторий с GitHub и переключитесь на стартовую ветку:
$ git clone https://github.com/googlecodelabs/android-gestural-navigation/
Альтернативно вы можете загрузить репозиторий в виде zip-файла, разархивировать его и открыть в Android Studio.
Выполните следующие шаги:
- Откройте и создайте приложение в Android Studio.
- Создайте новое виртуальное устройство и выберите уровень API 29 . Альтернативно вы можете подключить реальное устройство с API уровня 29 или выше.
- Запустите приложение. Список, который вы видите, группирует песни в разделах « Рекомендуемые» и «Альбомы» .
- Нажмите «Рекомендовано» и выберите песню из списка песен.
- Приложение начнет воспроизведение песни.
Включить навигацию с помощью жестов
Если вы запустите новый экземпляр эмулятора с уровнем API 29, навигация с помощью жестов может быть не включена по умолчанию. Чтобы включить навигацию с помощью жестов, выберите «Настройки системы» > «Система» > «Навигация по системе» > «Навигация с помощью жестов» .
Запустите приложение с помощью навигации по жестам
Если вы запустите приложение с включенной навигацией по жестам и начнете воспроизведение песни, вы можете заметить, что элементы управления плеером расположены очень близко к областям жестов «домой» и «назад».
4. Идите от края к краю
Что такое от края до края?
Приложения, работающие на Android 10 или более поздней версии, могут обеспечивать полноценный экран от края до края, независимо от того, включены ли жесты или кнопки для навигации. Чтобы обеспечить бесперебойную работу, ваши приложения должны располагаться за прозрачными панелями навигации и состояния.
Рисование за панелью навигации
Чтобы ваше приложение отображало содержимое под панелью навигации, необходимо сначала сделать фон панели навигации прозрачным. Затем необходимо сделать строку состояния прозрачной. Это позволяет вашему приложению отображать ваше приложение на всю высоту экрана.
Чтобы изменить цвет панели навигации и строки состояния, выполните следующие действия:
- Панель навигации: откройте
res/values-29/styles.xml
и задайтеnavigationBarColor
значениеcolor/transparent
. - Строка состояния: аналогично установите для
statusBarColor
значениеcolor/transparent
.
Просмотрите следующий пример кода res/values-29/styles.xml
:
<!-- change navigation bar color -->
<item name="android:navigationBarColor">
@android:color/transparent
</item>
<!-- change status bar color -->
<item name="android:statusBarColor">
@android:color/transparent
</item>
Флаги видимости системного пользовательского интерфейса
Вы также должны установить флаги видимости системного пользовательского интерфейса, чтобы указать системе размещать приложение под системными панелями. API-интерфейсы systemUiVisibility
класса View
позволяют устанавливать различные флаги. Выполните следующие шаги:
- Откройте класс
MainActivity.kt
и найдите методonCreate()
. Получите экземплярfragmentContainer
. - Установите для
content.systemUiVisibility
следующее:
-
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
-
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
-
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
Просмотрите следующий пример кода MainActivity.kt
:
val content: FrameLayout = findViewById(R.id.fragmentContainer)
content.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
Когда вы устанавливаете эти флаги вместе, вы сообщаете системе, что хотите, чтобы ваше приложение отображалось в полноэкранном режиме, как будто панели навигации и строки состояния отсутствуют. Выполните следующие шаги:
- Запустите приложение и перейдите на экран проигрывателя и выберите песню для воспроизведения.
- Убедитесь, что элементы управления плеером расположены под панелью навигации, что затрудняет доступ к ним:
- Перейдите к настройкам системы, вернитесь в режим трехкнопочной навигации и вернитесь в приложение.
- Убедитесь, что элементы управления с помощью трехкнопочной панели навигации еще сложнее использовать: обратите внимание, что
SeekBar
скрыта за панелью навигации, а воспроизведение/пауза в основном закрыта панелью навигации. - Исследуйте и немного поэкспериментируйте. После завершения перейдите к настройкам системы и вернитесь к навигации с помощью жестов:
Приложение теперь прорисовывается от края до края, но есть проблемы с удобством использования, элементы управления приложения конфликтуют и перекрываются, и их необходимо решить.
5. Вставки
WindowInsets
сообщает приложению, где системный пользовательский интерфейс отображается поверх вашего контента, а также какие области экрана. Системные жесты имеют приоритет над жестами в приложении. Вставки представлены классом WindowInsets
и классом WindowInsetsCompat
в Jetpack . Мы настоятельно рекомендуем использовать WindowInsetsCompat
для обеспечения единообразного поведения на всех уровнях API.
Системные вставки и обязательные системные вставки
Следующие API вставки являются наиболее часто используемыми типами вставки:
- Вставки системных окон: они сообщают вам, где в вашем приложении отображается системный пользовательский интерфейс. Мы обсуждаем, как можно использовать системные вставки, чтобы переместить элементы управления за пределы системных панелей.
- Системные вставки жестов: они возвращают все области жестов. Любые элементы управления смахиванием в приложении в этих областях могут случайно вызвать системные жесты.
- Обязательные вставки жестов: они являются подмножеством системных вставок жестов и не могут быть переопределены. Они сообщают вам области экрана, где поведение системных жестов всегда будет иметь приоритет над жестами в приложении.
Используйте вставки для перемещения элементов управления приложения
Теперь, когда вы знаете больше о встроенных API, вы можете исправить элементы управления приложения, как описано в следующих шагах:
- Получите экземпляр
playerLayout
из экземпляра объектаview
. - Добавьте
OnApplyWindowInsetsListener
вplayerView
. - Переместите представление из области жестов: найдите системное значение вставки для нижней части и увеличьте отступы представления на эту величину. Чтобы соответствующим образом обновить отступ представления, к [значению, связанному с нижним отступом приложения], добавьте [значение, связанное с нижним значением вставки системы].
Просмотрите следующий пример кода NowPlayingFragment.kt
:
playerView = view.findViewById(R.id.playerLayout)
playerView.setOnApplyWindowInsetsListener { view, insets ->
view.updatePadding(
bottom = insets.systemWindowInsetBottom + view.paddingBottom
)
insets
}
- Запустите приложение и выберите песню. Обратите внимание, что в элементах управления плеером ничего не меняется. Если вы добавите точку останова и запустите приложение в режиме отладки, вы увидите, что прослушиватель не вызывается.
- Чтобы это исправить, переключитесь на
FragmentContainerView
, который автоматически решит эту проблему. Откройтеactivity_main.xml
и изменитеFrameLayout
наFragmentContainerView
.
Просмотрите следующий пример кода activity_main.xml
:
<androidx.fragment.app.FragmentContainerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/fragmentContainer"
tools:context="com.example.android.uamp.MainActivity"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
- Запустите приложение еще раз и перейдите на экран проигрывателя. Нижние элементы управления проигрывателем смещены от нижней области жестов.
Элементы управления приложения теперь работают с навигацией с помощью жестов, но элементы управления перемещаются больше, чем ожидалось. Вы должны решить эту проблему.
Сохранять текущие отступы и поля
Если вы переключитесь на другие приложения или перейдете на главный экран и вернетесь в приложение, не закрывая его, обратите внимание, что элементы управления плеером каждый раз перемещаются вверх.
Это связано с тем, что приложение запускает requestApplyInsets()
каждый раз при запуске активности. Даже без этого вызова WindowInsets
может быть отправлен несколько раз в любой момент в течение жизненного цикла представления.
Текущий InsetListener
в playerView
отлично работает в первый раз, когда вы добавляете сумму нижнего значения вставки к значению нижнего заполнения приложения, объявленному в activity_main.xml
. Однако последующие вызовы продолжают добавлять нижнее значение вставки к нижнему отступу уже обновленного представления.
Чтобы исправить это, выполните следующие действия:
- Запишите начальное значение заполнения представления. Создайте новый val и сохраните начальное значение заполнения представления
playerView
непосредственно перед кодом прослушивателя.
Просмотрите следующий пример кода NowPlayingFragment.kt
:
val initialPadding = playerView.paddingBottom
- Используйте это начальное значение для обновления нижнего заполнения представления, что позволяет избежать использования текущего значения нижнего заполнения приложения.
Просмотрите следующий пример кода NowPlayingFragment.kt
:
playerView.setOnApplyWindowInsetsListener { view, insets ->
view.updatePadding(bottom = insets.systemWindowInsetBottom + initialPadding)
insets
}
- Запустите приложение еще раз. Перемещайтесь между приложениями и переходите на главный экран. Когда вы возвращаете приложение, элементы управления плеером находятся прямо над областью жестов.
Изменить дизайн элементов управления приложения
Полоса поиска плеера расположена слишком близко к нижней области жестов, что означает, что пользователь может случайно активировать домашний жест при горизонтальном пролистывании. Если вы увеличите отступ еще больше, это может решить проблему, но также может переместить игрока выше, чем хотелось бы.
Использование вставок позволяет исправить конфликты жестов, но иногда, внося небольшие изменения в дизайн, можно полностью избежать конфликтов жестов. Чтобы изменить дизайн элементов управления проигрывателя во избежание конфликтов жестов, выполните следующие действия:
- Откройте
fragment_nowplaying.xml
. Переключитесь в представление «Дизайн» и выберитеSeekBar
в самом низу:
- Переключитесь в режим просмотра кода.
- Чтобы переместить
SeekBar
в верхнюю частьplayerLayout
, изменитеlayout_constraintTop_toBottomOf
SeekBar наparent
. - Чтобы ограничить другие элементы
playerView
нижней частьюSeekBar
, изменитеlayout_constraintTop_toTopOf
с родительского на@+id/seekBar
вmedia_button
,title
иposition
.
Просмотрите следующий пример кода fragment_nowplaying.xml
:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="8dp"
android:layout_gravity="bottom"
android:background="@drawable/media_overlay_background"
android:id="@+id/playerLayout">
<ImageButton
android:id="@+id/media_button"
android:layout_width="@dimen/exo_media_button_width"
android:layout_height="@dimen/exo_media_button_height"
android:background="?attr/selectableItemBackground"
android:scaleType="centerInside"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="@+id/seekBar"
app:srcCompat="@drawable/ic_play_arrow_black_24dp"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="@dimen/text_margin"
android:layout_marginEnd="@dimen/text_margin"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Uamp.Title"
app:layout_constraintTop_toTopOf="@+id/seekBar"
app:layout_constraintLeft_toRightOf="@id/media_button"
app:layout_constraintRight_toLeftOf="@id/position"
tools:text="Song Title" />
<TextView
android:id="@+id/subtitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:layout_marginEnd="@dimen/text_margin"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Uamp.Subtitle"
app:layout_constraintTop_toBottomOf="@+id/title"
app:layout_constraintLeft_toRightOf="@id/media_button"
app:layout_constraintRight_toLeftOf="@id/position"
tools:text="Artist" />
<TextView
android:id="@+id/position"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginStart="@dimen/text_margin"
android:layout_marginEnd="@dimen/text_margin"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Uamp.Title"
app:layout_constraintTop_toTopOf="@+id/seekBar"
app:layout_constraintRight_toRightOf="parent"
tools:text="0:00" />
<TextView
android:id="@+id/duration"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:layout_marginEnd="@dimen/text_margin"
android:ellipsize="end"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.Uamp.Subtitle"
app:layout_constraintTop_toBottomOf="@id/position"
app:layout_constraintRight_toRightOf="parent"
tools:text="0:00" />
<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
- Запустите приложение и взаимодействуйте с плеером и панелью поиска.
Эти минимальные изменения дизайна значительно улучшают приложение.
6. API исключения жестов
Исправлены элементы управления плеером для конфликтов жестов в домашней области жестов. Область жестов назад также может создавать конфликты с элементами управления приложения. На следующем снимке экрана показано, что панель поиска игрока в настоящее время находится как в правой, так и в левой областях жестов:
SeekBar
автоматически обрабатывает конфликты жестов. Однако вам может потребоваться использовать другие компоненты пользовательского интерфейса, которые вызывают конфликты жестов. В этих случаях вы можете использовать Gesture Exclusion API
, чтобы частично отказаться от жеста назад.
Используйте API исключения жестов
Чтобы создать зону исключения жестов, вызовите setSystemGestureExclusionRects()
в своем представлении со списком rect
объектов. Эти rect
объекты сопоставляются с координатами исключенных прямоугольных областей. Этот вызов должен быть выполнен в методах onLayout()
или onDraw()
представления. Для этого выполните следующие шаги:
- Создайте новый пакет с именем
view
. - Чтобы вызвать этот API, создайте новый класс
MySeekBar
и расширьтеAppCompatSeekBar
.
Просмотрите следующий пример кода MySeekBar.kt
:
class MySeekBar @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = android.R.attr.seekBarStyle
) : androidx.appcompat.widget.AppCompatSeekBar(context, attrs, defStyle) {
}
- Создайте новый метод
updateGestureExclusion()
.
Просмотрите следующий пример кода MySeekBar.kt
:
private fun updateGestureExclusion() {
}
- Добавьте флажок, чтобы пропустить этот вызов на уровне API 28 или ниже.
Просмотрите следующий пример кода MySeekBar.kt
:
private fun updateGestureExclusion() {
// Skip this call if we're not running on Android 10+
if (Build.VERSION.SDK_INT < 29) return
}
- Поскольку API исключения жестов имеет ограничение в 200 dp, исключите только большой палец панели поиска. Получите копию границ панели поиска и добавьте каждый объект в изменяемый список.
Просмотрите следующий пример кода MySeekBar.kt
:
private val gestureExclusionRects = mutableListOf<Rect>()
private fun updateGestureExclusion() {
// Skip this call if we're not running on Android 10+
if (Build.VERSION.SDK_INT < 29) return
thumb?.also { t ->
gestureExclusionRects += t.copyBounds()
}
}
- Вызовите
systemGestureExclusionRects()
с помощью созданных вами списковgestureExclusionRects
.
Просмотрите следующий пример кода MySeekBar.kt
:
private val gestureExclusionRects = mutableListOf<Rect>()
private fun updateGestureExclusion() {
// Skip this call if we're not running on Android 10+
if (Build.VERSION.SDK_INT < 29) return
thumb?.also { t ->
gestureExclusionRects += t.copyBounds()
}
// Finally pass our updated list of rectangles to the system
systemGestureExclusionRects = gestureExclusionRects
}
- Вызовите метод
updateGestureExclusion()
изonDraw()
илиonLayout()
. ПереопределитеonDraw()
и добавьте вызовupdateGestureExclusion
.
Просмотрите следующий пример кода MySeekBar.kt
:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
updateGestureExclusion()
}
- Вы должны обновить ссылки
SeekBar
. Для начала откройтеfragment_nowplaying.xml
. - Измените
SeekBar
наcom.example.android.uamp.view.MySeekBar
.
Просмотрите следующий пример кода fragment_nowplaying.xml
:
<com.example.android.uamp.view.MySeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="parent" />
- Чтобы обновить ссылки
SeekBar
вNowPlayingFragment.kt
, откройтеNowPlayingFragment.kt
и измените типpositionSeekBar
наMySeekBar
. Чтобы соответствовать типу переменной, измените дженерикиSeekBar
для вызоваfindViewById
наMySeekBar
.
Просмотрите следующий пример кода NowPlayingFragment.kt
:
val positionSeekBar: MySeekBar = view.findViewById<MySeekBar>(
R.id.seekBar
).apply { progress = 0 }
- Запустите приложение и взаимодействуйте с
SeekBar
. Если конфликты жестов по-прежнему возникают, вы можете поэкспериментировать и изменить границы большого пальца вMySeekBar
. Будьте осторожны и не создавайте зону исключения жестов больше, чем необходимо, поскольку это ограничивает другие потенциальные вызовы исключения жестов и создает противоречивое поведение пользователя.
7. Поздравления
Поздравляем! Вы узнали, как избегать и решать конфликты с помощью системных жестов!
Вы сделали свое приложение полноэкранным, расширив его от края до края и использовав вставки для перемещения элементов управления приложения из зон жестов. Вы также узнали, как отключить жест возврата системы в элементах управления приложениями.
Теперь вы знаете ключевые шаги, необходимые для того, чтобы ваши приложения работали с системными жестами!
Дополнительные материалы
- WindowInsets — Слушатели макетов
- Навигация с помощью жестов: переход от края к краю
- Навигация с помощью жестов: обработка визуальных перекрытий
- Навигация по жестам: обработка конфликтов жестов
- Обеспечьте совместимость с навигацией с помощью жестов.