I'm trying to implement a bottom sheet in Android (using either BottomSheetDialogFragment or Jetpack's ModalBottomSheetLayout) that vertically centers its content depending on whether the sheet is fully or partially expanded. In both implementations, the sheet itself seems to descend past the bottom edge of the screen, so any content visible there will be hidden. The "halfway" mark on the sheet is therefore static.
In my case, I want the content to remain centered within the visible portion of the sheet, not taking into account the invisible portion (i.e., between the top of the sheet and the bottom edge of the screen). If the content is too long, it would scroll via a ScrollView or scrolling WebView, etc. This seems to be easy to accomplish in SwiftUI in iOS, but so far I haven't been able to replicate this behavior on Android. It seems like the layouts are simply not aware of how they're positioned within the screen's frame.
Here's an example using Jetpack that replicates the problem:
ModalBottomSheetLayout(
sheetState = bottomSheetState,
sheetShape = RoundedCornerShape(16.dp),
sheetContent = {
Column(modifier = Modifier
.fillMaxWidth()
.background(Color(0XFF0F9D58))) {
Column(Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally) {
Text(text = "Hello world!", fontSize = 20.sp, color = Color.White)
}
}
}
) {
Text("")
}
Perhaps I need to "manually" measure the halfway point, but I'm not quite sure how to do that. Any help would be appreciated.
CodePudding user response:
Use your sheetState.progress.fraction there you will be getting values starting from zero, whenever the sheet dragged up it will increase and vice-versa.
Hope you got it!
CodePudding user response:
I hope this helps, below is a simple attempt on how to make sure the content stay in the middle as the Sheet is being dragged open/close, based on a Parallax effect.
val configuration = LocalConfiguration.current
val coroutineScope = rememberCoroutineScope()
val fullScreenHeight = configuration.screenHeightDp.dp
val sheetState = rememberModalBottomSheetState(
initialValue = ModalBottomSheetValue.Hidden
)
val sheetOffset = sheetState.offset.value
// just a button to show the bottom sheet
Button(onClick = {
if (!sheetState.isVisible) {
coroutineScope.launch {
sheetState.animateTo(ModalBottomSheetValue.HalfExpanded)
}
}
}) {}
ModalBottomSheetLayout(
sheetState = sheetState,
sheetShape = RoundedCornerShape(16.dp),
sheetContent = {
Column(
modifier = Modifier
.fillMaxWidth()
.background(Color(0XFF0F9D58))
) {
Column(
modifier = Modifier
.height(fullScreenHeight)
.fillMaxWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
var toOffset by remember { mutableStateOf(0f) }
val parallax = .19f
Box(
modifier = Modifier
.size(50.dp)
.onGloballyPositioned {
val posInParent = it.positionInParent().y
toOffset = posInParent - (posInParent sheetOffset)
}
.offset(y = (toOffset * parallax).dp)
.background(Color.Red)
)
}
}
}
) {
Text("")
}
Its kind of hard to explain things as everything I did was a matter of luck, so I'll just enumerate them as clear as I can.
I specified the
Screenheightto the inner mostColumncomposable using theScreen'sfull heightA mutable state
FloatoffsetA fixed "parallax" value of
19%Observe
Box'slayout coordinates viaonGloballyPositioned{...}callback, and get itsyposition from its parent via.positionInParent().yContinuously compute the
offsetfor theBoxwhile thesheetis being dragged with the folowingBoxOffset = (Box Y in Parent) - (Box Y in Parent sheetOffset)Apply the computed
offsetmultiplied to theParallaxvalue (a magical19%that I have no idea what and why but its the only value that works) as.dpwith the followingBoxOffset * 19%
Below are the screenshots showing 4 states of the screen with the bottom sheet (apologies for it being ugly as I don't know yet how to make a GIF out of the screen capture and upload it here)
Its the best I can come up of, and hopefully somebody can enlighten me where did the magical 19% came from or you can adjust the math of it if you find any improvements needed. However I'm not sure if this is the better way to achieve this effect, I suspect that every offset changes, any composable starting from the Box down to all the composable it would contain will re-compose, I'm not sure though, I haven't tested this yet with such use-case and observe the layout inspector.

