Add drawer toggle button in Jetpack compose - android

I want to add the hamburger icon to the Appbar and open/close drawer using the icon.
How would I achieve this?
Scaffold(
drawerShape = RoundedCornerShape(topRight = 10.dp, bottomRight = 10.dp),
drawerElevation = 5.dp,
drawerContent = {
// Drawer
},
topBar = {
TopAppBar(
navigationIcon = {
Icon(
Icons.Default.Menu,
modifier = Modifier.clickable(onClick = {
// Open drawer => How?
})
)
},
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(bottomLeft = 10.dp, bottomRight = 10.dp)),
title = { Text(text = "Hello") }
)
},
) {}

Use rememberScaffoldState() to modify drawer state.
create a variable:
val state = rememberScaffoldState()
val scope = rememberCoroutineScope() // in 1.0.0-beta `open()` and `close` are suspend functions
Pass the state to Scaffold
Scaffold(
scaffoldState = state,
// ...
)
Use state.drawerState.open() or state.drawerState.close() in an onClick to open/close the drawer.
Create an Icon for navigationIcon in TopAppBar:
val state = rememberScaffoldState()
Scaffold(
scaffoldState = state,
topBar = {
TopAppBar(
title = { Text(text = "AppBar") },
navigationIcon = {
Icon(
Icons.Default.Menu,
modifier = Modifier.clickable(onClick = {
scope.launch { if(it.isClosed) it.open() else it.close() }
})
)
}
)
},
drawerShape = RoundedCornerShape(topRight = 10.dp, bottomRight = 10.dp),
drawerContent = {
Text(text = "Drawer")
}
) {
// Scaffold body
}

You can use:
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
drawerContent = { Text("Drawer content") },
topBar = {
TopAppBar(
modifier = Modifier
.clip(RoundedCornerShape(bottomStart = 8.dp, bottomEnd = 8.dp))
) {
IconButton(
onClick = {
scope.launch { scaffoldState.drawerState.open() }
}
) {
Icon(Icons.Filled.Menu,"")
}
}
},
content = {
//bodyContent()
})

Related

How to change TopAppBar height when using Scaffold under Material 3

I am using the Material 3 Scaffold with the material 3 TopAppBar. I need to change the height of TopAppBar but cannot use MediumTopAppBar as it's too heightened. Is there any way to do the same?
Here is the code
val scrollBehaviour = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
Scaffold(
modifier = Modifier.fillMaxSize(),
topBar = {
TopAppBar(
title = {
Text(
text = "About",
modifier = Modifier
.padding(start = 30.dp, bottom = 12.dp)
.wrapContentSize(),
color = colorResource(id = R.color.black)
)
},
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = Color.Red,
titleContentColor = Color.Black,
),
modifier = Modifier.fillMaxWidth(),
navigationIcon = {
Button(onClick = { }) {
Text(text = "B1")
}
}, actions = {
Button(onClick = {}) {
Text(text = "B2")
}
},
scrollBehavior = scrollBehaviour
)
},
content = {
}
)

ModalNavigationDrawer doesn't totally collapse in Jetpack Compose

Here is the code that produces this strange behavior:
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
Column(
modifier = Modifier.fillMaxSize()
) {
items.forEach { item ->
NavigationDrawerItem(
modifier = Modifier.padding(NavigationDrawerItemDefaults.ItemPadding),
icon = {
Icon(
imageVector = item.icon,
contentDescription = null
)
},
label = {
Text(
text = item.name
)
},
onClick = {},
selected = true
)
}
}
},
content = {
Scaffold(
topBar = {},
content = { padding ->
Box(
modifier = Modifier.fillMaxSize().padding(padding)
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "Profile"
)
}
}
}
)
}
)
I'm using:
implementation "androidx.compose.material3:material3:1.0.0-alpha16"
This exact code worked perfectly fine before updating from 1.0.0-alpha15.
How to solve this?
If you put the drawer contents into a ModalDrawerSheet it might help.
Thus:
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
ModalDrawerSheet {
Column(
modifier = Modifier.fillMaxSize()
etc

Navigating between composable's using a Navigation Drawer in Jetpack Compose

I am trying to set up navigation for the drawer icons/textfield in jetpack compose but not exactly sure how to do it properly. How can I set the navigation so that whenever I click in one of the Icons I get navigated to that composable screen? This is currently my MainDrawer layout:
#Composable
fun MainDrawer() {
val scaffoldState = rememberScaffoldState()
val scope = rememberCoroutineScope()
Scaffold(
scaffoldState = scaffoldState,
topBar = {
AppBar(
onNavigationIconClick = {
scope.launch {
scaffoldState.drawerState.isOpen
}
}
)
},
drawerContent = {
DrawerHeader()
DrawerBody(
items = listOf(
MenuItem(
id = "item1",
title = "item1",
contentDescription = "Go to item1 screen",
icon = Icons.Default.Home
),
MenuItem(
id = "item2",
title = "item2",
contentDescription = "Go to item2 screen",
icon = Icons.Default.Settings
),
MenuItem(
id = "item3",
title = "item3",
contentDescription = "Ge to item3",
icon = Icons.Default.Info
),
MenuItem(
id = "item4",
title = "item4",
contentDescription = "Go to Your item4",
icon = Icons.Default.Info
),
MenuItem(
id = "item5",
title = "item5",
contentDescription = "Your item5",
icon = Icons.Default.Info
),
MenuItem(
id = "item6",
title = "item6",
contentDescription = "Your item6",
icon = Icons.Default.Info
),
MenuItem(
id = "item7",
title = "item7",
contentDescription = "item7",
icon = Icons.Default.Info
),
MenuItem(
id = "item8",
title = "item8",
contentDescription = "item8",
icon = Icons.Default.Info
),
)
) {
println("Clicked on ${it.title}")
}
}
) {
}
}
Drawer Body:
This contains the element of the body
#Composable
fun DrawerBody(
items: List<MenuItem>,
modifier: Modifier = Modifier,
itemTextStyle: TextStyle = TextStyle(fontSize = 18.sp),
onItemClick: (MenuItem) -> Unit
) {
LazyColumn(modifier) {
items(items) { item ->
Row(
modifier = Modifier
.fillMaxWidth()
.clickable {
onItemClick(item)
}
.padding(16.dp)
) {
Icon(
imageVector = item.icon,
contentDescription = item.contentDescription
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = item.title,
style = itemTextStyle,
modifier = Modifier.weight(1f)
)
}
}
}
}
I managed to solve this issue. This is how it will work:
https://gyazo.com/4c32e855becff72f8979650545ad7f39
This is how I did it:
Begin by adding the dependencies to your project:
// Navigation with Compose
implementation "androidx.navigation:navigation-compose:2.5.0"
// Modal Drawer Layout
implementation "androidx.drawerlayout:drawerlayout:1.1.1"
// Coroutines
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4"
Start by creating the TopAppBar for the app. This is the following code:
#Composable
fun TopBar(scope: CoroutineScope, scaffoldState: ScaffoldState) {
TopAppBar(
title = { Text(text = stringResource(R.string.app_name), fontSize = 18.sp) },
navigationIcon = {
IconButton(onClick = {
scope.launch {
scaffoldState.drawerState.open()
}
}) {
Icon(Icons.Filled.Menu, "")
}
},
backgroundColor = colorResource(id = R.color.purple_200),
contentColor = Color.White
)
}
Next, create a sealed class. This class will represent the items you want inside your drawer. I put the following as an example:
sealed class NavDrawerItem(var route: String, var icon: Int, var title: String) {
object Add : NavDrawerItem("add", android.R.drawable.ic_menu_add, "Add")
object Edit : NavDrawerItem("edit", android.R.drawable.ic_menu_edit, "Edit")
object Search : NavDrawerItem("search", android.R.drawable.ic_menu_search, "Search")
object Location : NavDrawerItem("location", android.R.drawable.ic_menu_mylocation, "Location")
object Preferences : NavDrawerItem("preferences", android.R.drawable.ic_menu_preferences, "Preferences")
}
Now, create the highlighter. When an item in the drawer has been pressed, this will highlight what item has been pressed.
#Composable
fun DrawerItem(item: NavDrawerItem, selected: Boolean, onItemClick: (NavDrawerItem) -> Unit) {
val background = if (selected) R.color.purple_200 else android.R.color.transparent
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.fillMaxWidth()
.clickable(onClick = { onItemClick(item) })
.height(45.dp)
.background(colorResource(id = background))
.padding(start = 10.dp)
) {
Image(
painter = painterResource(id = item.icon),
contentDescription = item.title,
colorFilter = ColorFilter.tint(Color.White),
contentScale = ContentScale.Fit,
modifier = Modifier
.height(35.dp)
.width(35.dp)
)
Spacer(modifier = Modifier.width(7.dp))
Text(
text = item.title,
fontSize = 18.sp,
color = Color.White
)
}
}
Here we create each screen for the respective items. 1 composable screen for each item. I created an easy and quick screen but you can do your own screens here.
#Composable
fun AddScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(colorResource(id = R.color.myColor))
.wrapContentSize(Alignment.Center)
) {
Text(
text = "Add Screen",
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
fontSize = 25.sp
)
}
}
#Composable
fun EditScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(colorResource(id = R.color.myOrange))
.wrapContentSize(Alignment.Center)
) {
Text(
text = "Edit Screen",
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
fontSize = 25.sp
)
}
}
#Composable
fun SearchScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(colorResource(id = R.color.purple_500))
.wrapContentSize(Alignment.Center)
) {
Text(
text = "Search Screen",
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
fontSize = 25.sp
)
}
}
#Composable
fun LocationScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(colorResource(id = R.color.myGreen))
.wrapContentSize(Alignment.Center)
) {
Text(
text = "Location Screen",
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
fontSize = 25.sp
)
}
}
#Composable
fun PreferencesScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.background(colorResource(id = R.color.myGreen))
.wrapContentSize(Alignment.Center)
) {
Text(
text = "Preference Screen",
fontWeight = FontWeight.Bold,
color = Color.White,
modifier = Modifier.align(Alignment.CenterHorizontally),
textAlign = TextAlign.Center,
fontSize = 25.sp
)
}
}
Now we need to create the actual navigation using a navcontroller and navHostController. We do this in the following way:
#Composable
fun ComposeNavigation(navController: NavHostController) {
NavHost(navController, startDestination = NavDrawerItem.Add.route) {
composable(NavDrawerItem.Add.route) {
AddScreen()
}
composable(NavDrawerItem.Edit.route) {
EditScreen()
}
composable(NavDrawerItem.Search.route) {
SearchScreen()
}
composable(NavDrawerItem.Location.route) {
LocationScreen()
}
composable(NavDrawerItem.Preferences.route) {
PreferencesScreen()
}
}
}
After, create DrawerLayout with all the items, coroutines and navbackstack.
#Composable
fun DrawerLayout(scope: CoroutineScope, scaffoldState: ScaffoldState, navController: NavController) {
val items = listOf(
NavDrawerItem.Add,
NavDrawerItem.Edit,
NavDrawerItem.Search,
NavDrawerItem.Location,
NavDrawerItem.Preferences
)
Column {
// Header
Image(
painter = painterResource(id = R.drawable.ic_launcher_foreground),
contentDescription = R.drawable.ic_launcher_foreground.toString(),
modifier = Modifier
.height(100.dp)
.fillMaxWidth()
.padding(10.dp)
)
Spacer(
modifier = Modifier
.fillMaxWidth()
.height(5.dp)
)
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
items.forEach { item ->
DrawerItem(item = item, selected = currentRoute == item.route, onItemClick = {
navController.navigate(item.route) {
navController.graph.startDestinationRoute?.let { route ->
popUpTo(route) {
saveState = true
}
}
launchSingleTop = true
restoreState = true
}
scope.launch {
scaffoldState.drawerState.close()
}
})
}
}
}
Finally we create the mainLayout that will work as the main frame for our app. Here will use a scaffold for the topbar and drawercontent:
#SuppressLint("UnusedMaterialScaffoldPaddingParameter")
#Composable
fun MainLayout() {
val scaffoldState = rememberScaffoldState(rememberDrawerState(DrawerValue.Closed))
val scope = rememberCoroutineScope()
val navController = rememberNavController()
Scaffold(
scaffoldState = scaffoldState,
topBar = { TopBar(scope = scope, scaffoldState = scaffoldState) },
drawerBackgroundColor = colorResource(id = R.color.myColor),
drawerContent = {
DrawerLayout(scope = scope, scaffoldState = scaffoldState, navController = navController)
},
) {
ComposeNavigation(navController = navController)
}
}
Hope this helped anyone looking for a functional navigation drawer! Feel free to give it a arrowUp if this works for you. Happy Coding!

Why bottom sheet not work with bottom navigation in jetpack compose?

I try to learn jetpack compose these days, and I have a simple project in jetpack compose, bottom sheet work in my project, but when I use bottom navigation, it is not work, I search in many website and stackoverflow especially, but I did not find any solution, I do not know what I missed? is there any idea?
#Composable
fun BSDataScreen() {
val modalBottomSheetState = rememberModalBottomSheetState(initialValue =
ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
ModalBottomSheetLayout(
sheetContent = {
SheetScreen()
},
sheetState = modalBottomSheetState,
sheetShape = RoundedCornerShape(topStart = 15.dp, topEnd = 15.dp),
sheetBackgroundColor = Color.White,
) {
Scaffold(
backgroundColor = Color.White,
) {
DataScreen(
scope = scope, state = modalBottomSheetState)}}}
#Composable
fun DataScreen(
scope: CoroutineScope,
state: ModalBottomSheetState
) {
val listOfData = listOf(
Data( painterResource(R.drawable.image1)),
Data(painterResource(R.drawable.image2)),
Data(painterResource(R.drawable.image3),)
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.background(Color.White)
) {
LazyColumn(
modifier = Modifier
) {
items(listOfData.size) { index ->
DataListItem(listOfData[index]) data: Data->
scope.launch {
state.show()
}
}}}}
#Composable
fun DataListItem(data: Data, onLongClick: (Data) -> Unit) {
val context = LocalContext.current
Column(
modifier = Modifier.padding(5.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(5.dp)
.combinedClickable(
onLongClick= {
onLongClick(data)
},)
) {
Image(
painter = data.painter,
contentDescription = null,)}}}
BottomNav:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainScreen()
}
}
}
#Composable
fun MainScreen() {
val navController = rememberNavController()
Scaffold(
bottomBar = { BottomNavigationBar(navController) }
) {
Navigation(navController = navController)
}
}
#Composable
fun Navigation(navController: NavHostController) {
val modalBottomSheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
NavHost(navController, startDestination = NavigationItem.Data.route) {
composable(NavigationItem.Data.route) {
DataScreen(
scope = scope, state = modalBottomSheetState
)
}
composable(NavigationItem.Data2.route) {
Data2Screen()
}
composable(NavigationItem.Data3.route) {
Data3Screen()
}
composable(NavigationItem.Data4.route) {
Data4Screen()
}
composable(NavigationItem.Data5.route) {
Data5Screen()
}
}
}
#Composable
fun BottomNavigationBar(navController: NavController
) {
val items = listOf(
NavigationItem.Data,
NavigationItem.Data2,
NavigationItem.Data3,
NavigationItem.Data4,
NavigationItem.Data5
)
BottomNavigation(
backgroundColor = colorResource(id = R.color.white),
contentColor = colorResource(id = R.color.black)
) {
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
items.forEach { item ->
BottomNavigationItem(
icon = {
Icon(
painterResource(id = item.icon),
contentDescription = null
)
},
selectedContentColor = colorResource(id = R.color.red),
unselectedContentColor = colorResource(id = R.color.blue),
alwaysShowLabel = true,
selected = currentRoute == item.route,
onClick = {
navController.navigate(item.route) {
navController.graph.startDestinationRoute?.let { route ->
popUpTo(route) {
saveState = true
}
}
launchSingleTop = true
restoreState = true
}})}}}
I think your problem is your bottom sheets appear under bottom bar. The same thing I solved is very simple.
#Composable
fun SettingView() {
val navController = rememberNavController()
val scaffoldState = rememberScaffoldState()
Scaffold(
bottomBar = {
BottomBar(navController = navController)
},
content = {
Box(modifier = Modifier.padding(it)) {
BottomNavGraph(
navController = navController,
scaffoldState = scaffoldState
)
}
}
)
}
content screens wrap with Box and padding to scaffold's PaddingValues.
Just wrap the whole screen with ModalBottomSheetLayout.
You can put the following code:
Scaffold(
bottomBar = { BottomNavigationBar(navController) }
) {
Navigation(navController = navController)
}
inside
ModalBottomSheetLayout(...){
Scaffold(
bottomBar = { BottomNavigationBar(navController) }
) {
Navigation(navController = navController)
}
}
Try using the Navigation Material library provided by Accompanist.
https://google.github.io/accompanist/navigation-material/

Floating Button goes up with Bottom Sheet Jetpack Compose

I don't understand why the FloatingButton is going up with the BottomSheet.
I tried to change the sheetElevation which is higher than the elevation of the FloatingButton, but the issue remains. That's because the code inside BottomSheetScaffoldStack says to move the FloatingButton up along with the BottomSheet. Is there any way to avoid that?
Here is the code of the BottomSheetScaffold:
BottomSheetScaffold(
scaffoldState = bottomSheetScaffoldState,
topBar = { TopBar(
areButtonShowed = true,
title = topBarTitle,
onBackPressed = { BendRouter.navigateTo(onBackDestination) }
) },
floatingActionButtonPosition = FabPosition.Center,
floatingActionButton = {
ExtendedFloatingButton(
text = context.getString(R.string.start),
onClick = {}, // TODO
modifier = Modifier
.fillMaxWidth()
.height(45.dp)
.padding(
start = 24.dp,
end = 24.dp
),
backgroundColor = PureWhite
)
Spacer(modifier = Modifier.height(150.dp))
},
sheetBackgroundColor = PureWhite,
sheetPeekHeight = 0.dp,
sheetElevation = 70.dp,
sheetShape = RoundedCornerShape(50.dp),
sheetContent = {
openStretchDetails?.let { stretch ->
BottomSheetView(stretch = stretch)
}
},
content = { RoutinePageView(viewModel) }
)
And ExtendedFloatingButton:
#Composable
fun ExtendedFloatingButton(
text: String,
#DrawableRes icon: Int? = null,
onClick: () -> Unit,
modifier: Modifier = Modifier,
elevation: Dp = 12.dp,
backgroundColor: Color
) {
ExtendedFloatingActionButton(
text = {
Text(
text = text.uppercase(),
color = Gray,
fontSize = 18.sp,
maxLines = 1,
fontWeight = FontWeight.Bold,
letterSpacing = .5.sp
)
},
onClick = onClick,
icon = {
icon?.let {
Icon(
painter = painterResource(id = it),
contentDescription = ""
)
}
},
modifier = modifier,
elevation = FloatingActionButtonDefaults.elevation(elevation),
backgroundColor = backgroundColor
)
}
The BottomSheetScaffold has a default behavior that you can see by clicking on it while pressing CTRL. Looking at BottomSheetScaffoldStack inside the BottomSheetScaffold.kt class you'll see how the FloatingButton is moved along with the BottomSheet.
To solve this problem I used ModalBottomSheetLayout instead of BottomSheetScaffold.
ModalBottomSheetLayout(
sheetState = bottomState,
sheetContent = {
Box(Modifier.defaultMinSize(minHeight = 1.dp)) {
openStretchDetails?.let { stretch ->
BottomSheetView(stretch = stretch)
coroutineScope.launch {
bottomState.animateTo(ModalBottomSheetValue.Expanded)
}
}
}
},
sheetBackgroundColor = PureWhite,
sheetElevation = 16.dp,
sheetShape = RoundedCornerShape(50.dp),
) {
Scaffold(
topBar = { TopBar(
areButtonShowed = true,
title = topBarTitle,
onBackPressed = { BendRouter.navigateTo(onBackDestination) }
) },
floatingActionButtonPosition = FabPosition.Center,
floatingActionButton = {
ExtendedFloatingButton(
text = context.getString(R.string.start),
onClick = {}, // TODO
modifier = Modifier
.fillMaxWidth()
.height(45.dp)
.padding(
start = 24.dp,
end = 24.dp
),
backgroundColor = PureWhite
)
Spacer(modifier = Modifier.height(150.dp))
},
content = { RoutinePageView(viewModel) }
)
}
Add this on the top:
val bottomState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden,
confirmStateChange = { it != ModalBottomSheetValue.HalfExpanded }
)
val coroutineScope = rememberCoroutineScope()

Categories

Resources