从 Swipeable 迁移到 AnchoredDraggable

Swipeable 是一个 Compose Material API,可帮助您构建可以在离散状态(例如底部动作条、抽屉式导航栏或滑动移除)之间滑动的组件。为了更好地支持高级用例(例如依赖于组件尺寸的锚点),Compose-Foundation 1.6.0-alpha01 发布了一个后续 API:AnchoredDraggableAnchoredDraggable 是一个 Foundation API,用于构建具有锚定状态的可拖动组件,例如底部动作条、抽屉式导航栏或滑动移除。

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

SwipeableState 迁移到 AnchoredDraggableState

首先确定状态持有者的变化。AnchoredDraggableState 不能被继承,并且在初始化之前,偏移量用 Float.NaN 表示。

更新您的状态持有者

AnchoredDraggableState 是一个最终类 (final class),这意味着它不能被继承。如果您现有的组件继承自 SwipeableState,请更新您的状态持有者,使其持有 AnchoredDraggableState 的引用,而不是继承它。

Swipeable

class MySwitchState: SwipeableState()

AnchoredDraggable

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

由于您的状态持有者不再继承自 SwipeableState,您可能需要自行公开 API。您可以使用的最常用的 API 包括 offsetprogresscurrentValuetargetValue

访问偏移量

Swipeable 不同,AnchoredDraggableStateoffset 在初始化之前是 Float.NaN。在 AnchoredDraggable 中,锚点可以传递给 AnchoredDraggableState 的构造函数,或者通过 AnchoredDraggableState#updateAnchors 进行更新。将锚点传递给 AnchoredDraggableState 的构造函数会立即初始化偏移量。

如果您的锚点依赖于布局或可能会更改,请使用 AnchoredDraggableState#updateAnchors,以避免在锚点更改时重新创建状态。

如果您使用 updateAnchors,在将锚点传递给 updateAnchors 之前,偏移量将是 Float.NaN。为避免意外将 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.swipeableModifier.swipeable() 的一些参数已直接移至 AnchoredDraggableState,如下文所述。

定义锚点

使用 DraggableAnchors builder 方法定义锚点。然后,将它们传递给 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 参数,它接受一个 lambda 函数,该函数返回阈值的位置。例如,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 接口变化

以下是 API 接口变化的概览。

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]

N/A

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

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 构造函数