מעבר מניתנת להחלקה לגרירה במעוגנות

Swipeable הוא ממשק API של Compose Material שעוזר ליצור רכיבים שאפשר להחליק ביניהם בין מצבים נפרדים, כמו גיליונות תחתונים, תיבות אחסון או החלקה כדי לסגור. כדי לתמוך טוב יותר בתרחישי שימוש מתקדמים, כמו עוגנים שמסתמכים על גודל הרכיב, פרסמנו את AnchoredDraggable – רכיב חלופי ב-Compose-Foundation 1.6.0-alpha01. AnchoredDraggable הוא ממשק API בסיסי ליצירת רכיבים שניתן לגרור עם מצבים מוצמדים, כמו חלוניות תחתונות, תיבות אחסון או החלקה כדי לסגור.

ממשקי ה-API של Swipeable ב-Material הוצאו משימוש לטובת AnchoredDraggable ב-Foundation, והם יוסרו במהדורה עתידית. במדריך הזה מוסבר איך לעבור ממשקי API של Swipeable ל-AnchoredDraggable.

העברה של SwipeableState אל AnchoredDraggableState

קודם כול, צריך לזהות שינויים בבעלים של המצב. אי אפשר לרשת את AnchoredDraggableState, וההיסט מיוצג כ-Float.NaN לפני שהוא מאופשר.

עדכון של בעל הסטטוס

AnchoredDraggableState היא כיתה סופית, כלומר אי אפשר לרשת ממנה. אם הרכיב הקיים עובר בירושה מ-SwipeableState, מעדכנים את הבעלים של המצב כך שיכיל הפניה ל-AnchoredDraggableState במקום לעבור בירושה ממנו:

עם פונקציית החלקה

class MySwitchState: SwipeableState()

AnchoredDraggable

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

מכיוון שבעלים המצב כבר לא יורש מ-SwipeableState, יכול להיות שתצטרכו לחשוף את ממשקי ה-API בעצמכם. ממשקי ה-API הנפוצים ביותר שבהם אפשר להשתמש הם offset, ‏ progress, ‏ currentValue ו-targetValue.

גישה להיסט

בניגוד ל-Swipeable, הערך של offset ב-AnchoredDraggableState הוא Float.NaN לפני שהוא מופעל. ב-AnchoredDraggable, אפשר להעביר את הוויתורים ל-constructor של AnchoredDraggableState או לעדכן אותם דרך AnchoredDraggableState#updateAnchors. העברת האוחזים ל-constructor של 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, כפי שמתואר בקטעים הבאים.

הגדרת עוגנים

מגדירים את הווידג'טים באמצעות שיטת ה-builder‏ DraggableAnchors. לאחר מכן מעבירים אותם למבנה של AnchoredDraggableState#updateAnchors או AnchoredDraggableState:

Constructor

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) }
    )
}

אם ה-anchors סטטיים, מעבירים אותם למבנה ה-constructor. אם הם תלויים בפריסה או לא סטטיים, צריך להשתמש ב-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() } },
    ...
)

הגדרת ערכי סף למהירות

ערכי הסף של המהירות מועברים גם למבנה ה-constructor של 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)

מועבר ל-constructor של AnchoredDraggableState כ-positionalThreshold

resistance: ResistanceConfig? = …

החשבון עדיין לא נתמך. הסטטוס העדכני מופיע בבקשה b/288084801.

velocityThreshold: Dp = 125.dp

מועברים למבנה ה-constructor של AnchoredDraggable