Skip to content

Instantly share code, notes, and snippets.

@Alexbeard
Created July 27, 2021 15:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Alexbeard/f9132f45289a51ddff675d4660312c22 to your computer and use it in GitHub Desktop.
Save Alexbeard/f9132f45289a51ddff675d4660312c22 to your computer and use it in GitHub Desktop.
@ExperimentalAnimationApi
@Composable
@ExperimentalMaterialApi
fun FloatingPanelScaffold(
modifier: Modifier = Modifier,
scaffoldState: FloatingPanelScaffoldState = rememberFloatingPanelScaffoldState(),
sidePanelState: SidePanelValue = SidePanelValue.Closed,
isInListMode: Boolean = false,
bottomPanelContent: @Composable ColumnScope.() -> Unit,
bottomPanelModifier: Modifier = Modifier,
bottomPanelShape: Shape = RoundedCornerShape(15.dp),
bottomPanelElevation: Dp = BottomPanelScaffoldDefaults.SheetElevation,
bottomPanelBackgroundColor: Color = MaterialTheme.colors.surface,
bottomPanelContentColor: Color = contentColorFor(bottomPanelBackgroundColor),
bottomPanelPeekHeight: Dp = BottomPanelScaffoldDefaults.SheetPeekHeight,
bottomPanelGesturesEnabled: Boolean = true,
sidePanelContent: @Composable ColumnScope.() -> Unit,
sidePanelModifier: Modifier = Modifier,
backgroundColor: Color = MaterialTheme.colors.background,
contentColor: Color = contentColorFor(backgroundColor),
content: @Composable (PaddingValues) -> Unit
) {
BoxWithConstraints(modifier) {
val coroutineScope = rememberCoroutineScope()
val fullHeight = constraints.maxHeight.toFloat()
val peekHeightPx = with(LocalDensity.current) { bottomPanelPeekHeight.toPx() }
val bottomSheetHeight = remember { mutableStateOf(fullHeight) }
LaunchedEffect(isInListMode) {
coroutineScope.launch {
if (isInListMode) {
scaffoldState.bottomPanelState.expand()
} else {
scaffoldState.bottomPanelState.hide()
}
}
}
val swipeable = Modifier
.then(
if (!isInListMode)
Modifier
.nestedScroll(scaffoldState.bottomPanelState.nestedScrollConnection)
else Modifier
)
.bottomPanelSwipeable(
scaffoldState.bottomPanelState,
fullHeight,
peekHeightPx,
bottomSheetHeight,
if (isInListMode) false else bottomPanelGesturesEnabled
)
.onGloballyPositioned {
bottomSheetHeight.value = it.size.height.toFloat()
}
FloatingPanelScaffoldStack(
body = {
Surface(
color = backgroundColor,
contentColor = contentColor
) {
Column(Modifier.fillMaxSize()) {
content(PaddingValues(bottom = bottomPanelPeekHeight))
}
}
},
bottomPanel = {
Surface(
swipeable
.requiredHeightIn(min = bottomPanelPeekHeight)
.then(bottomPanelModifier)
.then(
if (isInListMode) Modifier
.padding(top = 16.dp, end = 2.dp) else Modifier
),
shape = bottomPanelShape,
elevation = bottomPanelElevation,
color = bottomPanelBackgroundColor,
contentColor = bottomPanelContentColor,
content = { Column(content = bottomPanelContent) }
)
},
sidePanel = {
Surface(
modifier = sidePanelModifier.then(
if (isInListMode)
Modifier
.fillMaxHeight()
.padding(top = 16.dp)
else Modifier
),
shape = RoundedCornerShape(topStart = 15.dp, bottomStart = 15.dp),
elevation = bottomPanelElevation,
color = bottomPanelBackgroundColor,
contentColor = bottomPanelContentColor,
content = {
Column {
AnimatedVisibility(
visible = sidePanelState == SidePanelValue.Open,
enter = fadeIn() + expandHorizontally(),
exit = fadeOut() + shrinkHorizontally()
) {
sidePanelContent()
}
}
}
)
},
isInListMode = isInListMode,
bottomPanelOffset = scaffoldState.bottomPanelState.offset
)
}
}
@Composable
private fun FloatingPanelScaffoldStack(
body: @Composable () -> Unit,
bottomPanel: @Composable () -> Unit,
sidePanel: @Composable () -> Unit,
isInListMode: Boolean,
bottomPanelOffset: State<Float>,
) {
Layout(
content = {
body()
sidePanel()
bottomPanel()
}
) { measurables, constraints ->
val placeable = measurables.first().measure(constraints)
layout(placeable.width, placeable.height) {
placeable.placeRelative(0, 0)
val sidePanelPlaceable = measurables[1].measure(constraints.copy(minWidth = 0, minHeight = 0))
val sheetPlaceable = measurables[2].let {
if (isInListMode) {
it.measure(
constraints.copy(
maxWidth = constraints.maxWidth - sidePanelPlaceable.width,
minHeight = 0
)
)
} else {
it.measure(constraints.copy(minWidth = 0, minHeight = 0))
}
}
// Condition to draw first either sidePanel or bottomPanel in box
if (isInListMode) {
val sheetOffsetY = bottomPanelOffset.value.roundToInt()
sheetPlaceable.placeRelative(0, sheetOffsetY)
sidePanelPlaceable.placeRelative(
constraints.maxWidth - sidePanelPlaceable.width,
constraints.maxHeight / 2 - sidePanelPlaceable.height / 2
)
} else {
sidePanelPlaceable.placeRelative(
constraints.maxWidth - sidePanelPlaceable.width,
constraints.maxHeight / 2 - sidePanelPlaceable.height / 2
)
val sheetOffsetY = bottomPanelOffset.value.roundToInt()
sheetPlaceable.placeRelative(0, sheetOffsetY)
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment