החלקה כדי לסגור או לעדכן

הרכיב SwipeToDismissBox מאפשר למשתמשים לסגור או לעדכן פריט על ידי החלקה שלו שמאלה או ימינה.

פלטפורמת ה-API

משתמשים בפונקציה הקומפוזבילית SwipeToDismissBox כדי להטמיע פעולות שמופעלות על ידי תנועות החלקה. הפרמטרים העיקריים כוללים:

  • ‫state: המצב SwipeToDismissBoxState שנוצר כדי לאחסן את הערך שנוצר על ידי חישובים בפריט ההחלקה, שמפעיל אירועים כשהוא נוצר.
  • ‫backgroundContent: תוכן קומפוזבילי שניתן להתאמה אישית ומוצג מאחורי תוכן הפריט, שנחשף כשמחליקים את התוכן.

דוגמה בסיסית: עדכון או סגירה בהחלקה

קטעי הקוד בדוגמה הזו מראים הטמעה של החלקה שמעדכנת את הפריט כשמחליקים מההתחלה לסוף, או סוגרת את הפריט כשמחליקים מהסוף להתחלה.

data class TodoItem(
    val itemDescription: String,
    var isItemDone: Boolean = false
)

@Composable
fun TodoListItem(
    todoItem: TodoItem,
    onToggleDone: (TodoItem) -> Unit,
    onRemove: (TodoItem) -> Unit,
    modifier: Modifier = Modifier,
) {
    val swipeToDismissBoxState = rememberSwipeToDismissBoxState(
        confirmValueChange = {
            if (it == StartToEnd) onToggleDone(todoItem)
            else if (it == EndToStart) onRemove(todoItem)
            // Reset item when toggling done status
            it != StartToEnd
        }
    )

    SwipeToDismissBox(
        state = swipeToDismissBoxState,
        modifier = modifier.fillMaxSize(),
        backgroundContent = {
            when (swipeToDismissBoxState.dismissDirection) {
                StartToEnd -> {
                    Icon(
                        painter = rememberVectorPainter(if (todoItem.isItemDone) AppIcons.CheckBox else AppIcons.CheckBoxOutlineBlank),
                        contentDescription = if (todoItem.isItemDone) "Done" else "Not done",
                        modifier = Modifier
                            .fillMaxSize()
                            .background(Color.Blue)
                            .wrapContentSize(Alignment.CenterStart)
                            .padding(12.dp),
                        tint = Color.White
                    )
                }
                EndToStart -> {
                    Icon(
                        painter = rememberVectorPainter(AppIcons.Delete),
                        contentDescription = "Remove item",
                        modifier = Modifier
                            .fillMaxSize()
                            .background(Color.Red)
                            .wrapContentSize(Alignment.CenterEnd)
                            .padding(12.dp),
                        tint = Color.White
                    )
                }
                Settled -> {}
            }
        }
    ) {
        ListItem(
            headlineContent = { Text(todoItem.itemDescription) },
            supportingContent = { Text("swipe me to update or remove.") }
        )
    }
}

מידע חשוב על הקוד

  • ‫swipeToDismissBoxState קובע את מצב הרכיב. הוא מפעיל את הקריאה החוזרת (callback) של confirmValueChange אחרי שהאינטראקציה עם הפריט מסתיימת. גוף הקריאה החוזרת (callback) מטפל בפעולות האפשריות השונות. הקריאה החוזרת מחזירה ערך בוליאני שמציין אם הרכיב צריך להציג אנימציה של סגירה. במקרה הזה:
    • אם מחליקים על הפריט מההתחלה עד הסוף, הוא קורא ל-lambda של onToggleDone ומעביר את ה-todoItem הנוכחי. הפעולה הזו תואמת לעדכון של הפריט ברשימת המשימות.
    • אם מחליקים את הפריט מהסוף להתחלה, הפונקציה מפעילה את פונקציית הלמבדה onRemove ומעבירה את ה-todoItem הנוכחי. הפעולה הזו תואמת למחיקת הפריט ברשימת המשימות.
    • it != StartToEnd: השורה הזו מחזירה את הערך true אם כיוון ההחלקה הוא לא StartToEnd, ואת הערך false אם הוא כן. החזרת הערך false מונעת מה-SwipeToDismissBox להיעלם מיד אחרי החלקה לסימון "בוצע", ומאפשרת להציג אישור חזותי או אנימציה.
  • SwipeToDismissBox מאפשר אינטראקציות של החלקה אופקית בכל פריט. במצב מנוחה, מוצג התוכן הפנימי של הרכיב, אבל כשהמשתמש מתחיל להחליק, התוכן זז ומופיע backgroundContent. גם התוכן הרגיל וגם backgroundContent מקבלים את האילוצים המלאים של מאגר ההורה כדי לעבד את עצמם. ה-content משורטט מעל ה-backgroundContent. במקרה הזה:
    • ‫backgroundContent מוטמע בתור Icon עם צבע רקע שמבוסס על SwipeToDismissBoxValue:
    • Blue כשמחליקים StartToEnd – החלפת מצב של משימה.
    • Red כשמחליקים EndToStart – מחיקת פריט ברשימת המשימות.
    • לא מוצג שום דבר ברקע של Settled – כשלא מחליקים את הפריט, לא מוצג שום דבר ברקע.
    • באופן דומה, הIcon שמוצג משתנה בהתאם לכיוון ההחלקה:
    • ב-StartToEnd, הסמל CheckBox מופיע כשהמשימה הושלמה, והסמל CheckBoxOutlineBlank מופיע כשהיא לא הושלמה.
    • ב-EndToStart מוצג הסמל Delete.

@Composable
fun SwipeItemExample() {
    val todoItems = remember {
        mutableStateListOf(
            TodoItem("Pay bills"), TodoItem("Buy groceries"),
            TodoItem("Go to gym"), TodoItem("Get dinner")
        )
    }

    LazyColumn {
        items(
            items = todoItems,
            key = { it.itemDescription }
        ) { todoItem ->
            TodoListItem(
                todoItem = todoItem,
                onToggleDone = { todoItem ->
                    todoItem.isItemDone = !todoItem.isItemDone
                },
                onRemove = { todoItem ->
                    todoItems -= todoItem
                },
                modifier = Modifier.animateItem()
            )
        }
    }
}

מידע חשוב על הקוד

  • ‫mutableStateListOf(...) יוצרת רשימה ניתנת לצפייה שיכולה להכיל אובייקטים מסוג TodoItem. כשמוסיפים פריט לרשימה הזו או מסירים ממנה פריט, Compose יוצר מחדש את חלקי ממשק המשתמש שתלויים בה.
    • בתוך mutableStateListOf(), ארבעה אובייקטים מסוג TodoItem מאותחלים עם התיאורים שלהם: "Pay bills",‏ "Buy groceries",‏ "Go to gym" ו-"Get dinner".
  • ‫LazyColumn מציג רשימה של todoItems עם גלילה אנכית.
  • ‫onToggleDone = { todoItem -> ... } היא פונקציית קריאה חוזרת שמופעלת מתוך TodoListItem כשהמשתמש מסמן אובייקט כ"בוצע". הוא מעדכן את המאפיין isItemDone של todoItem. מכיוון ש-todoItems הוא mutableStateListOf, השינוי הזה מפעיל קומפוזיציה מחדש ומעדכן את ממשק המשתמש.
  • ‫onRemove = { todoItem -> ... } היא פונקציית קריאה חוזרת שמופעלת כשהמשתמש מסיר את הפריט. היא מסירה את ה-todoItem הספציפי מהרשימה todoItems. זה גם גורם ליצירה מחדש, והפריט יוסר מהרשימה שמוצגת.
  • משתנה מסוג animateItem מוחל על כל TodoListItem, כך שהפונקציה placementSpec של המשתנה נקראת כשהפריט נסגר. הפעולה הזו מפעילה אנימציה של הסרת הפריט, וגם של שינוי הסדר של פריטים אחרים ברשימה.

תוצאה

בסרטון הבא מוצגת הפונקציונליות הבסיסית של החלקה לביטול מקטעי הקוד הקודמים:

איור 1. הטמעה בסיסית של החלקה לסגירה, שמאפשרת גם לסמן פריט כ"הושלם" וגם להציג אנימציה של סגירת פריט ברשימה.

הקוד המלא לדוגמה מופיע בקובץ המקור ב-GitHub.

דוגמה מתקדמת: אנימציה של צבע הרקע בהחלקה

קטעי הקוד הבאים מראים איך לשלב סף מיקום כדי להוסיף אנימציה לצבע הרקע של פריט בהחלקה.

data class TodoItem(
    val itemDescription: String,
    var isItemDone: Boolean = false
)

@Composable
fun TodoListItemWithAnimation(
    todoItem: TodoItem,
    onToggleDone: (TodoItem) -> Unit,
    onRemove: (TodoItem) -> Unit,
    modifier: Modifier = Modifier,
) {
    val swipeToDismissBoxState = rememberSwipeToDismissBoxState(
        confirmValueChange = {
            if (it == StartToEnd) onToggleDone(todoItem)
            else if (it == EndToStart) onRemove(todoItem)
            // Reset item when toggling done status
            it != StartToEnd
        }
    )

    SwipeToDismissBox(
        state = swipeToDismissBoxState,
        modifier = modifier.fillMaxSize(),
        backgroundContent = {
            when (swipeToDismissBoxState.dismissDirection) {
                StartToEnd -> {
                    Icon(
                        painter = rememberVectorPainter(if (todoItem.isItemDone) AppIcons.CheckBox else AppIcons.CheckBoxOutlineBlank),
                        contentDescription = if (todoItem.isItemDone) "Done" else "Not done",
                        modifier = Modifier
                            .fillMaxSize()
                            .drawBehind {
                                drawRect(lerp(Color.LightGray, Color.Blue, swipeToDismissBoxState.progress))
                            }
                            .wrapContentSize(Alignment.CenterStart)
                            .padding(12.dp),
                        tint = Color.White
                    )
                }
                EndToStart -> {
                    Icon(
                        painter = rememberVectorPainter(AppIcons.Delete),
                        contentDescription = "Remove item",
                        modifier = Modifier
                            .fillMaxSize()
                            .background(lerp(Color.LightGray, Color.Red, swipeToDismissBoxState.progress))
                            .wrapContentSize(Alignment.CenterEnd)
                            .padding(12.dp),
                        tint = Color.White
                    )
                }
                Settled -> {}
            }
        }
    ) {
        OutlinedCard(shape = RectangleShape) {
            ListItem(
                headlineContent = { Text(todoItem.itemDescription) },
                supportingContent = { Text("swipe me to update or remove.") }
            )
        }
    }
}

מידע חשוב על הקוד

  • drawBehind מצייר ישירות בלוח הציור מאחורי התוכן של ה-composable‏ Icon.
    • ‫drawRect() מצייר מלבן על לוח הציור וממלא את כל הגבולות של היקף הציור ב-Color שצוין.
  • כשמחליקים, צבע הרקע של הפריט משתנה בצורה חלקה באמצעות lerp.
    • כשמחליקים מ-StartToEnd, צבע הרקע משתנה בהדרגה מאפור בהיר לכחול.
    • כשמחליקים מ-EndToStart, צבע הרקע משתנה בהדרגה מאפור בהיר לאדום.
    • הערך של swipeToDismissBoxState.progress קובע את מידת המעבר מצבע אחד לצבע הבא.
  • ‫OutlinedCard מוסיף הפרדה חזותית עדינה בין הפריטים ברשימה.

@Composable
private fun SwipeItemWithAnimationExample() {
    val todoItems = remember {
        mutableStateListOf(
            TodoItem("Pay bills"), TodoItem("Buy groceries"),
            TodoItem("Go to gym"), TodoItem("Get dinner")
        )
    }

    LazyColumn {
        items(
            items = todoItems,
            key = { it.itemDescription }
        ) { todoItem ->
            TodoListItemWithAnimation(
                todoItem = todoItem,
                onToggleDone = { todoItem ->
                    todoItem.isItemDone = !todoItem.isItemDone
                },
                onRemove = { todoItem ->
                    todoItems -= todoItem
                },
                modifier = Modifier.animateItem()
            )
        }
    }
}

מידע חשוב על הקוד

  • בקטע מידע חשוב שלמעלה מופיע מידע חשוב על הקוד, שמתאר קטע קוד זהה.

תוצאה

בסרטון הבא מוצגת הפונקציונליות המתקדמת עם צבע רקע מונפש:

איור 2. הטמעה של החלקה כדי לחשוף או למחוק, עם צבעי רקע מונפשים וסף ארוך יותר לפני שהפעולה נרשמת.

הקוד המלא לדוגמה מופיע בקובץ המקור ב-GitHub.

מקורות מידע נוספים