Всплывающие сообщения и Snackbar

Последнее обновление: 01.04.2024

Всплывающие сообщения, которые извещают пользователя о некоторых процессах в приложения и которые через некоторое время исчезают, являются обычным делом в приложении. И Jetpack Compose предоставляет для создания подобных сообщений встроенный функционал.

Snackbar

Ключевым компонентом для создания всплывающих сообщений является Snackbar, который предоставляет короткое сообщение, отображаемое внизу экрана. Данный компонент имеет две версии. Первая версия:

@Composable
fun Snackbar(
    modifier: Modifier = Modifier,
    action: (@Composable () -> Unit)? = null,
    dismissAction: (@Composable () -> Unit)? = null,
    actionOnNewLine: Boolean = false,
    shape: Shape = SnackbarDefaults.shape,
    containerColor: Color = SnackbarDefaults.color,
    contentColor: Color = SnackbarDefaults.contentColor,
    actionContentColor: Color = SnackbarDefaults.actionContentColor,
    dismissActionContentColor: Color = SnackbarDefaults.dismissActionContentColor,
    content: @Composable () -> Unit
): Unit

Параметры функции компонента:

  • modifier: представляет объект Modifier, который определяет модификаторы компонента

  • action: вложенный компонент (обычно текст), по нажатию на который компонент уведомляет систему, что необходимо выполнить некоторое действие.

  • dismissAction: дополнительный компонент, по нажатию на который выполняется некоторое действие.

  • actionOnNewLine: указывает, будет ли действие располагаться на новой строке.

  • shape: объект Shape, который задает форму компонента.

  • containerColor: фоновый цвет

  • contentColor: цвет содержимого.

  • actionContentColor: цвет компонента, который представляет основное действие и устанавливается через параметр action

  • dismissActionContentColor: цвет компонента, который устанавливается через dismissAction

  • content: содержимое компонента

Вторая версия Snackbar:

@Composable
fun Snackbar(
    snackbarData: SnackbarData,
    modifier: Modifier = Modifier,
    actionOnNewLine: Boolean = false,
    shape: Shape = SnackbarDefaults.shape,
    containerColor: Color = SnackbarDefaults.color,
    contentColor: Color = SnackbarDefaults.contentColor,
    actionColor: Color = SnackbarDefaults.actionColor,
    actionContentColor: Color = SnackbarDefaults.actionContentColor,
    dismissActionContentColor: Color = SnackbarDefaults.dismissActionContentColor
): Unit

Тут в пинципе применяются те же параметры, за исключением того, что содержимое Snackbar устанавливается с помощью параметра snackbarData, который представляет объект SnackbarData. SnackbarData - это интерфейс, который предоставляет ряд свойств и методов для управления сообщением:

  • Свойство visuals позволяет установить общую информацию с помощью следующих свойств:

    • Свойство message: текст сообщения

    • Свойство actionLabel: текстовая метка, нажатие на которую будет извещать систему, что надо выполнить некотоое действие

    • Свойство duration: время отображения сообщения, представляет объект SnackbarDuration

  • Метод performAction(): уведомляет систему, что произошло нажатие на метку, представленную параметром actionLabel

  • Метод dismiss(): уведомляет систему, что отображение сообщения завершилось без нажатия на метку из параметра actionLabel

В принципе мы можем определить Snackbar как обычный компонент и сами управлять им. Например:

Snackbar{
	Text("Загрузка завершена", fontSize = 22.sp)
}

Однако в реальности мы можем даже явным образом не создавать объект Snackbar для отображения сообщение, а воспользоваться функцией showSnackbar() объекта SnackbarHostState.

SnackbarHostState

Объект SnackbarHostState отображает или ставит в очередь для отображения компоненты Snackbar. SnackbarHostState гарантирует, что одномоментно только один объект Snackbar будет отображаться на экране.

Для отображения Snackbar в SnackbarHostState определена функция showSnackbar():

suspend fun showSnackbar(
    message: String,
    actionLabel: String? = null,
    withDismissAction: Boolean = false,
    duration: SnackbarDuration = if (actionLabel == null) SnackbarDuration.Short else SnackbarDuration.Indefinite
): SnackbarResult

Основные параметры

  • message: отображаемое в Snackbar сообщение

  • actionLabel: метка, которая отображается в виде кнопки в Snackbar

  • withDismissAction: указывает, надо ли отображать кнопку отмены. Если равно true, то Snackbar отображает кнопку отмены в виде крестика

  • duration: длительность отображения сообщения в виде объекта SnackbarDuration. Значение по умолчанию SnackbarDuration.Short. Другие возможные значения: SnackbarDuration.Long и SnackbarDuration.Indefinite

Фактически все эти параметры аналогичны свойствам объекта SnackbarDat.visuals.

В качестве результата функция возвращает объект SnackbarResult. Он может иметь следующие значения: SnackbarResult.ActionPerformed (если была нажата метка действия в Snackbar) и SnackbarResult.Dismissed (если Snackbar был скрыт из-за действий пользователя или из-за того, что окончилось его время отображения)

Итак, используем функцию showSnackbar() для отображения всплывающего сообщения:

package com.example.helloapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text

import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val snackbarHostState = remember { SnackbarHostState() }
            val scope = rememberCoroutineScope()
            val count = remember{ mutableStateOf(0) }
            Scaffold(
                snackbarHost = { SnackbarHost(snackbarHostState) },
                floatingActionButton = {
                    FloatingActionButton(
                        content = {Icon(Icons.Filled.Add, contentDescription = "Добавить")},
                        onClick = {
                            scope.launch {
                                snackbarHostState.showSnackbar("Count: ${++count.value}")
                            }
                        }
                    )
                }
            ){
                Text("Count: ${count.value}", fontSize = 28.sp, modifier=Modifier.padding(it))
            }
        }
    }
}

Здесь надо отметить несколько моментов. Во-первых, определяем оббъект состояния в виде SnackbarHostState:

val snackbarHostState = remember { SnackbarHostState() }

Во-вторых, функция showSnackbar() - это suspend-функция, которую необходимо вызывать в рамках корутины. Для создания контекста корутины применяется другая встроенная функция - rememberCoroutineScope():

val scope = rememberCoroutineScope()

В Scaffold используем SnackbarHostState для установки параметра snakbarHost:

snackbarHost = { SnackbarHost(snackbarHostState) }
Для создания объекта состояния применяется встроенная функция rememberScaffoldState()(). Далее, используя это состояние, обращаемся к SnackbarHostState и его функции showSnackbar:

scaffoldState.snackbarHostState.showSnackbar("Count: ${++count.value}")

По нажатию на FloatingActionButton с помощью контекста корутины scope запускаем корутину и в ней вызывем функцию showSnackbar:

scope.launch {
	snackbarHostState.showSnackbar("Count: ${++count.value}")
}

В самой функции showSnackbar увеличиваем значение в переменной count. Вызов данной функции приведт к отображению сообщения в приложении:

SnackbarHostState и Scaffold в Jetpack Compose и Kotlin в Android

actionLabel

Теперь свяжем сообщение с некоторым действием. Для этого устанавим в функции showSnackbar() параметр actionLabel - фактически это просто текстовая метка, а не конкретное действие:

package com.example.helloapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text

import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val snackbarHostState = remember { SnackbarHostState() }
            val scope = rememberCoroutineScope()
            val count = remember{ mutableStateOf(0) }
            Scaffold(
                snackbarHost = { SnackbarHost(snackbarHostState) },
                floatingActionButton = {
                    FloatingActionButton(
                        content = {Icon(Icons.Filled.Add, contentDescription = "Добавить")},
                        onClick = {
                            scope.launch {
                                val result = snackbarHostState.showSnackbar(
                                        "Текущее значение: ${count.value}",
                                        actionLabel = "Подтвердить",
                                        withDismissAction = true,
                                        duration = SnackbarDuration.Short)
                                when (result) {
                                    SnackbarResult.ActionPerformed -> { count.value++; }
                                    SnackbarResult.Dismissed -> { snackbarHostState.showSnackbar("Действие отменено")}
                                }
                            }
                        }
                    )
                }
            ){
                Text("Count: ${count.value}", fontSize = 28.sp, modifier=Modifier.padding(it))
            }
        }
    }
}

В данном случае текстовая метка в сообщении, которая приглашает к некоторому действию, имеет текст "Подтвердить" и отображается в правой части окна сообщения.

SnackbarHostState и showSnackbar в Scaffold в Jetpack Compose и Kotlin в Android

Хотя сама эта метка ничего не делает, но теперь по нажатию на эту метку функция showSnackbar будет возвращать значение SnackbarResult.ActionPerformed. Если нажатия не было, и сообщение само по себе пропало, либо если мы нажали на кнопку отмены (на крестик), то возвращается значение SnackbarResult.Dismissed

Получив результат функции, с помощью конструкции when мы можем проверить его значение и выполнить определенные действия. В данном случае для простоты при одном результате к переменной count добавляется единица, а при другом результате - отображаем сообщение об отмене действия.

Результат showSnackbar в Scaffold в Jetpack Compose и Kotlin в Android

Может сложиться впечатление, что для отображения всплывающего сообщения необходимо обрабатывать нажатия обязательно в floatingActionButton. Однако в реальности мы можем запускать отображение в любом месте Scaffold. Например, по нажатию на обычную кнопку:

package com.example.helloapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text

import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val snackbarHostState = remember { SnackbarHostState() }
            val scope = rememberCoroutineScope()
            val count = remember{ mutableStateOf(0) }
            Scaffold(
                snackbarHost = { SnackbarHost(snackbarHostState) }
            ){
                Button(
                    onClick = {
                        scope.launch {
                            val result = snackbarHostState.showSnackbar("Count: ${count.value}", "Click", duration= SnackbarDuration.Short)
                            if(result==SnackbarResult.ActionPerformed) count.value++
                        }
                    },
                    modifier = Modifier.padding(it)
                ){
                    Text("Click", fontSize = 28.sp)
                }
            }
        }
    }
}

SnackbarHost

Выше система сама определяла визуальные аспекты вслывающего сообщения, мы только задавали текстовую соотавляющую. Однако в реальности мы также можем настроить отображение сообщения. Для этого необходимо явным образом создать отображаемый объект Snackbar.

Кроме того, для установки объектов Snackbar, которые связаны со Scaffold компонент предоставляет параметр snackbarHost, который по умолчанию представляет объект SnackbarHost. SnackbarHost фактически представляет хранилище объектов Snackbar и позволяет управлять их отображением. Его определение:

@Composable
fun SnackbarHost(
    hostState: SnackbarHostState,
    modifier: Modifier = Modifier,
    snackbar: @Composable (SnackbarData) -> Unit = { Snackbar(it) }
): Unit
  • hostState: представляет состояние SnackbarHost в виде объекта SnackbarHostState, который управляет отображением сообщения

  • snackbar: данные для Snackbar в виде объекта SnackbarData, которыt будут отображаться

Например, настроим цветовую гамму сообщения:

package com.example.helloapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text

import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val snackbarHostState = remember { SnackbarHostState() }
            val scope = rememberCoroutineScope()
            val count = remember{ mutableStateOf(0) }
            Scaffold(
                snackbarHost = {
                    SnackbarHost(snackbarHostState){ data ->
                        Snackbar(
                            snackbarData = data,
                            containerColor = Color.DarkGray,
                            contentColor = Color.LightGray,
                            actionOnNewLine =  true,
                            actionColor = Color.LightGray
                        )
                    }
                }
            ){
                Button(
                    {
                        scope.launch {
                            val result = snackbarHostState.showSnackbar("Count: ${count.value}", "Add", true)
                            if(result==SnackbarResult.ActionPerformed) count.value++
                        }
                    },
                    Modifier.padding(it)
                ){ Text("Click", fontSize = 28.sp) }
            }
        }
    }
}
SnackbarHost и Snackbar в Scaffold в Jetpack Compose и Kotlin в Android

В данном случае в SnackbarHost в качестве параметра передается объект SnackbarHostState, который запускает отображение сообщения:

SnackbarHost(snackbarHostState){ data ->
    Snackbar(
        snackbarData = data,

Далее в SnackbarHost определяется объект Snackbar, в который передается объект SnackbarData через параметр data. Этот объект будет содержать отображаемое сообщение и название метки действия. Но здесь мы их никак не изменям. Мы их будет отобажать, как они определены в функции showSnackbar(), изменяется только визуальная составляющая.

Полная настройка Snackbar

Выше у Snackbar был изменен применяемый цвет. Однако мы также можем полностью изменить его содержимое:

package com.example.helloapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val snackbarHostState = remember { SnackbarHostState() }
            val scope = rememberCoroutineScope()
            val count = remember{ mutableStateOf(0) }
            Scaffold(
                snackbarHost = {
                    SnackbarHost(snackbarHostState){ data ->
                        Snackbar(
                            modifier = Modifier.padding(10.dp),
                            containerColor = Color.DarkGray,
                            contentColor = Color.LightGray,
                            action = {
                                TextButton(onClick={ data.performAction() }){
                                    Text("OK", fontSize=22.sp, color=Color.LightGray)
                                }
                            },
                            dismissAction = {
                                TextButton(onClick={ data.dismiss() }){
                                    Text("Отмена", fontSize=22.sp, color=Color.LightGray)
                                }
                            }
                        ){ Text("Clicks: ${count.value}", fontSize=28.sp) }
                    }
                }
            ){
                Button(
                    {
                        scope.launch {
                            val result = snackbarHostState.showSnackbar("")
                            if(result==SnackbarResult.ActionPerformed) count.value++
                        }
                    },
                    Modifier.padding(it)
                ){ Text("Click", fontSize = 28.sp) }
            }
        }
    }
}
Настройка Snackbar в Scaffold в Jetpack Compose и Kotlin в Android

В данном случае полностью переопределяем внутреннее содержимое и метку действия у Snackbar. При этом параметры из функции showSnackbar() могут игнорироваться. Однако даже в этом случае передаваемый параметр SnackbarData может нам понадобиться. Так, в данном случае при нажатии на метку действия (которая в примере выше представлена компонентом TextButton) вызывается метод performAction()

action = {
    TextButton(onClick={ data.performAction() }){
        Text("OK", fontSize=22.sp, color=Color.LightGray)
    }
},

Благодаря этому система узнает, что результатом функции showSnackbar() является значение SnackbarResult.ActionPerformed.

Snackbar вне Scaffold

Может возникнуть вопрос, а можно ли использовать Snackbar вне Scaffold? В принципе можно, но в этом случае придется приложить дополнительные усилия, написать дополнительный код для позиционирования сообщения:

package com.example.helloapp

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.material3.Button
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            val scope = rememberCoroutineScope()
            val snackbarHostState = remember { mutableStateOf(SnackbarHostState()) }
            Button(
                onClick = {
                    scope.launch {
                        snackbarHostState.value.showSnackbar("Hello")
                    }
                }
            ) { Text("Click", fontSize = 28.sp) }
            SnackbarHost(snackbarHostState.value)
        }
    }
}
Snackbar вне Scaffold в Jetpack Compose и Kotlin в Android
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850