从 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 的引用,而不是继承它:

可滑动

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

指定位置阈值

阈值参数的类型和名称已更改。AnchoredDraggableState 没有单独的 ThresholdConfig 接口,而是有一个 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 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 构造函数