当列表项在撰写中可拖动时,如何通过拖动打开导航抽屉?
How can Navigation Drawer be opened with a drag when a list item is draggable in compose?
所以我正在使用 Jetpack Compose 重写应用程序的 UI。我已经使用常规的 Scaffold 函数实现了一个 Navigation Drawer。开箱即用,这提供了两种打开抽屉的方法:按 navigationIcon 或拖动到屏幕末尾。有问题的屏幕是列表项的 LazyColumn。
我稍后在这些列表项上实施了 SwipeToDismiss 模式。滑动关闭可以正常工作,但无法再拖动到任何地方以打开导航抽屉。
在旧的基于视图的系统中,导航抽屉会保留一个小宽度,您可以随时在其中拖动以打开抽屉 - 无论子项目是否支持拖动。我不确定如何使用 Compose 实现相同的效果。看起来应该是导航抽屉的工作来处理这个 - 而不是它里面的屏幕。
带导航抽屉的屏幕:
val coroutineScope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState(
rememberDrawerState(initialValue = DrawerValue.Closed)
)
Scaffold(
scaffoldState = scaffoldState,
topBar = {
TopAppBar(
title = { Text(screenTitle) },
navigationIcon = {
IconButton(
onClick = {
coroutineScope.launch {
scaffoldState.drawerState.open()
}
}
) {
Icon(
Icons.Default.Menu,
contentDescription = "Drawer toggle button"
)
}
},
actions = {
...
}
)
},
drawerContent = {
// List of stuff
...
},
floatingActionButton = {
...
}
) { padding ->
/// Layout with a LazyColumn with elements having SwipeToDismiss
...
}
并滑动以关闭项目(显示在 LazyColumn 内)
@OptIn(
ExperimentalFoundationApi::class,
ExperimentalMaterialApi::class,
ExperimentalAnimationApi::class
)
@Composable
fun SwipeableFeedItemPreview(
onSwipe: suspend () -> Unit,
onlyUnread: Boolean,
item: FeedListItem,
showThumbnail: Boolean,
imagePainter: @Composable (String) -> Unit,
onMarkAboveAsRead: () -> Unit,
onMarkBelowAsRead: () -> Unit,
onItemClick: () -> Unit
) {
val animatedVisibilityState = remember { MutableTransitionState(true) }
val swipeableState = rememberSwipeableState(initialValue = FeedItemSwipeState.NONE)
// Needs to be set once layout is complete
var itemSize by remember { mutableStateOf(Size(1f, 1f)) }
val anchors = mapOf(
0f to FeedItemSwipeState.NONE,
-itemSize.width to FeedItemSwipeState.LEFT,
itemSize.width to FeedItemSwipeState.RIGHT
)
AnimatedVisibility(
visibleState = animatedVisibilityState,
enter = fadeIn(1f),
exit = shrinkVertically(Alignment.CenterVertically) + fadeOut()
) {
Box(
modifier = Modifier
.fillMaxWidth()
.onGloballyPositioned { layoutCoordinates ->
itemSize = layoutCoordinates.size.toSize()
}
.swipeable(
state = swipeableState,
anchors = anchors,
orientation = Orientation.Horizontal,
thresholds = { _, _ ->
FractionalThreshold(0.25f)
}
)
) {
Box(
contentAlignment = swipeIconAlignment,
modifier = Modifier
.matchParentSize()
.background(color)
.padding(horizontal = 24.dp)
) {
AnimatedVisibility(
visible = swipeableState.targetValue != FeedItemSwipeState.NONE,
enter = fadeIn(),
exit = fadeOut()
) {
Icon(
when (item.unread) {
true -> Icons.Default.VisibilityOff
false -> Icons.Default.Visibility
},
contentDescription = stringResource(id = R.string.toggle_read_status)
)
}
}
FeedItemPreview(
item = item,
showThumbnail = showThumbnail,
imagePainter = imagePainter,
onMarkAboveAsRead = onMarkAboveAsRead,
onMarkBelowAsRead = onMarkBelowAsRead,
onItemClick = onItemClick,
modifier = Modifier
.offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
)
}
}
}
您可以使用填充轻松减小可滑动范围,如下所示:
enum class FeedItemSwipeState {
NONE, LEFT, RIGHT,
}
@Composable
fun TestView(
) {
val scaffoldState = rememberScaffoldState(
rememberDrawerState(initialValue = DrawerValue.Closed)
)
Scaffold(
scaffoldState = scaffoldState,
drawerContent = {
},
) {
val swipeableState = rememberSwipeableState(initialValue = FeedItemSwipeState.NONE)
// Needs to be set once layout is complete
var itemSize by remember { mutableStateOf(Size(1f, 1f)) }
val anchors = mapOf(
0f to FeedItemSwipeState.NONE,
-itemSize.width to FeedItemSwipeState.LEFT,
itemSize.width to FeedItemSwipeState.RIGHT
)
Box(
modifier = Modifier
.fillMaxWidth()
) {
Box(Modifier.fillMaxWidth()) {
Box(
modifier = Modifier
.matchParentSize()
.clickable { // clickable on whole view
}
.padding(start = 30.dp) // left distance for drawer
.onGloballyPositioned { layoutCoordinates ->
itemSize = layoutCoordinates.size.toSize()
}
.swipeable( // swipeable after padding to allow drawerContent work
state = swipeableState,
anchors = anchors,
orientation = Orientation.Horizontal,
thresholds = { _, _ ->
FractionalThreshold(0.25f)
}
)
)
Text(
"item",
modifier = Modifier
.offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
)
}
}
}
}
我不确定 Scaffold
是否应该负责,如果你认为它应该 - create an issue 关于撰写问题跟踪器
这是我在菲利普给出答案后最终使用的方法。它有点“不那么四四方方”。总之 - 关键是让父框处理点击 - 允许一个单独的框只关注滑动 - 而 feeditem 本身不处理点击
enum class FeedItemSwipeState {
NONE, LEFT, RIGHT,
}
@Composable
fun TestView(
) {
val scaffoldState = rememberScaffoldState(
rememberDrawerState(initialValue = DrawerValue.Closed)
)
Scaffold(
scaffoldState = scaffoldState,
drawerContent = {
},
) {
val swipeableState = rememberSwipeableState(initialValue = FeedItemSwipeState.NONE)
// Needs to be set once layout is complete
var itemSize by remember { mutableStateOf(Size(1f, 1f)) }
val anchors = mapOf(
0f to FeedItemSwipeState.NONE,
-itemSize.width to FeedItemSwipeState.LEFT,
itemSize.width to FeedItemSwipeState.RIGHT
)
Box(
modifier = Modifier
.fillMaxWidth()
.onGloballyPositioned { layoutCoordinates ->
itemSize = layoutCoordinates.size.toSize()
}
.combinedClickable(
onLongClick = { ... },
onClick = { ... },
)
) {
Box(
modifier = Modifier
.padding(start = 48.dp)
.matchParentSize()
.swipeable(
state = swipeableState,
anchors = anchors,
orientation = Orientation.Horizontal,
thresholds = { _, _ ->
FractionalThreshold(0.25f)
}
)
)
FeedItemPreview(
item = "item",
swipeableModifier = Modifier
.padding(start = 30.dp) // left distance for drawer
.onGloballyPositioned { layoutCoordinates ->
itemSize = layoutCoordinates.size.toSize()
}
.swipeable(
state = swipeableState,
anchors = anchors,
orientation = Orientation.Horizontal,
thresholds = { _, _ ->
FractionalThreshold(0.25f)
}
)
,
modifier = Modifier
.offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
)
}
}
}
@Composable
fun FeedItemPreview(
item: String,
modifier: Modifier,
) {
Text(
item,
modifier = modifier
)
}
以应用程序中的示例为例,其中可滑动区域由边框突出显示:
所以我正在使用 Jetpack Compose 重写应用程序的 UI。我已经使用常规的 Scaffold 函数实现了一个 Navigation Drawer。开箱即用,这提供了两种打开抽屉的方法:按 navigationIcon 或拖动到屏幕末尾。有问题的屏幕是列表项的 LazyColumn。
我稍后在这些列表项上实施了 SwipeToDismiss 模式。滑动关闭可以正常工作,但无法再拖动到任何地方以打开导航抽屉。
在旧的基于视图的系统中,导航抽屉会保留一个小宽度,您可以随时在其中拖动以打开抽屉 - 无论子项目是否支持拖动。我不确定如何使用 Compose 实现相同的效果。看起来应该是导航抽屉的工作来处理这个 - 而不是它里面的屏幕。
带导航抽屉的屏幕:
val coroutineScope = rememberCoroutineScope()
val scaffoldState = rememberScaffoldState(
rememberDrawerState(initialValue = DrawerValue.Closed)
)
Scaffold(
scaffoldState = scaffoldState,
topBar = {
TopAppBar(
title = { Text(screenTitle) },
navigationIcon = {
IconButton(
onClick = {
coroutineScope.launch {
scaffoldState.drawerState.open()
}
}
) {
Icon(
Icons.Default.Menu,
contentDescription = "Drawer toggle button"
)
}
},
actions = {
...
}
)
},
drawerContent = {
// List of stuff
...
},
floatingActionButton = {
...
}
) { padding ->
/// Layout with a LazyColumn with elements having SwipeToDismiss
...
}
并滑动以关闭项目(显示在 LazyColumn 内)
@OptIn(
ExperimentalFoundationApi::class,
ExperimentalMaterialApi::class,
ExperimentalAnimationApi::class
)
@Composable
fun SwipeableFeedItemPreview(
onSwipe: suspend () -> Unit,
onlyUnread: Boolean,
item: FeedListItem,
showThumbnail: Boolean,
imagePainter: @Composable (String) -> Unit,
onMarkAboveAsRead: () -> Unit,
onMarkBelowAsRead: () -> Unit,
onItemClick: () -> Unit
) {
val animatedVisibilityState = remember { MutableTransitionState(true) }
val swipeableState = rememberSwipeableState(initialValue = FeedItemSwipeState.NONE)
// Needs to be set once layout is complete
var itemSize by remember { mutableStateOf(Size(1f, 1f)) }
val anchors = mapOf(
0f to FeedItemSwipeState.NONE,
-itemSize.width to FeedItemSwipeState.LEFT,
itemSize.width to FeedItemSwipeState.RIGHT
)
AnimatedVisibility(
visibleState = animatedVisibilityState,
enter = fadeIn(1f),
exit = shrinkVertically(Alignment.CenterVertically) + fadeOut()
) {
Box(
modifier = Modifier
.fillMaxWidth()
.onGloballyPositioned { layoutCoordinates ->
itemSize = layoutCoordinates.size.toSize()
}
.swipeable(
state = swipeableState,
anchors = anchors,
orientation = Orientation.Horizontal,
thresholds = { _, _ ->
FractionalThreshold(0.25f)
}
)
) {
Box(
contentAlignment = swipeIconAlignment,
modifier = Modifier
.matchParentSize()
.background(color)
.padding(horizontal = 24.dp)
) {
AnimatedVisibility(
visible = swipeableState.targetValue != FeedItemSwipeState.NONE,
enter = fadeIn(),
exit = fadeOut()
) {
Icon(
when (item.unread) {
true -> Icons.Default.VisibilityOff
false -> Icons.Default.Visibility
},
contentDescription = stringResource(id = R.string.toggle_read_status)
)
}
}
FeedItemPreview(
item = item,
showThumbnail = showThumbnail,
imagePainter = imagePainter,
onMarkAboveAsRead = onMarkAboveAsRead,
onMarkBelowAsRead = onMarkBelowAsRead,
onItemClick = onItemClick,
modifier = Modifier
.offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
)
}
}
}
您可以使用填充轻松减小可滑动范围,如下所示:
enum class FeedItemSwipeState {
NONE, LEFT, RIGHT,
}
@Composable
fun TestView(
) {
val scaffoldState = rememberScaffoldState(
rememberDrawerState(initialValue = DrawerValue.Closed)
)
Scaffold(
scaffoldState = scaffoldState,
drawerContent = {
},
) {
val swipeableState = rememberSwipeableState(initialValue = FeedItemSwipeState.NONE)
// Needs to be set once layout is complete
var itemSize by remember { mutableStateOf(Size(1f, 1f)) }
val anchors = mapOf(
0f to FeedItemSwipeState.NONE,
-itemSize.width to FeedItemSwipeState.LEFT,
itemSize.width to FeedItemSwipeState.RIGHT
)
Box(
modifier = Modifier
.fillMaxWidth()
) {
Box(Modifier.fillMaxWidth()) {
Box(
modifier = Modifier
.matchParentSize()
.clickable { // clickable on whole view
}
.padding(start = 30.dp) // left distance for drawer
.onGloballyPositioned { layoutCoordinates ->
itemSize = layoutCoordinates.size.toSize()
}
.swipeable( // swipeable after padding to allow drawerContent work
state = swipeableState,
anchors = anchors,
orientation = Orientation.Horizontal,
thresholds = { _, _ ->
FractionalThreshold(0.25f)
}
)
)
Text(
"item",
modifier = Modifier
.offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
)
}
}
}
}
我不确定 Scaffold
是否应该负责,如果你认为它应该 - create an issue 关于撰写问题跟踪器
这是我在菲利普给出答案后最终使用的方法。它有点“不那么四四方方”。总之 - 关键是让父框处理点击 - 允许一个单独的框只关注滑动 - 而 feeditem 本身不处理点击
enum class FeedItemSwipeState {
NONE, LEFT, RIGHT,
}
@Composable
fun TestView(
) {
val scaffoldState = rememberScaffoldState(
rememberDrawerState(initialValue = DrawerValue.Closed)
)
Scaffold(
scaffoldState = scaffoldState,
drawerContent = {
},
) {
val swipeableState = rememberSwipeableState(initialValue = FeedItemSwipeState.NONE)
// Needs to be set once layout is complete
var itemSize by remember { mutableStateOf(Size(1f, 1f)) }
val anchors = mapOf(
0f to FeedItemSwipeState.NONE,
-itemSize.width to FeedItemSwipeState.LEFT,
itemSize.width to FeedItemSwipeState.RIGHT
)
Box(
modifier = Modifier
.fillMaxWidth()
.onGloballyPositioned { layoutCoordinates ->
itemSize = layoutCoordinates.size.toSize()
}
.combinedClickable(
onLongClick = { ... },
onClick = { ... },
)
) {
Box(
modifier = Modifier
.padding(start = 48.dp)
.matchParentSize()
.swipeable(
state = swipeableState,
anchors = anchors,
orientation = Orientation.Horizontal,
thresholds = { _, _ ->
FractionalThreshold(0.25f)
}
)
)
FeedItemPreview(
item = "item",
swipeableModifier = Modifier
.padding(start = 30.dp) // left distance for drawer
.onGloballyPositioned { layoutCoordinates ->
itemSize = layoutCoordinates.size.toSize()
}
.swipeable(
state = swipeableState,
anchors = anchors,
orientation = Orientation.Horizontal,
thresholds = { _, _ ->
FractionalThreshold(0.25f)
}
)
,
modifier = Modifier
.offset { IntOffset(swipeableState.offset.value.roundToInt(), 0) }
)
}
}
}
@Composable
fun FeedItemPreview(
item: String,
modifier: Modifier,
) {
Text(
item,
modifier = modifier
)
}
以应用程序中的示例为例,其中可滑动区域由边框突出显示: