1. BottomDrawer、ModalBottomSheetLayout 怎么跳过HalfExpanded
ModalBottomSheetLayout:
如果compose是1.2.0及以上版本
val bottomSheetStatus =
rememberModalBottomSheetState(
ModalBottomSheetValue.Hidden,
skipHalfExpanded = true,
)
compose是1.1.1版本:
val bottomSheetState =
rememberModalBottomSheetState(ModalBottomSheetValue.Hidden, confirmStateChange = {
it != ModalBottomSheetValue.HalfExpanded
})
scope.launch {
if(bottomSheetState.isVisible) bottomSheetState.hide() else bottomSheetState.animateTo(ModalBottomSheetValue.Expanded)
}
BottomDrawer:
val drawerState: BottomDrawerState = rememberBottomDrawerState(BottomDrawerValue.Closed){
it != BottomDrawerValue.Open
}
scope.launch {
if (drawerState.isClosed) drawerState.expand() else drawerState.close()
}
ModalDrawer 从右侧往左侧打开
1. 从stackoverflow找到的一个解决方案:
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl ) {
ModalDrawer(
drawerState = drawerState,
drawerContent = { /* ...*/ },
content = { /* ..*/ }
)
}
但是用这个方法会导致drawerContent 和 content的内容是从右向左布局的
可以在content 再反过来:
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl ) {
ModalDrawer(
drawerState = drawerState,
drawerContent = {
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr
) {/* ...*/ }},
content = {CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr
) {/* ...*/ } }
)
}
这样做还是会碰到一些奇怪的问题。
2. Copy ModalDrawer 自定义一个RightModalDrawer
@Composable
@OptIn(ExperimentalMaterialApi::class)
fun RightModalDrawer(
modifier: Modifier = Modifier,
drawerState: MyDrawerState = rememberMyDrawerState(DrawerValue.Closed),
gesturesEnabled: Boolean = true,
drawerContent: @Composable ColumnScope.() -> Unit,
@FloatRange(from = 0.0, to = 1.0) drawerPercent: Float = 0.8f,
drawerShape: Shape = MaterialTheme.shapes.large,
drawerElevation: Dp = DrawerDefaults.Elevation,
drawerBackgroundColor: Color = MaterialTheme.colors.surface,
drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
scrimColor: Color = DrawerDefaults.scrimColor,
content: @Composable () -> Unit
) {
val scope = rememberCoroutineScope()
BoxWithConstraints(modifier.fillMaxSize()) {
val modalDrawerConstraints = constraints
// TODO : think about Infinite max bounds case
if (!modalDrawerConstraints.hasBoundedWidth) {
throw IllegalStateException("Drawer shouldn't have infinite width")
}
val fullWidth = constraints.maxWidth.toFloat()
val drawerWidth = fullWidth * drawerPercent
val maxValue = kotlin.math.max(0f, fullWidth - drawerWidth)
val anchors = mapOf(fullWidth to DrawerValue.Closed, maxValue to DrawerValue.Open)
Box(
Modifier.swipeable(
state = drawerState,
anchors = anchors,
thresholds = { _, _ -> FractionalThreshold(0.5f) },
orientation = Orientation.Horizontal,
enabled = gesturesEnabled,
velocityThreshold = fullWidth.dp,
resistance = null
)
) {
Box {
content()
}
Scrim(
open = drawerState.isOpen,
onClose = {
if (
gesturesEnabled &&
drawerState.confirmStateChange(DrawerValue.Closed)
) {
scope.launch { drawerState.close() }
}
},
fraction = {
calculateFraction(fullWidth, maxValue, drawerState.offset.value)
},
color = scrimColor
)
Surface(
modifier = with(LocalDensity.current) {
Modifier
.sizeIn(
maxWidth = drawerWidth.toDp(),
maxHeight = modalDrawerConstraints.maxHeight.toDp()
)
}
.offset { IntOffset(drawerState.offset.value.roundToInt(), 0) }
.semantics {
paneTitle = "navigationMenu"
if (drawerState.isOpen) {
dismiss {
if (drawerState.confirmStateChange(DrawerValue.Closed)) {
scope.launch { drawerState.close() }
}
true
}
}
},
shape = drawerShape,
color = drawerBackgroundColor,
contentColor = drawerContentColor,
elevation = drawerElevation
) {
Column(Modifier.fillMaxSize(), content = drawerContent)
}
}
}
}
3. Drawer 从上往下打开
一样copy BottomDrawer的代码 然后自定义一个
@Composable
@ExperimentalMaterialApi
fun TopDrawer(
drawerContent: @Composable ColumnScope.() -> Unit,
modifier: Modifier = Modifier,
drawerState: MyDrawerState = rememberMyDrawerState(DrawerValue.Closed),
gesturesEnabled: Boolean = true,
drawerShape: Shape = MaterialTheme.shapes.large,
drawerElevation: Dp = DrawerDefaults.Elevation,
drawerBackgroundColor: Color = MaterialTheme.colors.surface,
drawerContentColor: Color = contentColorFor(drawerBackgroundColor),
scrimColor: Color = DrawerDefaults.scrimColor,
content: @Composable () -> Unit
) {
val scope = rememberCoroutineScope()
BoxWithConstraints(modifier.fillMaxSize()) {
val fullHeight = constraints.maxHeight.toFloat()
//var drawerHeight by remember(fullHeight) { mutableStateOf(fullHeight) }
val minHeight = 0f
//val expandedHeight = max(minHeight, fullHeight - drawerHeight)
val minValue = minHeight - fullHeight
val maxValue = minHeight
val anchors =
mapOf(
minValue to DrawerValue.Closed,
maxValue to DrawerValue.Open
)
val drawerConstraints = with(LocalDensity.current) {
Modifier
.sizeIn(
maxWidth = constraints.maxWidth.toDp(),
maxHeight = constraints.maxHeight.toDp()
)
}
Box(
Modifier
//.then(nestedScroll)
.swipeable(
state = drawerState,
anchors = anchors,
orientation = Orientation.Vertical,
enabled = gesturesEnabled,
resistance = null
)
) {
content()
Scrim(
open = drawerState.isOpen,
onClose = {
if (
/*gesturesEnabled && */drawerState.confirmStateChange(DrawerValue.Closed)
) {
scope.launch { drawerState.close() }
}
},
fraction = {
calculateFraction(minValue, maxValue, drawerState.offset.value)
},
color = scrimColor,
)
Surface(
drawerConstraints
.offset { IntOffset(x = 0, y = drawerState.offset.value.roundToInt()) }
/*.onGloballyPositioned { position ->
drawerHeight = position.size.height.toFloat()
}*/
.semantics {
paneTitle = "navigationMenu"
if (drawerState.isOpen) {
dismiss {
if (drawerState.confirmStateChange(DrawerValue.Closed)) {
scope.launch { drawerState.close() }
}
true
}
}
},
shape = drawerShape,
color = drawerBackgroundColor,
contentColor = drawerContentColor,
elevation = drawerElevation
) {
Column(content = drawerContent)
}
}
}
}
@Composable
@ExperimentalMaterialApi
fun rememberMyDrawerState(
initialValue: DrawerValue,
confirmStateChange: (DrawerValue) -> Boolean = { true }
): MyDrawerState {
return rememberSaveable(saver = MyDrawerState.Saver(confirmStateChange)) {
MyDrawerState(initialValue, confirmStateChange)
}
}
@ExperimentalMaterialApi
class MyDrawerState(
initialValue: DrawerValue,
internal val confirmStateChange: (DrawerValue) -> Boolean = { true }
) : SwipeableState<DrawerValue>(
initialValue = initialValue,
confirmStateChange = confirmStateChange
) {
/**
* Whether the drawer is open.
*/
val isOpen: Boolean
get() = currentValue == DrawerValue.Open
/**
* Whether the drawer is closed.
*/
val isClosed: Boolean
get() = currentValue == DrawerValue.Closed
suspend fun open() = animateTo(DrawerValue.Open)
suspend fun close() = animateTo(DrawerValue.Closed)
companion object {
/**
* The default [Saver] implementation for [BottomDrawerState].
*/
fun Saver(confirmStateChange: (DrawerValue) -> Boolean) =
Saver<MyDrawerState, DrawerValue>(
save = { it.currentValue },
restore = { MyDrawerState(it, confirmStateChange) }
)
}
}
@Composable
private fun Scrim(
open: Boolean,
onClose: () -> Unit,
fraction: () -> Float,
color: Color
) {
val dismissDrawer = if (open) {
Modifier
.pointerInput(onClose) { detectTapGestures { onClose() } }
.semantics(mergeDescendants = true) {
contentDescription = "closeDrawer"
onClick { onClose(); true }
}
} else {
Modifier
}
Canvas(
Modifier
.fillMaxSize()
.then(dismissDrawer)
) {
drawRect(color, alpha = fraction())
}
}
private fun calculateFraction(a: Float, b: Float, pos: Float) =
((pos - a) / (b - a)).coerceIn(0f, 1f)