Перетаскиваемый дочерний элемент предотвращает перетаскивание панели навигации в Jetpack Compose

Поэтому я переписываю пользовательский интерфейс приложения с помощью Jetpack Compose. Я реализовал панель навигации, используя обычную функцию Scaffold. По умолчанию это предоставляет два способа открыть ящик: либо нажмите значок навигации, либо перетащите его в сторону конца экрана. Рассматриваемый экран представляет собой 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) }
            )
        }
    }
}

2 ответа

Вы можете легко уменьшить диапазон прокрутки, используя отступы, например:

      AnimatedVisibility(
    visibleState = animatedVisibilityState,
    enter = fadeIn(1f),
    exit = shrinkVertically(Alignment.CenterVertically) + fadeOut()
) {
    Box(
        modifier = Modifier
            .fillMaxWidth()
    ) {
        Box(
            modifier = Modifier
                .matchParentSize()
                .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)
                    }
                )
        )
        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) }
        )
    }
}

Я не уверен, что это что-то Scaffoldдолжен нести ответственность, если вы считаете, что должен - создать проблему в системе отслеживания проблем

Это тот подход, который я в конечном итоге использовал после того, как Филип дал свой ответ. Он немного «менее квадратный». В итоге - ключ был просто в том, чтобы позволить родительскому блоку обрабатывать щелчок - позволяя отдельному блоку сосредоточиться исключительно на смахивании - а сам элемент фида не обрабатывает щелчки

      
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
    )
}

В примере в приложении, где прокручиваемая область выделена рамкой:

Другие вопросы по тегам