Swipeable から AnchoredDraggable に移行する

Swipeable は、ボトムシート、ドロワー、スワイプして閉じるなど、個別の状態をスワイプで切り替えられるコンポーネントの作成に役立つ Compose Material API です。コンポーネントのサイズに依存するアンカーなど、高度なユースケースをより適切にサポートするため、Compose-Foundation 1.6.0-alpha01 で後継の AnchoredDraggable が公開されました。AnchoredDraggable は、ボトムシート、ドロワー、スワイプして閉じるなど、アンカー付きの状態を持つドラッグ可能なコンポーネントを作成するための Foundation API です。

Material の Swipeable API は、Foundation の AnchoredDraggable に置き換えられ、今後のリリースで削除される予定です。このガイドでは、Swipeable API から AnchoredDraggable に移行する方法について説明します。

SwipeableStateAnchoredDraggableState に移行する

まず、状態ホルダーの変更を確認します。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 を使用して、 オフセットを読み取るときにオフセットが初期化されていることを確認します。これにより、不整合や潜在的なバグを早期に検出できます。

@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.swipeableModifier.anchoredDraggable に移行する

Modifier.anchoredDraggable()Modifier.swipeable に代わるものです。次のセクションで説明するように、Modifier.swipeable() の一部のパラメータは AnchoredDraggableState に直接移動しました。

アンカーを定義する

DraggableAnchors ビルダー メソッドを使用してアンカーを定義します。次に、 それらを AnchoredDraggableState#updateAnchors または AnchoredDraggableState's コンストラクタに渡します。

コンストラクタ

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インターフェースを使用する代わりに、AnchoredDraggableStatepositionalThresholdには、しきい値の 位置を返すラムダ関数を受け取るパラメータがあります。たとえば、位置のしきい値が 50% の場合は、次のように表すことができます。

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

位置のしきい値が 56dp の場合は、次のように表すことができます。

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

速度のしきい値を定義する

速度のしきい値も AnchoredDraggableState のコンストラクタに渡され、ラムダとして表されます。

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 コンストラクタに渡されます