从 Swipeable 迁移到 AnchoredDraggable

Swipeable 是一个 Compose Material API,可帮助您构建可在离散状态之间滑动的组件,例如底部工作表、抽屉或滑动以关闭。为了更好地支持高级用例(例如取决于组件大小的锚点),Compose-Foundation 1.6.0-alpha01 发布了一个后续版本:AnchoredDraggableAnchoredDraggable 是一个用于构建具有锚定状态的可拖动组件的 Foundation API,例如底部工作表、抽屉或滑动以关闭。

Material 的 Swipeable API 已弃用,现在推荐使用 Foundation 的 AnchoredDraggable,并将从将来的版本中删除。本指南介绍如何从 Swipeable API 迁移到 AnchoredDraggable

SwipeableState 迁移到 AnchoredDraggableState

首先确定您状态持有者的更改。 AnchoredDraggableState 不可继承,并且偏移量表示为 Float.NaN,直到它被初始化。

更新您的状态持有者

AnchoredDraggableState 是一个最终类,这意味着它不可继承。如果您的现有组件继承自 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 来要求在读取偏移量时已将其初始化。这有助于您及早发现不一致或可能的错误。

@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 生成器方法定义锚点。然后,将它们传递给 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
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)

传递给 AnchoredDraggableState 构造函数,作为 positionalThreshold

resistance: ResistanceConfig? = …

尚不支持。有关最新状态,请参阅 b/288084801

velocityThreshold: Dp = 125.dp

传递给 AnchoredDraggable 构造函数