在 ModalNavigationDrawer 上按返回按钮会导航回去,而不是关闭抽屉。

huangapple go评论70阅读模式
英文:

Pressing back button on a ModalNavigationDrawer navigates back instead of closing the drawer

问题

我正在使用Jetpack Compose和Compose Destinations来开发Android应用。我的应用有一个ModalNavigationDrawer。模态框通过手势打开和关闭,但当模态框打开时,我希望能够使用返回按钮来关闭它。这似乎是一种自然的操作,在我使用的几乎所有其他应用中都有效。

然而,返回按钮只是关闭(最小化)应用程序或导航到上一个屏幕。我认为这是因为Compose Destinations正在捕获返回操作并从返回堆栈中弹出。然而,在文档中一开始并没有提到如何解决这个问题。唯一提到抽屉的地方是这里,但这与目前的问题无关。

我在SO上找到了一些关于如何拦截返回按键的帖子,但对我来说这些方法似乎有点不正规。这似乎是一个基本的用例,应该有一个简单而合理的解决方案。因此,我来问一下是否有推荐的解决方法。

主要应用程序看起来像这样:

@Composable
fun App() {
    val navEngine = rememberNavHostEngine()
    val navController = navEngine.rememberNavController()
    val startRoute = HomeRouteDestination

    Surface(
        modifier = Modifier.fillMaxSize(),
        color = MaterialTheme.colorScheme.background
    ) {
        DestinationsNavHost(
            engine = navEngine,
            navController = navController,
            navGraph = NavGraphs.root,
            startRoute = startRoute
        )
    }
}

我的抽屉组件:

private data class DrawerItem(
    val icon: ImageVector,
    val name: String,
    val destination: DirectionDestination
)

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NavDrawerWrapper(
    navigator: DestinationsNavigator = EmptyDestinationsNavigator,
    content: @Composable (() -> Unit, () -> Unit) -> Unit
) {
    val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
    val scope = rememberCoroutineScope()

    val openDrawer: () -> Unit = { scope.launch { drawerState.open() } }
    val closeDrawer: () -> Unit = { scope.launch { drawerState.close() } }

    val items = listOf(
        DrawerItem(Icons.Default.List, "Menu", HomeRouteDestination),
        DrawerItem(Icons.Default.Settings, "Settings", SettingsRouteDestination)
    )

    var selectedItem by remember { mutableStateOf(items[0]) }

    ModalNavigationDrawer(
        drawerState = drawerState,
        drawerContent = {
            DrawerSheet(
                items, selectedItem,
                onSelectItem = { item ->
                    closeDrawer()
                    selectedItem = item
                    navigator.navigate(item.destination)
                }
            )
        },
        content = { content(openDrawer, closeDrawer) }
    )
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DrawerSheet(
    items: List<DrawerItem> = listOf(),
    selectedItem: DrawerItem? = null,
    onSelectItem: (DrawerItem) -> Unit = {}
) {
    ModalDrawerSheet {
        LazyColumn(modifier = Modifier.padding(horizontal = 15.dp, vertical = 20.dp)) {
            items(items.size) { index ->
                val item = items[index]
                NavigationDrawerItem(
                    label = { Text(text = item.name) },
                    icon = { Icon(imageVector = item.icon, contentDescription = item.name) },
                    selected = item == selectedItem,
                    onClick = { onSelectItem(item) }
                )
            }
        }

    }
}

希望这可以帮助你解决问题。如果你需要更多的帮助,请随时告诉我。

英文:

I'm working on an Android app using Jetpack Compose, and Compose Destinations for navigation. My app has a ModalNavigationDrawer. The modal opens and closes fine using gestures, but when the modal is open I'd like it to be able to close using the back button. This feels like a natural action, and works in just about any other app I've used.

Instead, the back button just closes (minimises) the app, or navigates to the previous screen. I assume this is because Compose Destinations is catching the back action and popping off the back stack. However, the documentation doesn't mention anything about solving this issue at first glance. The only time drawers are mentioned are here, but this isn't relevant to the issue at hand.

I've found a few posts on SO about how to intercept the back press, but these seem a bit hacky to me. This seems like a basic use case that should have an easy and decent solution. Hence, I came to ask if there is a recommended way of solving this.

The main app looks like this:

@Composable
fun App() {
    val navEngine = rememberNavHostEngine()
    val navController = navEngine.rememberNavController()
    val startRoute = HomeRouteDestination

    Surface(
        modifier = Modifier.fillMaxSize(),
        color = MaterialTheme.colorScheme.background
    ) {
        DestinationsNavHost(
            engine = navEngine,
            navController = navController,
            navGraph = NavGraphs.root,
            startRoute = startRoute
        )
    }
}

My drawer component:

private data class DrawerItem(
    val icon: ImageVector,
    val name: String,
    val destination: DirectionDestination
)

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NavDrawerWrapper(
    navigator: DestinationsNavigator = EmptyDestinationsNavigator,
    content: @Composable (() -&gt; Unit, () -&gt; Unit) -&gt; Unit
) {
    val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
    val scope = rememberCoroutineScope()

    val openDrawer: () -&gt; Unit = { scope.launch { drawerState.open() } }
    val closeDrawer: () -&gt; Unit = { scope.launch { drawerState.close() } }

    val items = listOf(
        DrawerItem(Icons.Default.List, &quot;Menu&quot;, HomeRouteDestination),
        DrawerItem(Icons.Default.Settings, &quot;Settings&quot;, SettingsRouteDestination)
    )

    var selectedItem by remember { mutableStateOf(items[0]) }

    ModalNavigationDrawer(
        drawerState = drawerState,
        drawerContent = {
            DrawerSheet(
                items, selectedItem,
                onSelectItem = { item -&gt;
                    closeDrawer()
                    selectedItem = item
                    navigator.navigate(item.destination)
                }
            )
        },
        content = { content(openDrawer, closeDrawer) }
    )
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun DrawerSheet(
    items: List&lt;DrawerItem&gt; = listOf(),
    selectedItem: DrawerItem? = null,
    onSelectItem: (DrawerItem) -&gt; Unit = {}
) {
    ModalDrawerSheet {
        LazyColumn(modifier = Modifier.padding(horizontal = 15.dp, vertical = 20.dp)) {
            items(items.size) { index -&gt;
                val item = items[index]
                NavigationDrawerItem(
                    label = { Text(text = item.name) },
                    icon = { Icon(imageVector = item.icon, contentDescription = item.name) },
                    selected = item == selectedItem,
                    onClick = { onSelectItem(item) }
                )
            }
        }

    }
}

答案1

得分: 1

以下是翻译的代码部分:

更短的解决方案是在每个屏幕级别需要更多自定义时使用 `BackHandler`

    BackHandler(
        enabled = drawerState.isOpen,
    ) {
        closeDrawer()
    }
我有一些实用方法和可重用的 Composable 用来处理这个问题我在我的项目中使用它我在这里分享它供参考根据您的需求进行更改

由于我在整个应用程序中都使用它所以它相当大我有一个单一的 Scaffold这个 Composable 对我有效

[GitHub 链接以供探索](https://github.com/Abhimanyu14/finance-manager/blob/main/codebase/android/core/ui/src/main/java/com/makeappssimple/abhimanyu/financemanager/android/core/ui/common/BottomSheetUtil.kt)

**代码**

    @Composable
    fun BottomSheetHandler(
        showModalBottomSheet: Boolean,
        bottomSheetType: BottomSheetType,
        coroutineScope: CoroutineScope,
        modalBottomSheetState: ModalBottomSheetState,
        keyboardController: SoftwareKeyboardController?,
        resetBottomSheetType: () -> Unit,
    ) {
        BottomSheetDisposeHandler(
            modalBottomSheetState = modalBottomSheetState,
            resetBottomSheetType = resetBottomSheetType,
        )
    
        BottomSheetTypeChangeHandler(
            showModalBottomSheet = showModalBottomSheet,
            bottomSheetType = bottomSheetType,
            coroutineScope = coroutineScope,
            modalBottomSheetState = modalBottomSheetState,
            keyboardController = keyboardController,
        )
    }
    
    @Composable
    private fun BottomSheetDisposeHandler(
        modalBottomSheetState: ModalBottomSheetState,
        resetBottomSheetType: () -> Unit,
    ) {
        if (modalBottomSheetState.currentValue != ModalBottomSheetValue.Hidden) {
            DisposableEffect(
                key1 = Unit,
            ) {
                onDispose {
                    resetBottomSheetType()
                }
            }
        }
    }
    
    @Composable
    private fun BottomSheetTypeChangeHandler(
        showModalBottomSheet: Boolean,
        bottomSheetType: BottomSheetType,
        coroutineScope: CoroutineScope,
        modalBottomSheetState: ModalBottomSheetState,
        keyboardController: SoftwareKeyboardController?,
    ) {
        LaunchedEffect(
            key1 = bottomSheetType,
        ) {
            if (showModalBottomSheet) {
                keyboardController?.hide()
                showModalBottomSheet(
                    coroutineScope = coroutineScope,
                    modalBottomSheetState = modalBottomSheetState,
                )
            } else {
                hideModalBottomSheet(
                    coroutineScope = coroutineScope,
                    modalBottomSheetState = modalBottomSheetState,
                )
            }
        }
    }
    
    @Composable
    internal fun BottomSheetBackHandler(
        enabled: Boolean,
        coroutineScope: CoroutineScope,
        modalBottomSheetState: ModalBottomSheetState,
        resetBottomSheetType: () -> Unit,
    ) {
        BackHandler(
            enabled = enabled,
        ) {
            hideModalBottomSheet(
                coroutineScope = coroutineScope,
                modalBottomSheetState = modalBottomSheetState,
            ) {
                resetBottomSheetType()
            }
        }
    }
    
    private fun showModalBottomSheet(
        coroutineScope: CoroutineScope,
        modalBottomSheetState: ModalBottomSheetState,
        action: (() -> Unit)? = null,
    ) {
        coroutineScope.launch {
            if (modalBottomSheetState.currentValue == modalBottomSheetState.targetValue) {
                modalBottomSheetState.show()
                action?.invoke()
            }
        }
    }
    
    private fun hideModalBottomSheet(
        coroutineScope: CoroutineScope,
        modalBottomSheetState: ModalBottomSheetState,
        action: (() -> Unit)? = null,
    ) {
        coroutineScope.launch {
            if (modalBottomSheetState.currentValue == modalBottomSheetState.targetValue) {
                modalBottomSheetState.hide()
                action?.invoke()
            }
        }
    }

**用法**

    BottomSheetBackHandler(
        enabled = bottomSheetType != HomeBottomSheetType.NONE,
        coroutineScope = coroutineScope,
        modalBottomSheetState = sheetState,
        resetBottomSheetType = onBackPress,
    )

我使用一个枚举来存储当前的底部抽屉类型
英文:

The shorter version of the solution is to use BackHandler if you need more customization per screen level.

BackHandler(
enabled = drawerState.isOpen,
) {
closeDrawer()
}

I have some util methods and reusable Composable to handle this which I use in my project. I am sharing the same here for reference. Change it according to your requirement.

It is quite large as I use it for my whole app. I have a single Scaffold and this composable works for me.

GitHub link for exploration

Code

@Composable
fun BottomSheetHandler(
showModalBottomSheet: Boolean,
bottomSheetType: BottomSheetType,
coroutineScope: CoroutineScope,
modalBottomSheetState: ModalBottomSheetState,
keyboardController: SoftwareKeyboardController?,
resetBottomSheetType: () -&gt; Unit,
) {
BottomSheetDisposeHandler(
modalBottomSheetState = modalBottomSheetState,
resetBottomSheetType = resetBottomSheetType,
)
BottomSheetTypeChangeHandler(
showModalBottomSheet = showModalBottomSheet,
bottomSheetType = bottomSheetType,
coroutineScope = coroutineScope,
modalBottomSheetState = modalBottomSheetState,
keyboardController = keyboardController,
)
}
@Composable
private fun BottomSheetDisposeHandler(
modalBottomSheetState: ModalBottomSheetState,
resetBottomSheetType: () -&gt; Unit,
) {
if (modalBottomSheetState.currentValue != ModalBottomSheetValue.Hidden) {
DisposableEffect(
key1 = Unit,
) {
onDispose {
resetBottomSheetType()
}
}
}
}
@Composable
private fun BottomSheetTypeChangeHandler(
showModalBottomSheet: Boolean,
bottomSheetType: BottomSheetType,
coroutineScope: CoroutineScope,
modalBottomSheetState: ModalBottomSheetState,
keyboardController: SoftwareKeyboardController?,
) {
LaunchedEffect(
key1 = bottomSheetType,
) {
if (showModalBottomSheet) {
keyboardController?.hide()
showModalBottomSheet(
coroutineScope = coroutineScope,
modalBottomSheetState = modalBottomSheetState,
)
} else {
hideModalBottomSheet(
coroutineScope = coroutineScope,
modalBottomSheetState = modalBottomSheetState,
)
}
}
}
@Composable
internal fun BottomSheetBackHandler(
enabled: Boolean,
coroutineScope: CoroutineScope,
modalBottomSheetState: ModalBottomSheetState,
resetBottomSheetType: () -&gt; Unit,
) {
BackHandler(
enabled = enabled,
) {
hideModalBottomSheet(
coroutineScope = coroutineScope,
modalBottomSheetState = modalBottomSheetState,
) {
resetBottomSheetType()
}
}
}
private fun showModalBottomSheet(
coroutineScope: CoroutineScope,
modalBottomSheetState: ModalBottomSheetState,
action: (() -&gt; Unit)? = null,
) {
coroutineScope.launch {
if (modalBottomSheetState.currentValue == modalBottomSheetState.targetValue) {
modalBottomSheetState.show()
action?.invoke()
}
}
}
private fun hideModalBottomSheet(
coroutineScope: CoroutineScope,
modalBottomSheetState: ModalBottomSheetState,
action: (() -&gt; Unit)? = null,
) {
coroutineScope.launch {
if (modalBottomSheetState.currentValue == modalBottomSheetState.targetValue) {
modalBottomSheetState.hide()
action?.invoke()
}
}
}

Usage

BottomSheetBackHandler(
enabled = = bottomSheetType != HomeBottomSheetType.NONE,
coroutineScope = coroutineScope,
modalBottomSheetState = sheetState,
resetBottomSheetType = onBackPress,
)

I use an enum to store the current bottom sheet type.

huangapple
  • 本文由 发表于 2023年6月27日 19:16:41
  • 转载请务必保留本文链接:https://go.coder-hub.com/76564309.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定