從滑動式遷移至 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()

可拖曳

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

由於狀態容器不再從 SwipeableState 繼承,因此您可能需要自行公開 API。最常用的 API 為 offsetprogresscurrentValuetargetValue

存取偏移量

Swipeable 不同,AnchoredDraggableStateoffset 在初始化前是 Float.NaN。在 AnchoredDraggable 中,錨點可傳遞至 AnchoredDraggableState 的建構函式,或透過 AnchoredDraggableState#updateAnchors 更新。將錨點傳遞至 AnchoredDraggableState 的建構函式,會立即初始化偏移。

如果錨點需要使用版面配置或可能會變更,請使用 AnchoredDraggableState#updateAnchors,避免在錨點變更時重新建立狀態。

如果您使用 updateAnchors,則偏移值為 Float.NaN 之前,將錨點傳遞至 updateAnchors。為避免意外將 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.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

定義排名門檻

閾值參數的類型和名稱已變更。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 介面變更

請參閱下方的 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]

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 建構函式