I'm using the new BackdropScaffold composable to make a similar looking screen like Google Map with a Map on the back and a list on the front. (See the image)
As you can see there is a problem with the corner around the front layer. Currently is displayed the surface under (pale blue). What I would like to achieve is having the Google Map shown in those corners. I tried to play with the size and padding of GoogleMap composable or the front panel but no luck.
UPDATE
The following example code shows the issue I'm facing. As you can see the BackdropScaffold background is correctly applied (RED). The corners of the front layer are transparent. The issue comes out when you have a different color in your background layer (BLUE). If the background layer contains a map you have the same issue.
BackdropScaffold is dividing the space but not overlaying any layer. The front layer should overlay a bit the back layer to fix this problem.
#OptIn(ExperimentalMaterialApi::class)
#Composable
internal fun test() {
val scope = rememberCoroutineScope()
val selection = remember { mutableStateOf(1) }
val scaffoldState = rememberBackdropScaffoldState(BackdropValue.Concealed)
val frontLayerHeightDp = LocalConfiguration.current.screenHeightDp / 3
LaunchedEffect(scaffoldState) {
scaffoldState.conceal()
}
BackdropScaffold(
scaffoldState = scaffoldState,
appBar = {
TopAppBar(
title = { Text("Backdrop scaffold") },
navigationIcon = {
if (scaffoldState.isConcealed) {
IconButton(onClick = { scope.launch { scaffoldState.reveal() } }) {
Icon(Icons.Default.Menu, contentDescription = "Localized description")
}
} else {
IconButton(onClick = { scope.launch { scaffoldState.conceal() } }) {
Icon(Icons.Default.Close, contentDescription = "Localized description")
}
}
},
actions = {
var clickCount by remember { mutableStateOf(0) }
IconButton(
onClick = {
// show snackbar as a suspend function
scope.launch {
scaffoldState.snackbarHostState
.showSnackbar("Snackbar #${++clickCount}")
}
}
) {
Icon(Icons.Default.Favorite, contentDescription = "Localized description")
}
},
elevation = 0.dp,
backgroundColor = Color.Transparent
)
},
backLayerContent = {
LazyColumn(modifier = Modifier.background(Color.Blue)) {
items(if (selection.value >= 3) 3 else 5) {
ListItem(
Modifier.clickable {
selection.value = it
scope.launch { scaffoldState.conceal() }
},
text = { Text("Select $it", color = Color.White) }
)
}
}
},
backLayerBackgroundColor = Color.Red,
frontLayerShape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
headerHeight = frontLayerHeightDp.dp,
frontLayerBackgroundColor = Color.White,
frontLayerContent = {
LazyColumn {
items(50) {
ListItem(
text = { Text("Item $it") },
icon = {
Icon(
Icons.Default.Favorite,
contentDescription = "Localized description"
)
}
)
}
}
}
)
}
BackdropScaffold is creating a Surface for from layer under the hood, when you create your own inside frontLayerContent it's displayed on top of built-in one.
Instead use frontLayerShape and frontLayerBackgroundColor parameters:
frontLayerShape = BottomSheetShape,
frontLayerBackgroundColor = Color.White,
frontLayerContent = {
LazyColumn(
modifier = Modifier.padding(16.dp)
) {
items(
items = moorings,
itemContent = { mooring ->
...
}
)
}
}
p.s. some comments about your code:
When you have modifier parameter, you should only apply it for the topmost container in your view - here you've applied it for content of frontLayerContent, which may cause unexpected behaviour.
You don't need to wrap LazyColumn in a Column - it has no effect when Column has only one child, and if you need to apply a modifier you can do it directly for LazyColumn
Related
I'm trying to make a swipe to reveal component using compose, but I want the width of the card that will appear after the swipe to grow to the size of the wrap content without using it, but I don't understand how to calculate the wrap content size.
var width by remember {
mutableStateOf(0.dp)
}
val lowerTransition = updateTransition(transitionState, "lowerCardTransition")
val lowerOffsetTransition by lowerTransition.animateFloat(
label = "lowerCardOffsetTransition",
transitionSpec = { tween(durationMillis = ANIMATION_DURATION) },
targetValueByState = { if (isRevealed) width.value else 0f },
)
How do I equate the width value used here to the wrap content value?
I'm trying to make the resulting delete button appear all without using a constant value
Try using AnimatedVisibility. For demo purpose I used OnClick, replace it with OnSwipe.
#Preview
#Composable
fun AnimateVisibility2() {
var visible by remember {
mutableStateOf(false)
}
Row(
modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.Center
) {
AnimatedVisibility(
visible = visible, enter = expandHorizontally(), exit = shrinkHorizontally()
) {
IconButton(onClick = { /*TODO*/ }) {
Icon(Icons.Default.Phone, contentDescription = null)
}
}
Button(onClick = { visible = !visible }, Modifier.weight(1f)) {
Text("Click Me")
}
}
}
I am struggling to understand how both Sync and Gmail have achieved this with there dialogs. I have tried different options such as Position Alert dialog in Android Compose, to move the alert dialog position however, I am still unable to get a similar appearance and lose the ability to tap outside to dismiss, which these two versions retain.
I am unsure how they can achieve the same result, both looking better than what I am able to achieve, while I am using a hacky solution. I am unsure if I'm missing something, maybe a different thing all together from Dialog/AlertDialog?
My current code implementation
if (openDialog) {
AlertDialog(
onDismissRequest = {
menuStatus = !menuStatus
openDialog = !openDialog
expanded = !expanded
},
shape = RoundedCornerShape(10.dp),
confirmButton = { null },
text = {
Column() {
Row() {
Text(text = "Account Management")
}
Spacer(modifier = Modifier.padding(16.dp))
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
painter = painterResource(id = R.drawable.baseline_logout_24),
contentDescription = null,
modifier = Modifier
.padding(end = 8.dp)
)
Text(text = "Sign out", )
}
}
},
modifier = Modifier
.customDialogModifier(CustomDialogPosition.TOP)
)
}
enum class CustomDialogPosition {
BOTTOM, TOP
}
fun Modifier.customDialogModifier(pos: CustomDialogPosition) = layout { measurable, constraints ->
val placeable = measurable.measure(constraints);
layout(constraints.maxWidth, constraints.maxHeight) {
when (pos) {
CustomDialogPosition.BOTTOM -> {
placeable.place(0, constraints.maxHeight - placeable.height, 10f)
}
CustomDialogPosition.TOP -> {
placeable.place(0, 150, 10f)
}
}
}
}
Right now I am using the Scaffold() composable to create a basic drawer layout. However the drawer is always the same size but I want it with a custom width, taking only 2/3 of the screen size and also a custom height, like a padding top and bottom. This is my code so far:
Scaffold(
scaffoldState = state,
topBar = {
TopAppBar(
title = {
SearchBar()
},
navigationIcon = {
IconButton(onClick = {
coroutineScope.launch { state.drawerState.open() }
}) {
Icon(Icons.Default.Menu, contentDescription = null)
}
},
backgroundColor = MaterialTheme.colors.background
)
},
drawerShape = RoundedCornerShape(topEnd = 16.dp, bottomEnd = 16.dp),
drawerContent = { NavigationDrawer(state, coroutineScope) },
content = {
MapScreen(
)
}
)
Changing anything in my composable function NavigationDrawer() won't change anything. Is there any way to achieve this in jetpack compose?
So there is a private val in Drawer which is the problem EndDrawerPadding = 56.dp
On tablets you would want a much larger amount of padding.
Since this can't be changed the best solution I could come up with was to make the drawer background transparent with no shadow, then in content set a smaller view, with a shape and shadow.
Scaffold(
topBar = { TopAppBarTablet(){
scope.launch { drawerState.open() }
} },
drawerBackgroundColor = colorResource(id = R.color.transparent),
drawerElevation = 0.dp,
drawerContent = {
Surface(
Modifier
.width(300.dp).fillMaxHeight(),
shape = RoundedCornerShape(4.dp),
color = colorResource(id = R.color.positive),
elevation = 4.dp
) {
Icon(
painter = painterResource(id = R.drawable.ic_home),
modifier = Modifier.size(25.dp),
contentDescription = null,
tint = colorResource(R.color.negative)
)
}
}
)
You can add something like this.
#Composable
fun ScaffoldWithPanel() {
Scaffold(
topBar = { ... },
bottomBar = { ... },
bodyContent = {
WithConstraints {
val parentWidth = with(AmbientDensity.current) {
constraints.maxWidth.toDp()
}
val parentHeight = with(AmbientDensity.current) {
constraints.maxHeight.toDp()
}
Box {
MainContent()
if(drawerState.value != DrawerValue.Open) {
Box(modifier = Modifier.size(parentWidth / 2, height = parentHeight)) {
SidePanel()
}
}
}
}
}
)
}
For more detail visit this website:
https://amryousef.me/side-drawer-jetpack-compose
Hello I can't figure out how to make a cut corners menu in jetpack compose 1.0.0-beta02. I tried wrapping the while menu with a surface but It didn't work.
TopAppBar(
modifier = Modifier
.statusBarsPadding(),
title = {
Text(text = "Title")
},
actions = {
var menuExpanded by remember { mutableStateOf(false) }
IconButton(onClick = { menuExpanded = true }) {
Icon(Icons.Default.MoreVert, contentDescription = null)
}
DropdownMenu(
expanded = menuExpanded,
onDismissRequest = {
menuExpanded = false
},
) {
DropdownMenuItem(onClick = {}) {
Text("Item 2")
}
}
},
)
Which gives me
But I need something like this, which is rounded.
Using a M2 MaterialTheme theme, the default shape used by the DropdownMenu is defined by the
medium attribute in the shapes used in the MaterialTheme (check your theme).
val Shapes = Shapes(
small = RoundedCornerShape(4.dp),
medium = RoundedCornerShape(4.dp), //<- used by `DropdownMenu`
large = RoundedCornerShape(0.dp)
)
You can change this value in your theme or you can override the medium shape only in your DropdownMenu.
Something like:
MaterialTheme(shapes = MaterialTheme.shapes.copy(medium = RoundedCornerShape(16.dp))) {
DropdownMenu(
expanded = menuExpanded,
onDismissRequest = {
menuExpanded = false
}
) {
DropdownMenuItem(onClick = {}) {
Text("Item 2")
}
DropdownMenuItem(onClick = {}) {
Text("Item 3")
}
}
}
Using a M3 MaterialTheme the default shape used by the DropdownMenu is defined by the extraSmall attribute in the shapes:
MaterialTheme(
shapes = MaterialTheme.shapes.copy(extraSmall = RoundedCornerShape(16.dp))){
//... DropdownMenu()
}
I want to implement a screen which can show two different bottom sheets.
Since ModalBottomSheetLayout only has a slot for one sheet I decided to change the sheetContent of the ModalBottomSheetLayout dynamically using a selected state when I want to show either of the two sheets (full code).
val sheetState = rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
val (selected, setSelected) = remember(calculation = { mutableStateOf(0) })
ModalBottomSheetLayout(sheetState = sheetState, sheetContent = {
when (selected) {
0 -> Layout1()
1 -> Layout2()
}
}) {
Content(sheetState = sheetState, setSelected = setSelected)
}
This works fine for very similar sheets, but as soon as you add more complexity to either of the two sheet layouts the sheet will not show when the button is pressed for the first time, it will only show after the button is pressed twice as you can see here:
Here you can find a reproducible example
I had a similar usecase, where I needed to show 2-3 stacked bottomsheets.
I ended up copying large part of Compose BottomSheet and added the desired behavior:
enum class BottomSheetValue { SHOWING, HIDDEN }
#Composable
fun BottomSheet(
parentHeight: Int,
topOffset: Dp = 0.dp,
fillMaxHeight: Boolean = false,
sheetState: SwipeableState<BottomSheetValue>,
shape: Shape = bottomSheetShape,
backgroundColor: Color = MaterialTheme.colors.background,
contentColor: Color = contentColorFor(backgroundColor),
elevation: Dp = 0.dp,
content: #Composable () -> Unit
) {
val topOffsetPx = with(LocalDensity.current) { topOffset.roundToPx() }
var bottomSheetHeight by remember { mutableStateOf(parentHeight.toFloat())}
val scrollConnection = sheetState.PreUpPostDownNestedScrollConnection
BottomSheetLayout(
maxHeight = parentHeight - topOffsetPx,
fillMaxHeight = fillMaxHeight
) {
val swipeable = Modifier.swipeable(
state = sheetState,
anchors = mapOf(
parentHeight.toFloat() to BottomSheetValue.HIDDEN,
parentHeight - bottomSheetHeight to BottomSheetValue.SHOWING
),
orientation = Orientation.Vertical,
resistance = null
)
Surface(
shape = shape,
color = backgroundColor,
contentColor = contentColor,
elevation = elevation,
modifier = Modifier
.nestedScroll(scrollConnection)
.offset { IntOffset(0, sheetState.offset.value.roundToInt()) }
.then(swipeable)
.onGloballyPositioned {
bottomSheetHeight = it.size.height.toFloat()
},
) {
content()
}
}
}
#Composable
private fun BottomSheetLayout(
maxHeight: Int,
fillMaxHeight: Boolean,
content: #Composable () -> Unit
) {
Layout(content = content) { measurables, constraints ->
val sheetConstraints =
if (fillMaxHeight) {
constraints.copy(minHeight = maxHeight, maxHeight = maxHeight)
} else {
constraints.copy(maxHeight = maxHeight)
}
val placeable = measurables.first().measure(sheetConstraints)
layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
}
}
}
TopOffset e.g. allows to place the bottomSheet below the AppBar:
BoxWithConstraints {
BottomSheet(
parentHeight = constraints.maxHeight,
topOffset = with(LocalDensity.current) {56.toDp()}
fillMaxHeight = true,
sheetState = yourSheetState,
) {
content()
}
}
I wanted to implement the same thing and because of the big soln, I wrote a post on dev.to that solves this problem, Here is the link
I implemented it like this. It looks pretty simple, but I still could not figure out how to pass the argument to "mutableStateOf ()" directly, I had to create a variable "content"
fun Screen() {
val bottomSheetState = rememberModalBottomSheetState(ModalBottomSheetValue.Hidden)
val scope = rememberCoroutineScope()
val content: #Composable (() -> Unit) = { Text("NULL") }
var customSheetContent by remember { mutableStateOf(content) }
ModalBottomSheetLayout(
sheetState = bottomSheetState,
sheetContent = {
customSheetContent()
}
) {
Column {
Button(
onClick = {
customSheetContent = { SomeComposable1() }
scope.launch { bottomSheetState.show() }
}) {
Text("First Button")
}
Button(
onClick = {
customSheetContent = { SomeComposable2() }
scope.launch { bottomSheetState.show() }
}) {
Text("Second Button")
}
}
}
I just tried your code. I am not sure but looks like when you click first time, since selected state changes, Content function tries to recompose itself and it somehow blocks sheetState. Because i can see that when i click first time, bottom sheet shows up a little and disappears immediately. But second time i click same button, since selected state doesnt change, sheetState works properly.