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

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

ממשקי ה-API של Material‏ Swipeable הוצאו משימוש לטובת AnchoredDraggable של Foundation ויוסרו בגרסה עתידית. במדריך הזה מוסבר איך לעבור מ-Swipeable API אל 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, אפשר להעביר את העוגנים לבונה של AnchoredDraggableState או לעדכן אותם באמצעות AnchoredDraggableState#updateAnchors. העברת העוגנים לקונסטרוקטור של 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) }
    )
}

אם העוגנים סטטיים, מעבירים אותם ל-constructor. אם הם תלויים בפריסה או שהם לא סטטיים, צריך להשתמש ב-updateAnchors.

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

הסוג והשם של פרמטר הספים השתנו. במקום להשתמש בממשק ThresholdConfig נפרד, ל-AnchoredDraggableState יש פרמטר positionalThreshold שמקבל פונקציית lambda שמחזירה את המיקום של ערך הסף. לדוגמה, סף מיקום של 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)

הועבר ל-constructor של AnchoredDraggableState בתור positionalThreshold

resistance: ResistanceConfig? = …

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

velocityThreshold: Dp = 125.dp

הועבר אל ה-constructor‏ AnchoredDraggable