Компонент ModalNavigationDrawer предназначен для создания приложений с выдвижной панелью, где часто располагается какое-нибдуь меню. Этот компонент имеет следующее определение:
@Composable fun ModalNavigationDrawer( drawerContent: @Composable () -> Unit, modifier: Modifier = Modifier, drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed), gesturesEnabled: Boolean = true, scrimColor: Color = DrawerDefaults.scrimColor, content: @Composable () -> Unit ): Unit
Параметры компонента:
drawerContent
: содержимое выдвижной панели
modifier
: применяемыек компоненты модификаторы
drawerState
: состояние в виде объекта DrawerState. Для определения состояния применяется функция rememberDrawerState()
, в которую передается
передается начальное состояние панели с помощью констант перечисления DrawerValue: DrawerValue.Closed
(панель закрыта) и DrawerValue.Open
(панель открыта)
По умолчанию равно rememberDrawerState(DrawerValue.Closed)
gesturesEnabled
: указывает, может или нет панель управляться касаниями. По умолчанию равно true
scrimColor
: цвет, который скрывает основное содержимое приложения, когда панель открыта
content
: остальное содержимое приложения
Первый и последний параметр являются обязательными и в реальности они могут представлять любые компоненты. Например, определим простейшую выдвижную панель:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material3.Text import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.sp class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ModalNavigationDrawer( drawerContent = { Text("Drawer", fontSize = 22.sp, color=Color.LightGray) }, scrimColor = Color.DarkGray, content={Text("Main Content", fontSize = 28.sp)}) } } }
Здесь и содержимое выдвижной панели, и содержимое основной части приложения представлено компонентом Text. При выдвижении панели основной контент закрашивается сервым цветом - то есть фактически это цвет самой панели. В данном случае по умолчанию панель не видна, и для ее выдвижения надо провести пальцем от левой стороны приложения вправо:
Выдвижная панель может быть открыта или закрыта. Соответственно компонент может находиться в двух состояниях. Для хранения состояния применяется класс DrawerState. Внутри этого класса для
хранения состояния применяется перечисление DrawerValue. В частности, оно имеет два значения:
DrawerValue.Closed
(панель закрыта) и DrawerValue.Open
(панель открыта).
Для создания и управления состояния DrawerState применяется функция rememberDrawerState():
@Composable fun rememberDrawerState( initialValue: DrawerValue, confirmStateChange: (DrawerValue) -> Boolean = { true } ): DrawerState
В качестве обязательного параметра она принимает значение DrawerValue, которое указывает, открыта или закрыта панель по умолчанию. Для получения информации о состоянии DrawerState предоставляет ряд свойств:
currentValue: текущее состояние в виде значения DrawerValue
isClosed: если равно true, то выдвижная панель скрыта
isOpen: если равно true, то выдвижная панель раскрыта
Кроме того, DrawerState предоставляет ряд методов для управления состоянием:
open(): раскрывает выдвижную панель
close(): скрывает выдвижную панель
Обе эти функции являются suspend-функциями. Например, используем эти функции для программного управления состоянием панели:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.material3.DrawerValue import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.rememberCoroutineScope 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 drawerState = rememberDrawerState(DrawerValue.Closed) val scope = rememberCoroutineScope() ModalNavigationDrawer( drawerState = drawerState, drawerContent = { Text("Menu", fontSize = 22.sp, color=Color.LightGray) }, scrimColor = Color.DarkGray, content={ IconButton(onClick = { scope.launch {drawerState.open()} }) { Icon(Icons.Filled.Menu, "Меню") } } ) } } }
Сначала определяем контекст DrawerState и контекст корутины для запуска suspend-функций:
val drawerState = rememberDrawerState(DrawerValue.Closed) val scope = rememberCoroutineScope()
Для связи этого состояния с ModalNavigationDrawer определяем параметр drawerState:
ModalNavigationDrawer( drawerState = drawerState, ..........................
Затем через параметр content определяем кнопку-иконку меню, по нажатию на которую будет раскрываться панель с помощью выполнения метода drawerState.open()
:
content={ IconButton(onClick = { scope.launch {drawerState.open()} }) { Icon(Icons.Filled.Menu, "Меню") } }
Также здесь мы могли бы сделать более тонкое управление состоянием - открывать панель, когда она скрыта, и скрывать ее, когда она раскрыта:
scope.launch { if(drawerState.isClosed) drawerState.open() else drawerState.close() }
Нередко на подобных выдвижных панелях размещается некоторое меню. Определим подобное меню и определим обработку выбора его пунктов:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Column import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.DrawerValue import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.Text import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.TextButton import androidx.compose.material3.rememberDrawerState 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.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 items = listOf("Home", "Contact", "About") val selectedItem = remember { mutableStateOf(items[0]) } val drawerState = rememberDrawerState(DrawerValue.Closed) val scope = rememberCoroutineScope() ModalNavigationDrawer( drawerState = drawerState, drawerContent = { Column { items.forEach { item -> TextButton(onClick = { scope.launch { drawerState.close() } selectedItem.value = item }, colors = ButtonDefaults.buttonColors(contentColor = Color.LightGray, containerColor = Color.Transparent)) { Text(item, fontSize = 22.sp) } } } }, scrimColor = Color.DarkGray, content={ Column { IconButton(onClick = { scope.launch {drawerState.open()} }, content = { Icon(Icons.Filled.Menu, "Меню") }) Text(selectedItem.value, fontSize = 28.sp) } } ) } } }
В данном случае все пункты меню определены в массиве items. Для хранения выбранного пункта определена переменная selectedItem.
При наполнении выдвижной панели компонентами создаем для каждого элемента в items компонент TextButton. При нажатии на такую кнопку в переменную selectedItem будет передаваться нажатый элемент.
drawerContent = { Column { items.forEach { item -> TextButton(onClick = { scope.launch { drawerState.close() } selectedItem.value = item }, colors = ButtonDefaults.buttonColors(contentColor = Color.LightGray, containerColor = Color.Transparent)) { Text(item, fontSize = 22.sp) } } } },
В основном содержимом приложения отображаем выбранный пункт:
content={Text(selectedItem.value, fontSize = 28.sp)}
Для настройки отображения и поведения элементов на выдвижной панели применяется компонент ModalDrawerSheet
@Composable fun ModalDrawerSheet( modifier: Modifier = Modifier, drawerShape: Shape = DrawerDefaults.shape, drawerContainerColor: Color = DrawerDefaults.modalContainerColor, drawerContentColor: Color = contentColorFor(drawerContainerColor), drawerTonalElevation: Dp = DrawerDefaults.ModalDrawerElevation, windowInsets: WindowInsets = DrawerDefaults.windowInsets, content: @Composable ColumnScope.() -> Unit ): Unit
Он использует следующие параметры:
modifier
: функции модификатора, которые применются к компоненту
drawerShape
: форма компонента в виде объекта Shape
drawerContainerColor
: цвет контейнера
drawerContentColor
: цвет содержимого
drawerTonalElevation
: эффект анимации при нажатии на элементы компонента
windowInsets
: отступы от границ контейнера
content
: содержимое выдвижной панели
Применим ModalDrawerSheet:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Row import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.material3.DrawerValue import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.Text import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.TextButton import androidx.compose.material3.rememberDrawerState 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 items = listOf("Home", "Contact", "About") val selectedItem = remember { mutableStateOf(items[0]) } val drawerState = rememberDrawerState(DrawerValue.Closed) val scope = rememberCoroutineScope() ModalNavigationDrawer( drawerState = drawerState, drawerContent = { ModalDrawerSheet{ items.forEach { item -> TextButton( onClick = { scope.launch { drawerState.close() } selectedItem.value = item }, ) { Text(item, fontSize = 22.sp) } } } }, content={ Row{ IconButton(onClick = {scope.launch {drawerState.open()}}, content = { Icon(Icons.Filled.Menu, "Меню") } ) Text(selectedItem.value, fontSize = 28.sp) } } ) } } }
Как видно, ModalDrawerSheet имеет некоторую стандартную стилизацию и упрощает создание содержимого. И мы можем помещать в ModalDrawerSheet любые компоненты, как в данном случае компоненты TextButton. Тем не менее для определения элементов в ModalDrawerSheet в Jetpack Compose предназначен специальный компонент - NavigationDrawerItem:
@Composable fun NavigationDrawerItem( label: @Composable () -> Unit, selected: Boolean, onClick: () -> Unit, modifier: Modifier = Modifier, icon: (@Composable () -> Unit)? = null, badge: (@Composable () -> Unit)? = null, shape: Shape = NavigationDrawerTokens.ActiveIndicatorShape.value, colors: NavigationDrawerItemColors = NavigationDrawerItemDefaults.colors(), interactionSource: MutableInteractionSource? = null ): Unit
Отмечу основные параметры компонента:
label
: определяет содержимое
selected
: указывает, будет ли выделен элемент (при значении true)
onClick
: обработчик нажатия на компонент
colors
: устанавливает цвета. Для установки цветов можно использовать функцию NavigationDrawerItemDefaults.colors()
@Composable fun colors( selectedContainerColor: Color = NavigationDrawerTokens.ActiveIndicatorColor.value, unselectedContainerColor: Color = Color.Transparent, selectedIconColor: Color = NavigationDrawerTokens.ActiveIconColor.value, unselectedIconColor: Color = NavigationDrawerTokens.InactiveIconColor.value, selectedTextColor: Color = NavigationDrawerTokens.ActiveLabelTextColor.value, unselectedTextColor: Color = NavigationDrawerTokens.InactiveLabelTextColor.value, selectedBadgeColor: Color = selectedTextColor, unselectedBadgeColor: Color = unselectedTextColor ): NavigationDrawerItemColors
Она устанавливает следующие цвета:
selectedContainerColor
: цвет фона выбранного элемента
unselectedContainerColor
: цвет фона невыбранного элемента
selectedIconColor
: цвет иконки выбранного элемента
unselectedIconColor
: цвет иконки невыбранного элемента
selectedTextColor
: цвет текста выбранного элемента
unselectedTextColor
: цвет текста невыбранного элемента
selectedBadgeColor
: цвет значка выбранного элемента
unselectedBadgeColor
: цвет значка невыбранного элемента
Применим NavigationDrawerItem для создания элементов выдвижной панели:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Row import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.material3.DrawerValue import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.Text import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.NavigationDrawerItem import androidx.compose.material3.rememberDrawerState 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 items = listOf("Home", "Contact", "About") val selectedItem = remember { mutableStateOf(items[0]) } val drawerState = rememberDrawerState(DrawerValue.Closed) val scope = rememberCoroutineScope() ModalNavigationDrawer( drawerState = drawerState, drawerContent = { ModalDrawerSheet{ items.forEach { item -> NavigationDrawerItem( label= { Text(item, fontSize = 22.sp) }, selected = selectedItem.value==item, onClick = { scope.launch { drawerState.close() } selectedItem.value = item } ) } } }, content={ Row{ IconButton(onClick = {scope.launch {drawerState.open()}}, content = { Icon(Icons.Filled.Menu, "Меню") } ) Text(selectedItem.value, fontSize = 28.sp) } } ) } } }
Аналогично можно настроить цветовую гамму для выдвижной панели:
package com.example.helloapp import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.Row import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.material3.DrawerValue import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.ModalDrawerSheet import androidx.compose.material3.Text import androidx.compose.material3.ModalNavigationDrawer import androidx.compose.material3.NavigationDrawerItem import androidx.compose.material3.NavigationDrawerItemDefaults import androidx.compose.material3.rememberDrawerState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope 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 items = listOf("Home", "Contact", "About") val selectedItem = remember { mutableStateOf(items[0]) } val drawerState = rememberDrawerState(DrawerValue.Closed) val scope = rememberCoroutineScope() ModalNavigationDrawer( drawerState = drawerState, drawerContent = { ModalDrawerSheet( drawerContainerColor = Color.DarkGray, drawerContentColor = Color.LightGray ) { items.forEach { item -> NavigationDrawerItem( label= { Text(item, fontSize = 22.sp) }, selected = selectedItem.value==item, onClick = { scope.launch { drawerState.close() } selectedItem.value = item }, colors = NavigationDrawerItemDefaults.colors( selectedContainerColor = Color.Transparent, unselectedContainerColor = Color.Transparent, selectedTextColor = Color.White, unselectedTextColor = Color.LightGray ) ) } } }, content={ Row{ IconButton(onClick = {scope.launch {drawerState.open()}}, content = { Icon(Icons.Filled.Menu, "Меню") } ) Text(selectedItem.value, fontSize = 28.sp) } } ) } } }