
Каждый Android-разработчик сталкивается с задачей обучения пользователей новым функциям или помощи в навигации по интерфейсу. Традиционные всплывающие окна или сообщения могут быть навязчивыми. Конечно же есть способ элегантно подсвечивать элементы UI и предоставлять контекстную помощь. Встречайте TAO Bubbles – легковесную библиотеку для Jetpack Compose, созданную для отображения настраиваемых "пузырей", "подсказок" или "тултипов", которые могут указывать на конкретные UI-компоненты.TAO Bubbles прекрасно подходит для создания пошаговых руководств, демонстрации новых возможностей или предоставления контекстно-зависимой справки прямо в вашем приложении.
P.S. Проект создавался во время изучения Jetpack Compose, так что конструктивная критика и pull requests с улучшениями приветствуется ).
Ключевые особенности TAO Bubbles для Jetpack Compose
Полная кастомизация внешнего вида: Управляйте положением стрелки, размером, радиусом углов, цветами, границами и многим другим. Вы можете легко адаптировать стиль подсказок под дизайн вашего приложения. Внутри можно расположить любой ваш макет Composable.
Гибкое позиционирование: Подсказки могут указывать на любую сторону целевого composable-компонента (LEFT, RIGHT, TOP, BOTTOM).
Адаптация к границам экрана: пузыри Bubble автоматически корректируют свое положение, чтобы оставаться в пределах видимости экрана, обеспечивая отличный пользовательский опыт на любых устройствах.
Последовательный показ: Используйте BubbleShowController для легкого отображения серии подсказок одна за другой, проводя пользователя через несколько шагов.
Анимации появления/исчезновения: Плавные и настраиваемые анимации для появления и исчезновения подсказок делают взаимодействие более живым.
Затемнение фона (Scrim): Опциональный слой затемнения фона помогает сфокусировать внимание пользователя на подсказке.
Декларативный API: Библиотека разработана с учетом современных практик Jetpack Compose, предлагая простой и интуитивно понятный API.
Легкое использование: Никаких сложных конструкций. Добавление к существующему проекту очень простое.

Как это работает?
Пример использования (одиночный Bubble):
Определяете общие стили для всех ваших "пузырей" или для конкретной группы.
val settings = BubblesSettings(
scrimColor = Color(0x22002EFF), // Цвет затемнения фона
backgroundColor = OrangeVeryLight, // Цвет фона подсказки
bubbleBorderColor = Color.Black, // Цвет рамки
bubbleBorderWidth = 2.dp // Толщина рамки
... и так далее
)
Подготавливаете данные для каждой подсказки.
val bubbleData = BubbleData(
id = "Intro_Feature", // Уникальный ID для сохранения состояния (показана/скрыта)
arrowPosition = ArrowPosition.BOTTOM, // С какой стороны целевого объекта прилегает стрелка и подсказка
content = { onDismissClick, onStopShowRequest ->
// Здесь размещается любой ваш Composable-контент для подсказки
// onDismissClick - функция вызываемая по закрытию подсказки
// onStopShowRequest - функция для остановки всей последовательности (если используется BubbleShowController)
Text("Это важная новая кнопка!")
}
)
И используете эти данные в своем коде:
@Composable
fun SingleBubbleExample() {
var showBubble by remember { mutableStateOf(true) }
var targetRect by remember { mutableStateOf<Rect?>(null) } // Rect целевого компонента
Box(modifier = Modifier.fillMaxSize()) {
Button(
onClick = { showBubble = !showBubble },
modifier = Modifier
.align(Alignment.Center)
.onGloballyPositioned { coordinates ->
// Получаем геометрию компонента, к которому будет привязана подсказка
targetRect = coordinates.boundsInWindow()
}
) {
Text("Показать подсказку")
}
Bubble(
targetComponentRect = targetRect, // Передаем Rect цели
bubbleData = bubbleData, // Наши данные для подсказки
settings = settings, // Наши настройки стиля
isVisible = showBubble, // Управляем видимостью
onDismissRequest = { showBubble = false } // Действие при закрытии
)
}
}
Настройка показа последовательных подсказок.
Показ большого количества подсказок не сильно сложнее. Для настройки используем BubbleShowController.
Подготавливаем список (или по одному) BubbleData для каждой подсказки:
val testBubbles = listOf(
BubbleData(
id = "Step_1",
arrowPosition = ArrowPosition.BOTTOM,
content = { onDismissClick, onStopShowRequest ->
MyContent("Подсказка 1: Нажмите здесь", onDismissClick, onStopShowRequest)
}
),
BubbleData(
id = "Step_2",
arrowPosition = ArrowPosition.LEFT,
content = { onDismissClick, onStopShowRequest ->
MyContent("Подсказка 2: Затем проверьте это", onDismissClick, onStopShowRequest)
}
),
BubbleData(
id = "Step_3",
arrowPosition = ArrowPosition.RIGHT,
content = { onDismissClick, onStopShowRequest ->
MyContent("Подсказка 3: И наконец, сюда!", onDismissClick, onStopShowRequest)
}
)
)
// MyContent - ваш кастомный Composable для содержимого подсказки
@Composable
fun MyContent(text: String, onDismiss: () -> Unit, onStopShow: () -> Unit) {
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(text)
Button(onClick = onDismiss) { Text("Далее") }
// Button(onClick = onStopShow) { Text("Завершить тур") } // Если нужно
}
}
И используем подготовленные данные в основном коде связывая каждый BubbleData с целевым объектом :
@Composable
fun BubbleSequenceExample() {
// Подготовка показа
val bubblesSettings = remember { testSettings } // Используем ранее созданные настройки
val bubblesData = remember { testBubbles } // Используем ранее созданные данные для каждой подсказки. Но можно и по одному.
val bubbleShowController = rememberBubbleShowController( // Контроллер показа
settings = bubblesSettings,
bubbles = bubblesData,
onFinished = {
// Действия после завершения всех подсказок
Log.d("BubbleShow", "Все подсказки показаны!")
}
)
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0xFFE0F7FA))
) {
// Ваши UI компоненты, к которым будут привязаны подсказки
Box(
modifier = Modifier
.align(Alignment.TopStart)
.padding(16.dp)
.size(100.dp)
// Связываем компонент с данными подсказки через контроллер
.assignBubble(controller = bubbleShowController, bubbleData = bubblesData[0]),
contentAlignment = Alignment.Center
) {
Text("Элемент 1", color = Color.Black)
}
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(16.dp)
.size(100.dp)
.assignBubble(controller = bubbleShowController, bubbleData = bubblesData[1]),
contentAlignment = Alignment.Center
) {
Text("Элемент 2", color = Color.Black)
}
Box(
modifier = Modifier
.align(Alignment.BottomStart)
.padding(16.dp)
.size(100.dp)
.assignBubble(controller = bubbleShowController, bubbleData = bubblesData[2]),
contentAlignment = Alignment.Center
) {
Text("Элемент 3", color = Color.Black)
}
// Кнопка для перезапуска показа
Button(
modifier = Modifier
.align(Alignment.Center)
.padding(top = 224.dp),
onClick = {
bubbleShowController.restartShow()
}) {
Text("Перезапустить показ")
}
// Отображаем текущую подсказку из контроллера
// Composable Bubble с контроллером автоматически управляет видимостью и настройками.
bubbleShowController.ShowBubbles()
}
}
Модификатор .assignBubble() связывает ваши UI-компоненты с соответствующими данными подсказок в контроллере. Контроллер затем сам позаботится об определении targetComponentRect для каждой подсказки.
Нужно больше настроек?
Если вы предпочитаете иметь полный контроль и кастомизацию каждого пузыря в списке для показа, то используйте BubbleShowControllerExtended для контроля и BubbleDataExtended для настройки каждого пузыря.
Заключение
TAO Bubbles для Jetpack Compose – это простой, но мощный инструмент для создания интерактивных подсказок и туров по приложению. Благодаря гибкой настройке и удобному API, вы сможете значительно улучшить пользовательский опыт, помогая пользователям осваивать функционал вашего приложения легко и непринужденно.
Попробуйте TAO Bubbles в своем следующем проекте!
Скачать последний релиз и код демо-приложения https://github.com/lordtao/android-tao-bubbles
Лицензия MIT