我可以毫无问题地实现模态抽屉。但是我想在同一个屏幕上添加两个模态抽屉,一个从左边,一个从右边。在xml中很容易做到,但是我不知道如何在Jetpack中做到这一点。
val navController = rememberNavController()
Surface(color = MaterialTheme.colors.background) {
val drawerState = rememberDrawerState(DrawerValue.Closed)
val scope = rememberCoroutineScope()
val openDrawer = {
scope.launch {
drawerState.open()
}
}
//CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl ) {
ModalDrawer(drawerState = drawerState, gesturesEnabled = true,//drawerState.isOpen,
drawerContent = {
Drawer(onDestinationClicked = { route ->
scope.launch {
drawerState.close()
}
Handler(Looper.getMainLooper()).postDelayed({
navController.navigate(route) {
//popUpTo = navController.graph.startDestination
//popUpTo = navController.graph.startDestinationId
popUpTo(navController.graph.startDestinationId)
launchSingleTop = true
}
}, 100)
}
)
}
) {
//CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr ) {
NavHost(navController = navController, startDestination = DrawerScreens.Home.route) {
composable(DrawerScreens.Home.route) {
Home(openDrawer = { openDrawer() })
}
composable(DrawerScreens.Account.route) {
Account(openDrawer = { openDrawer() })
}
composable(DrawerScreens.Help.route) {
Help(navController = navController)
}
}
//}
}
//}
}
我找不到任何内置解决方案。因此,我编写了自己的双模态抽屉实现。
双抽屉布局
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationVector1D
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.VectorConverter
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.border
import androidx.compose.foundation.gestures.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.listSaver
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.consumeAllChanges
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.onClick
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import inn.rachika.ram2.common.lib.WriteLog
import inn.rachika.ram2.presentation.list.memberList.MemberListScreen
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.math.abs
import kotlin.math.roundToInt
import kotlin.random.Random
class DoubleDrawerState(){
var scope = CoroutineScope(Dispatchers.Main)
var maxWidth = 0f
var leftOffsetX = Animatable(-5000f)/*initial value is to prevent the drawer to show during startup*/
var leftDrawerWidthPx = 0f
var leftThreshold = 0f
var isLeftOpen = false
var leftDrawerEnabled = true
var rightDrawerWidthPx = 0f
var rightOffsetX = Animatable(5000f)
var rightThreshold = 0f
var isRightOpen = false
var rightDrawerEnabled = true
var fiftyPercentL = 0f
var fiftyPercentR = 0f
private var isLeftDragging = false
private var isRightDragging = false
private var velocity = 0f
private val velocityThreshold = 25f
internal fun onDrag(delta: Float){
if ((!leftOffsetX.isRunning) && (!rightOffsetX.isRunning)) {
velocity = delta
when {
delta > 0 -> {/* left drawer */
when {
isRightDragging -> {
onDragRight(delta)
}
else -> {
if(!isRightOpen){ isLeftDragging = true }/*if right drawer is not open then capture dragging*/
if (isRightOpen) onDragRight(delta) else onDragLeft(delta)
}
}
}
delta < 0 -> {/* right drawer */
when {
isLeftDragging -> {
onDragLeft(delta)
}
else -> {
if(!isLeftOpen){ isRightDragging = true }/*if left drawer is not open then capture dragging*/
if (isLeftOpen) onDragLeft(delta) else onDragRight(delta)
}
}
}
}
}
}
internal fun onDragEnd(){
if(abs(velocity) > velocityThreshold){
performFling(velocity)
return
} else{
if (isRightOpen) {
when {
rightOffsetX.value > (rightThreshold + fiftyPercentR) -> hideRight()
else -> showRight()
}
} else {
when {
rightOffsetX.value > (maxWidth - fiftyPercentR) -> hideRight()
rightOffsetX.value < (maxWidth - fiftyPercentR) -> showRight()
}
}
if (isLeftOpen) {
/*hide if 20% swipe in*/
when {
abs(leftOffsetX.value) > (abs(leftThreshold) + fiftyPercentL) -> hideLeft()
else -> showLeft()
}
} else {
/*show if 20% swipe out*/
when {
abs(leftOffsetX.value) < (maxWidth - fiftyPercentL) -> showLeft()
else -> hideLeft()
}
}
isLeftDragging = false
isRightDragging = false
}
//WriteLog("Pointer Velocity: $velocity ------- ${Random.nextInt(0, 100)}")
velocity = 0f
}
private fun performFling(velocity: Float){
if(velocity > velocityThreshold){
if(isRightOpen) hideRight() else showLeft()
}
if(velocity < velocityThreshold){
if(isLeftOpen) hideLeft() else showRight()
}
}
private fun onDragLeft(delta: Float){
if(!leftDrawerEnabled){ return }
when {
(leftOffsetX.value + delta) > leftThreshold -> {
scope.launch { leftOffsetX.snapTo(leftThreshold) }
isLeftOpen = true
}
(leftOffsetX.value + delta) < -maxWidth -> {
scope.launch { leftOffsetX.snapTo(-maxWidth) }
isLeftOpen = false
}
else -> {
scope.launch { leftOffsetX.snapTo(leftOffsetX.value + delta) }
}
}
}
private fun onDragRight(delta: Float){
if(!rightDrawerEnabled){ return }
when {
(rightOffsetX.value + delta) <= rightThreshold -> {
scope.launch { rightOffsetX.snapTo(rightThreshold) }
isRightOpen = true
}
(rightOffsetX.value + delta) > maxWidth -> {
scope.launch { rightOffsetX.snapTo(maxWidth) }
isRightOpen = false
}
else -> {
scope.launch { rightOffsetX.snapTo(rightOffsetX.value + delta) }
}
}
}
fun showLeft(){
if(!leftDrawerEnabled){ return }
scope.launch {
rightOffsetX.snapTo(maxWidth)/*hide right first*/
leftOffsetX.animateTo(leftThreshold)/*then show left*/
}
if(!leftOffsetX.isRunning){ isLeftOpen = true }
}
fun hideLeft(){
scope.launch {
leftOffsetX.animateTo(-maxWidth)
}
if(!leftOffsetX.isRunning){ isLeftOpen = false }
}
fun showRight(){
if(!rightDrawerEnabled){ return }
scope.launch {
leftOffsetX.snapTo(-maxWidth)/*hide left first*/
rightOffsetX.animateTo(rightThreshold)/*then show right*/
}
if(!rightOffsetX.isRunning){ isRightOpen = true }
}
fun hideRight(){
scope.launch {
rightOffsetX.animateTo(maxWidth)
}
if(!rightOffsetX.isRunning){ isRightOpen = false }
}
internal fun onConfigurationChange(){
WriteLog("leftThreshold: $leftThreshold")
if(isLeftOpen){
scope.launch {leftOffsetX.snapTo(leftThreshold)}
} else{
scope.launch {leftOffsetX.snapTo(-maxWidth)}
}
if(isRightOpen){
scope.launch { rightOffsetX.snapTo(rightThreshold) }
} else{
scope.launch { rightOffsetX.snapTo(maxWidth) }
}
}
companion object{
val OffsetSaver = listSaver<Animatable<Float, AnimationVector1D>, Any>(
save = { listOf(it.value)},
restore = { Animatable(it[0] as Float, Float.VectorConverter, Spring.DefaultDisplacementThreshold) }
)
}
}
@Composable
fun DoubleDrawerLayout(
state: DoubleDrawerState = remember{ DoubleDrawerState() },
leftDrawerWidth: Dp = 300.dp,
leftDrawerContent: @Composable BoxScope.() -> Unit = { LeftDrawerTestContent { state.hideLeft() } },
leftDrawerEnabled: Boolean = true,
rightDrawerWidth: Dp = 250.dp,
rightDrawerContent: @Composable BoxScope.() -> Unit = { RightDrawerTestContent { state.hideRight() } },
rightDrawerEnabled: Boolean = true,
body: @Composable BoxScope.() -> Unit = { TestBody() }
) {
val scope = rememberCoroutineScope()
state.scope = scope
BoxWithConstraints(modifier = Modifier.fillMaxSize()) {
val constraintScope = this
val density = LocalDensity.current
state.maxWidth = with(density){ constraintScope.maxWidth.toPx()}
LaunchedEffect(Unit) {
state.leftOffsetX.snapTo(-state.maxWidth)
state.rightOffsetX.snapTo(state.maxWidth)
}
state.leftDrawerWidthPx = with(density){ (leftDrawerWidth).toPx()}
state.leftThreshold = -(state.maxWidth - state.leftDrawerWidthPx)
state.leftDrawerEnabled = leftDrawerEnabled
state.rightDrawerWidthPx = with(density){ (rightDrawerWidth).toPx()}
state.rightThreshold = (state.maxWidth - state.rightDrawerWidthPx)
state.rightDrawerEnabled = rightDrawerEnabled
state.fiftyPercentL = (state.leftDrawerWidthPx * 50) / 100
state.fiftyPercentR = (state.rightDrawerWidthPx * 50) / 100
Box(
modifier = Modifier
.matchParentSize()
.pointerInput(Unit) {
detectDragGestures(
onDrag = { change: PointerInputChange, dragAmount: Offset ->
change.consumeAllChanges()
state.onDrag(dragAmount.x)
},
onDragEnd = {
state.onDragEnd()
}
)
}
){
MemberListScreen()(vm = hiltViewModel(), {}, { photoId, personId -> })
//body()
LeftDrawerBody(
leftOffsetX = state.leftOffsetX.value,
leftThreshold = state.leftThreshold,
leftDrawerWidth = leftDrawerWidth,
onHideRequest = {state.hideLeft()},
content = leftDrawerContent
)
RightDrawerBody(
rightOffsetX = state.rightOffsetX.value,
rightThreshold = state.rightThreshold,
rightDrawerWidth = rightDrawerWidth,
onHideRequest = {state.hideRight()},
content = rightDrawerContent
)
}
}
}
@Composable
private fun LeftDrawerBody(
leftOffsetX: Float,
leftThreshold: Float,
leftDrawerWidth: Dp,
onHideRequest: () -> Unit,
content: @Composable BoxScope.() -> Unit
) {
Scrim(
open = leftOffsetX >= leftThreshold,
onClose = { onHideRequest() },
opacity = {0f},
color = Color.Transparent//DrawerDefaults.scrimColor
)
Surface(
modifier = Modifier
.fillMaxSize()
.offset { IntOffset(x = leftOffsetX.roundToInt(), y = 0) }
) {
Box(modifier = Modifier.fillMaxSize()){
Box(
modifier = Modifier
.fillMaxHeight()
.width(leftDrawerWidth)
.border(width = 5.dp, color = Color.Yellow)
.align(Alignment.TopEnd)
){
content()
}
}
}
}
@Composable
private fun RightDrawerBody(
rightOffsetX: Float,
rightThreshold: Float,
rightDrawerWidth: Dp,
onHideRequest: () -> Unit,
content: @Composable BoxScope.() -> Unit
) {
Scrim(
open = rightOffsetX == rightThreshold,
onClose = { onHideRequest() },
opacity = {0f},
color = Color.Transparent//DrawerDefaults.scrimColor
)
Surface(
modifier = Modifier
.fillMaxSize()
.offset { IntOffset(x = rightOffsetX.roundToInt(), y = 0) }
) {
Box(modifier = Modifier.fillMaxSize()){
Box(
modifier = Modifier
.fillMaxHeight()
.width(rightDrawerWidth)
.border(width = 5.dp, color = Color.Yellow)
.align(Alignment.TopStart)
){
content()
}
}
}
}
@Composable
private fun TestBody() {
LazyColumn(modifier = Modifier.fillMaxSize()){
for(i in 1..50){
item {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
){
OutlinedButton(
onClick = {},
content = { Text(text = "Double!") },
modifier = Modifier.padding(start = 16.dp)
)
Text(
text = "#$i. Double Drawer Layout...",
modifier = Modifier
.weight(1f)
.padding(start = 16.dp)
)
OutlinedButton(
onClick = {},
content = { Text(text = "Click Me!") },
modifier = Modifier.padding(end = 16.dp)
)
}
Divider()
}
}
}
}
@Composable
private fun BoxScope.LeftDrawerTestContent(onHideRequest: () -> Unit) {
Button(
onClick = { onHideRequest() },
content = { Text(text = "#Click to hide!") },
modifier = Modifier.align(Alignment.Center)
)
}
@Composable
private fun BoxScope.RightDrawerTestContent(onHideRequest: () -> Unit) {
Button(
onClick = { onHideRequest() },
content = { Text(text = "Click to hide!") },
modifier = Modifier.align(Alignment.Center)
)
}
@Composable
private fun Scrim(
open: Boolean,
onClose: () -> Unit,
opacity: () -> Float,
color: Color
) {
val closeDrawer = "getString(Strings.CloseDrawer)"
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 = opacity())
}
}
问题内容: 我有两个桌子:玩具和游戏。 一个小孩可以有多个玩具。一个小孩可以一次参加多个游戏。 我想要一个查询,该查询将为我提供little_kid涉及的玩具和游戏的总数。 基本上,我想要这两个查询的总和: 是否可以在单个SQL查询中获得它?显然,我可以通过编程的方式对它们进行汇总,但这并不是很理想。 (我意识到人为的例子使模式看起来效率低下。让我们假设我们无法更改模式。) 问题答案: 包装它们并
我在写一个程序,允许用户通过菜单选项画出不同的形状,画出后的形状需要在同一屏幕上,但问题是在菜单中选择另一个选项画出另一个形状后,前一个形状就消失了。我该怎么解决这个?这是我的程序: 仅在中。
问题内容: 如何从一个活动屏幕导航到另一个活动屏幕?在第一个屏幕中,我有一个按钮,如果我单击该按钮,则必须移至另一个“活动”屏幕。 问题答案:
问题内容: 每当我使用外键指向对象B编辑对象A时,在对象B的选择旁边都会有一个加号选项“添加另一个”。如何删除该选项? 我配置了没有权限添加对象B的用户。加号仍然可用,但是当我单击它时,它显示“权限被拒绝”。它很丑。 我正在使用Django 1.0.2 问题答案: 不建议的答案 从那以后,Django使这成为可能。 您是否考虑过使用CSS来简单地不显示按钮?也许有点太过分了。 这未经测试,但我在想
问题内容: 我有两个数字。例如: 为什么数字不支持算术运算?无论如何,我将如何在Java中将这两个数字相加?(当然,我是从某个地方获取它们的,我不知道它们是Integer还是float等)。 问题答案: 你不知道数字是整数还是浮点数…使用该类时,编译器也不知道你的数字是整数,浮点数还是其他东西。结果,像+和-这样的基本数学运算符不起作用;计算机将不知道如何处理这些值。 开始编辑 根据讨论,我认为一
下面的代码是一个按钮的方法。 它总是给我一个错误。 如果我删除,那么它可以正常工作。 当我单击按钮时,如何使用POST方法,并移动到另一个活动? 错误:04-27 19:41:26.066 197 4-1974/com.opshun_test.opshun_test e/androidruntime:致命异常:main process:com.opshun_test.opshun_test,pid