Миграция с Swipeable на AnchoredDraggable

Swipeable — это API Compose Material, который помогает создавать компоненты, позволяющие переключаться между дискретными состояниями с помощью свайпа, например, нижние панели, выдвижные панели или кнопки закрытия свайпом. Для лучшей поддержки сложных сценариев использования, таких как привязки, зависящие от размера компонента, в Compose-Foundation 1.6.0-alpha01 был опубликован преемник: AnchoredDraggable . AnchoredDraggable — это API Foundation для создания перетаскиваемых компонентов с привязанными состояниями, такими как нижние панели, выдвижные панели или кнопки закрытия свайпом.

API-интерфейсы Swipeable в Material Design устарели и заменены на AnchoredDraggable в Foundation, поэтому они будут удалены в одном из будущих релизов. В этом руководстве описывается, как перейти с API-интерфейсов Swipeable на AnchoredDraggable .

Перенести SwipeableState в AnchoredDraggableState

Начните с определения изменений в вашем контейнере состояния. От AnchoredDraggableState нельзя получить наследие, а смещение представляется как Float.NaN до инициализации.

Обновите данные владельца вашего штата.

Класс AnchoredDraggableState является финальным (final class), то есть от него нельзя наследовать. Если ваш существующий компонент наследует от SwipeableState , обновите ваш state holder, чтобы он содержал ссылку на AnchoredDraggableState вместо наследования от него:

Пролистывание

class MySwitchState: SwipeableState()

AnchoredDraggable

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

Поскольку ваш stateholder больше не наследует от SwipeableState , вам, возможно, придётся самостоятельно предоставлять API. Наиболее распространённые API, которые вы можете использовать, — это offset , progress , currentValue и targetValue .

Получите доступ к смещению

В отличие от Swipeable , offset в AnchoredDraggableState до инициализации имеет значение Float.NaN . В AnchoredDraggable якоря можно передать в конструктор AnchoredDraggableState или обновить с помощью AnchoredDraggableState#updateAnchors . Передача якорей в конструктор AnchoredDraggableState немедленно инициализирует смещение.

If your anchors depend on layout or could change, use AnchoredDraggableState#updateAnchors to avoid recreating the state when the anchors change.

Если вы используете 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) }
    }
}

Migrate Modifier.swipeable to 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) }
    )
}

обновить якоря

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 .

Определите позиционные пороговые значения

Изменились тип и имя параметра thresholds. Вместо отдельного интерфейса 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)

Передается в конструктор AnchoredDraggableState как positionalThreshold

resistance: ResistanceConfig? = …

Поддержка пока отсутствует. Актуальную информацию см. в репозитории b/288084801 .

velocityThreshold: Dp = 125.dp

Передаётся в конструктор AnchoredDraggable