I need to show my bottomsheet first in collapsed state. And on swipping bottomsheet up, it should first fix in half height of screen first. Again on swipping up , it should expand to max height of screen. Same during collapsing. First from max height to half height, then to peek height(the height of bottomsheet which will be visible in collapsed state). Is there any way to achieve it using BottomSheetScaffold?
I started write that solution for you. You can beautify it
enum class ExpandedType {
HALF, FULL, COLLAPSED
}
@Composable
private fun BottomSheet() {
val configuration = LocalConfiguration.current
val screenHeight = configuration.screenHeightDp
var expandedType by remember {
mutableStateOf(ExpandedType.COLLAPSED)
}
val height by animateIntAsState(
when (expandedType) {
ExpandedType.HALF -> screenHeight / 2
ExpandedType.FULL -> screenHeight
ExpandedType.COLLAPSED -> 70
}
)
val bottomSheetScaffoldState = rememberBottomSheetScaffoldState(
bottomSheetState = BottomSheetState(BottomSheetValue.Collapsed)
)
BottomSheetScaffold(
scaffoldState = bottomSheetScaffoldState,
sheetElevation = 8.dp,
sheetShape = RoundedCornerShape(
bottomStart = 0.dp,
bottomEnd = 0.dp,
topStart = 12.dp,
topEnd = 12.dp
),
sheetContent = {
var isUpdated = false
Box(
Modifier
.fillMaxWidth()
.height(height.dp)
.pointerInput(Unit) {
detectVerticalDragGestures(
onVerticalDrag = { change, dragAmount ->
change.consume()
if (!isUpdated) {
expandedType = when {
dragAmount < 0 && expandedType == ExpandedType.COLLAPSED -> {
ExpandedType.HALF
}
dragAmount < 0 && expandedType == ExpandedType.HALF -> {
ExpandedType.FULL
}
dragAmount > 0 && expandedType == ExpandedType.FULL -> {
ExpandedType.HALF
}
dragAmount > 0 && expandedType == ExpandedType.HALF -> {
ExpandedType.COLLAPSED
}
else -> {
ExpandedType.FULL
}
}
isUpdated = true
}
},
onDragEnd = {
isUpdated = false
}
)
}
.background(Color.Red)
)
},
sheetPeekHeight = height.dp
) {
Box(
Modifier
.fillMaxSize()
.background(Color.Black)
)
}
}
Found the way to fix issue about
While collapsing my bottomsheet, its bottom end moving up
Based on accepted answer made some changes, and it seems to solve an issue
val bottomSheetSt = rememberStandardBottomSheetState(
skipHiddenState = true,
initialValue = SheetValue.PartiallyExpanded
)
val scaffoldState = rememberBottomSheetScaffoldState(bottomSheetSt)
var peekHeight: Int by remember { mutableStateOf(0) }
BottomSheetScaffold(
scaffoldState = scaffoldState,
sheetContent = {
val scope = rememberCoroutineScope()
BottomSheetGestureWrapper(
onExpandTypeChanged = {
scope.launch {
peekHeight = when (it) {
ExpandedType.COLLAPSED -> 70
ExpandedType.FULL -> screenHeight - 46
ExpandedType.HALF -> screenHeight / 2
}
bottomSheetSt.partialExpand() // Smooth animation to desired height
}
}
) {
// Bottom Sheet content
}
},
sheetPeekHeight = peekHeight.dp, // <------- Important
modifier = Modifier.fillMaxSize(),
sheetShadowElevation = 0.dp,
sheetContainerColor = Color.Transparent,
sheetContentColor = Color.Transparent,
sheetDragHandle = null,
) {
// Scaffold content
}
And also moved draggable staff to separate file
@Composable
fun BottomSheetGestureWrapper(
modifier: Modifier = Modifier,
onExpandTypeChanged: (ExpandedType) -> Unit,
content: @Composable () -> Unit
) {
var expandedType by remember {
mutableStateOf(ExpandedType.COLLAPSED)
}
var isUpdated = false
LaunchedEffect(key1 = expandedType) {
onExpandTypeChanged(expandedType)
}
Box(
modifier
.fillMaxSize()
.pointerInput(Unit) {
detectVerticalDragGestures(
onVerticalDrag = { change, dragAmount ->
change.consume()
if (!isUpdated) {
expandedType = when {
dragAmount < 0 && expandedType == ExpandedType.COLLAPSED -> {
ExpandedType.HALF
}
dragAmount < 0 && expandedType == ExpandedType.HALF -> {
ExpandedType.FULL
}
dragAmount > 0 && expandedType == ExpandedType.FULL -> {
ExpandedType.HALF
}
dragAmount > 0 && expandedType == ExpandedType.HALF -> {
ExpandedType.COLLAPSED
}
else -> {
expandedType
}
}
isUpdated = true
}
},
onDragEnd = {
isUpdated = false
}
)
}
.background(Color.White)
) {
content()
}
}
val screenHeight = LocalConfiguration.current.screenHeightDp
–
Pedagogics I wasted lots of time and it works perfectly, with smooth and natural finger animation!
@Composable
fun BottomSheetContent() {
Surface(
modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background
) {
val density = LocalDensity.current
val configuration = LocalConfiguration.current
val initialScreenHeightInDp = configuration.screenHeightDp
val coroutineScope = rememberCoroutineScope()
var job by remember { mutableStateOf<Job?>(null) }
val sheetState = rememberStandardBottomSheetState(SheetValue.PartiallyExpanded)
val scaffoldState = rememberBottomSheetScaffoldState(sheetState)
val scope = rememberCoroutineScope()
var screenSizeInDp by remember { mutableStateOf(DpSize.Zero) }
val minHeightInDp = 35f.dp
val centerHeightInDp = (initialScreenHeightInDp.dp - minHeightInDp) / 2
var currentHeightInDp by remember { mutableStateOf(centerHeightInDp) }
BottomSheetScaffold(
scaffoldState = scaffoldState, sheetContent = {
Box(modifier = Modifier
.fillMaxWidth()
.height(currentHeightInDp)
.background(color = Color.White)
.pointerInput(Unit) {
detectVerticalDragGestures(onDragStart = {
android.util.Log.d("###", "onDragStart")
}, onVerticalDrag = { change, dragAmount ->
change.consume()
currentHeightInDp = if (currentHeightInDp - dragAmount.toDp() < minHeightInDp) {
minHeightInDp
} else if(currentHeightInDp - dragAmount.toDp() > screenSizeInDp.height) {
screenSizeInDp.height
} else {
currentHeightInDp - dragAmount.toDp()
}
}, onDragEnd = {
job?.cancel()
job = coroutineScope.launch {
when(currentHeightInDp) {
in minHeightInDp..centerHeightInDp / 2 -> {
while (currentHeightInDp > minHeightInDp) {
currentHeightInDp -= 5.dp
delay(1)
}
currentHeightInDp = minHeightInDp
}
in centerHeightInDp / 2..centerHeightInDp -> {
while (currentHeightInDp < centerHeightInDp) {
currentHeightInDp += 5.dp
delay(1)
}
currentHeightInDp = centerHeightInDp
}
in centerHeightInDp..centerHeightInDp + centerHeightInDp / 2 -> {
while (currentHeightInDp > centerHeightInDp) {
currentHeightInDp -= 5.dp
delay(1)
}
currentHeightInDp = centerHeightInDp
}
in centerHeightInDp + centerHeightInDp / 2..screenSizeInDp.height -> {
while (currentHeightInDp < screenSizeInDp.height) {
currentHeightInDp += 5.dp
delay(1)
}
currentHeightInDp = screenSizeInDp.height
}
}
job?.cancel()
return@launch
}
})
}) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxSize()
) {
}
}
},
sheetShape = RoundedCornerShape(
bottomStart = 0.dp, bottomEnd = 0.dp, topStart = 16.dp, topEnd = 16.dp
),
sheetDragHandle = {},
modifier = Modifier,
sheetPeekHeight = currentHeightInDp
) {
Box(
modifier = Modifier.fillMaxSize().background(Color(0xFFEEEEEE)).onSizeChanged {
screenSizeInDp = density.run {
DpSize(
it.width.toDp(),
it.height.toDp()
)
}
}, contentAlignment = Alignment.Center
) {
}
}
}
}
I realized the first answer has a good way to handle dragging actions. I want to have the dragging handler visible. So in this demo I applied Material3. I also remain using a custom dragging handler similar to the original one. If I stick to the original handler only the dash area is available to drag.
I also noticed the best match of the full height is to go by the container's height as well.
One more thing, this demo measures the dragging handler height, so the bottom sheet is more accurate how low to go and also half way.
P.S. I would like to test the third answer related to finger dragging.. :)
package info.juanmendez.view.components.generic
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectVerticalDragGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.material3.BottomSheetDefaults
import androidx.compose.material3.BottomSheetScaffold
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.SheetValue
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
enum class ExpandedType {
COLLAPSED, HALF_EXPANDED, EXPANDED
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun CustomBottomSheet(
modifier: Modifier,
screenHeight: Dp,
) {
val localDensity = LocalDensity.current
var expandedType by remember {
mutableStateOf(ExpandedType.COLLAPSED)
}
var dragHandleHeight by remember {
mutableStateOf(0.dp)
}
val sheetHeight by animateDpAsState(
when (expandedType) {
ExpandedType.HALF_EXPANDED -> (screenHeight / 2)
ExpandedType.EXPANDED -> screenHeight
ExpandedType.COLLAPSED -> dragHandleHeight
},
label = "sheetHeight"
)
BottomSheetScaffold(
modifier = Modifier,
sheetDragHandle = {
var isUpdated by remember {
mutableStateOf(false)
}
CustomSheetDragHandle(
modifier = Modifier
.onSizeChanged {
dragHandleHeight = Dp(it.height / localDensity.density)
}
.pointerInput(Unit) {
detectVerticalDragGestures(
onDragEnd = {
isUpdated = false
}
) { change, dragAmount ->
change.consume()
if (!isUpdated) {
expandedType = when {
dragAmount < 0 -> {
when (expandedType) {
ExpandedType.COLLAPSED -> ExpandedType.HALF_EXPANDED
ExpandedType.HALF_EXPANDED -> ExpandedType.EXPANDED
else -> ExpandedType.COLLAPSED
}
}
dragAmount > 0 -> {
when (expandedType) {
ExpandedType.EXPANDED -> ExpandedType.HALF_EXPANDED
ExpandedType.HALF_EXPANDED -> ExpandedType.COLLAPSED
else -> ExpandedType.COLLAPSED
}
}
else -> ExpandedType.COLLAPSED
}
isUpdated = true
}
}
},
)
},
sheetContent = {
Box(
modifier
.fillMaxWidth()
.height(sheetHeight - dragHandleHeight)
.background(Color.Red)
)
},
sheetPeekHeight = sheetHeight
) {
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CustomSheetDragHandle(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
BottomSheetDefaults.DragHandle()
}
}
// PREVIEW
@Preview
@Composable
fun CustomBottomSheetPreview() {
val localDensity = LocalDensity.current
var screenHeight by remember {
mutableStateOf(0.dp)
}
MaterialTheme {
Column(
modifier = Modifier
.background(Color.Blue)
.fillMaxSize()
.onSizeChanged {
screenHeight = Dp(it.height / localDensity.density)
}
) {
CustomBottomSheet(modifier = Modifier, screenHeight)
}
}
}
LocalConfiguration.current.screenHeightDp
because I have a native toolbar. So in this case you can pass screenHeightDp, or measure the height of the hosting component like I did. –
Illsorted © 2022 - 2024 — McMap. All rights reserved.