从 Swipeable 迁移到 AnchoredDraggable

Swipeable 是一种 Compose Material API,可帮助您构建 可以在不同的状态(如底部动作条、抽屉式导航栏或 滑动关闭。为了更好地支持高级用例,例如锚点 取决于组件大小,后续版本发布于 Compose-Foundation 1.6.0-alpha01:AnchoredDraggableAnchoredDraggable 是用于构建具有锚定状态的可拖动组件的基础 API, 显示为底部动作条、抽屉式导航栏或滑动关闭。

Material 的 Swipeable API 已废弃,取而代之的是 Foundation 的 AnchoredDraggable,并将在未来的版本中移除。本指南 介绍了如何从 Swipeable API 迁移到 AnchoredDraggable

SwipeableState 迁移到 AnchoredDraggableState

首先,确定对状态容器的更改。AnchoredDraggableState 且该偏移量在它之前表示为 Float.NaN 进行了初始化。

更新状态容器

AnchoredDraggableState 是最终类,这意味着它无法继承 。如果现有组件继承自 SwipeableState,请更新 您的状态容器来保存对 AnchoredDraggableState 的引用,而不是 继承:

可滑动浏览

class MySwitchState: SwipeableState()

锚定拖动

class MySwitchState {
    private val anchoredDraggableState = AnchoredDraggableState(...)
}

由于您的状态容器不再从 SwipeableState 继承,因此您 可能必须自行公开 API您可以使用的最常用 API 包括 offsetprogresscurrentValuetargetValue

获取偏移值

与在 Swipeable 中不同,AnchoredDraggableStateoffset 早于 SwipeableFloat.NaN 完成初始化在 AnchoredDraggable 中,可将锚点传递给 AnchoredDraggableState 的构造函数或通过 AnchoredDraggableState#updateAnchors。将锚点传递给 AnchoredDraggableState 的构造函数会立即初始化偏移量。

如果您的锚点依赖于布局或者可能会发生变化,请使用 AnchoredDraggableState#updateAnchors,以避免在创建 定位点发生变化

如果您使用 updateAnchors,则在传递Float.NaN 锚定到 updateAnchors。为避免意外将 Float.NaN 传递给 组件,请使用 AnchoredDraggableState#requireOffset 来要求 在读取时已初始化偏移量。这有助于您 或可能出现的 bug 问题。

@Composable
fun AnchoredDraggableBox() {
    val state = remember { AnchoredDraggableState(...) }
    val density = LocalDensity.current
    val anchors = remember { DraggableAnchors { ... } }
    SideEffect {
        state.updateAnchors(anchors)
    }
    Box(
        Modifier.offset { IntOffset(x = state.requireOffset(), y = 0) }
    }
}

Modifier.swipeable 迁移到 Modifier.anchoredDraggable

Modifier.anchoredDraggable() 取代了 Modifier.swipeable。部分 的 Modifier.swipeable() 参数已移至 AnchoredDraggableState 如以下部分所述。

定义锚点

使用 DraggableAnchors 构建器方法定义锚点。然后,将 将其发送到 AnchoredDraggableState#updateAnchorsAnchoredDraggableState 的 构造函数:

构造函数

enum class DragValue { Start, Center, End }

@Composable
fun AnchoredDraggableBox() {
    val anchors = DraggableAnchors {
        Start at -100.dp.toPx()
        Center at 0f
        End at 100.dp.toPx()
    }
    val state = remember {
        AnchoredDraggableState(anchors = anchors)
    }
    Box(
        Modifier.offset { IntOffset(x = state.requireOffset(), y = 0) }
    )
}

updateAnchors

enum class DragValue { Start, Center, End }

@Composable
fun AnchoredDraggableBox() {
    val state = remember { AnchoredDraggableState(...) }
    val density = LocalDensity.current
    val anchors = with (density) {
        DraggableAnchors {
            Start at -100.dp.toPx()
            Center at 0f
            End at 100.dp.toPx()
        }
    }
    SideEffect {
        state.updateAnchors(anchors)
    }
    Box(
        Modifier.offset { IntOffset(x = state.requireOffset(), y = 0) }
    )
}

如果锚点是静态的,则将其传递给构造函数。如果它们依赖于 非静态布局,请使用 updateAnchors

指定位置阈值

阈值参数的类型和名称已更改。与使用 单独的 ThresholdConfig 接口,但 AnchoredDraggableState 有一个 positionalThreshold 参数,它接受一个可返回 阈值的位置。例如,排名阈值为 50% 可以 表示为:

val anchoredDraggableState = AnchoredDraggableState(
    positionalThreshold = { distance -> distance * 0.5f },
    ...
)

位置阈值 56dp 可表示为:

val density = LocalDensity.current
val anchoredDraggableState = AnchoredDraggableState(
    positionalThreshold = { with(density) { 56.dp.toPx() } },
    ...
)

定义速度阈值

速度阈值也会传递给 AnchoredDraggableState 的构造函数。 也可以表示为 lambda:

val density = LocalDensity.current
val anchoredDraggableState = AnchoredDraggableState(
    velocityThreshold = { with(density) { 125.dp.toPx() } },
    ...
)

API Surface 的变化

下文提供了 API Surface 变更的概览。

AnchoredDraggableState

SwipeableState

AnchoredDraggableState

open class SwipeableState(initialValue: T, animationSpec: AnimationSpec = …, confirmStateChange: (T) -> Boolean = …)

class AnchoredDraggableState( initialValue: T, animationSpec: AnimationSpec = …, confirmValueChange: (T) -> Boolean = …, positionalThreshold: Density.(Float) -> Float = …, velocityThreshold: Dp = …)

offset: State

offset: Float
requireOffset()

progress: SwipeProgress

progress: Float [0f..1f]

currentValue: T

currentValue: T

targetValue: T

targetValue: T

direction: Float [-1f, 0f, 1f]

不适用

suspend animateTo(
targetValue: T,
anim: AnimationSpec = …)

suspend animateTo(
targetState: T,
velocity: Float =
lastVelocity)

suspend snapTo(targetValue: T)

suspend snapTo(targetValue: T)

performDrag(delta: Float)

dispatchRawDelta(delta: Float)

suspend performFling(velocity: Float)

suspend settle(velocity: Float)

isAnimationRunning: Boolean

isAnimationRunning: Boolean

lastVelocity: Float

Modifier.anchoredDraggable

Modifier.swipeable

Modifier.anchoredDraggable

state: SwipeableState

state: AnchoredDraggableState

anchors: Map

AnchoredDraggableState#updateAnchors
or

AnchoredDraggableState#constructor

orientation: Orientation

orientation: Orientation

enabled: Boolean = true

enabled: Boolean = true

reverseDirection: Boolean = false

reverseDirection: Boolean = false

interactionSource: MutableInteractionSource? = null

interactionSource: MutableInteractionSource? = null

thresholds: (from: T, to: T) -> ThresholdConfig = FixedThreshold(56.dp)

作为 positionalThreshold 传递给 AnchoredDraggableState 构造函数

resistance: ResistanceConfig? = …

尚不支持。如需了解最新状态,请参阅 b/288084801

velocityThreshold: Dp = 125.dp

已传递给 AnchoredDraggable 构造函数