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

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

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

הגדרת עוגנים

מגדירים את העוגנים באמצעות שיטת ה-build של 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)

מועבר לבונה AnchoredDraggableState בתור positionalThreshold

resistance: ResistanceConfig? = …

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

velocityThreshold: Dp = 125.dp

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