Swipeable から AnchoredDraggable に移行する

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

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

SwipeableState を AnchoredDraggableState に移行する

まず、状態ホルダーの変更を特定します。AnchoredDraggableState は継承できません。オフセットは、初期化されるまで Float.NaN として表されます。

状態ホルダーを更新する

AnchoredDraggableState は final クラスであるため、継承できません。既存のコンポーネントが SwipeableState から継承している場合は、状態ホルダーを更新して、AnchoredDraggableState から継承するのではなく、への参照を保持するようにします。

スワイプ可能

class MySwitchState: SwipeableState()

AnchoredDraggable

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

状態ホルダーは SwipeableState から継承されなくなったため、API を自分で公開する必要がある場合があります。使用できる最も一般的な API は、offset、progress、currentValue、targetValue です。

オフセットにアクセスする

Swipeable とは異なり、AnchoredDraggableState の offset は初期化されるまで 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.swipeable に代わるものです。次のセクションで説明するように、Modifier.swipeable() のパラメータの一部は AnchoredDraggableState に直接移動しました。

アンカーを定義する

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

コンストラクタ

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 パラメータがあります。たとえば、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 コンストラクタに渡される