Swipeable から AnchoredDraggable に移行する

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

マテリアルの Swipeable API は非推奨になりました。代わりに Foundation の AnchoredDraggable を使用してください。今後のリリースで削除される予定です。このガイドでは、Swipeable API から AnchoredDraggable に移行する方法について説明します。

SwipeableStateAnchoredDraggableState に移行する

まず、状態ホルダーに対する変更を特定します。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 を使用する場合、アンカーを 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.swipeable の代わりに Modifier.anchoredDraggable() が使用されるようになりました。以降のセクションで説明するように、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) }
    )
}

update アンカー

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 パラメータがあります。たとえば、位置しきい値が 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)

AnchoredDraggableState コンストラクタに positionalThreshold として渡されます。

resistance: ResistanceConfig? = …

現時点ではサポートされていません。最新のステータスについては、b/288084801 をご覧ください。

velocityThreshold: Dp = 125.dp

AnchoredDraggable コンストラクタに渡される