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

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

ממשק API

אפשר להשתמש ב-composable 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(
                        if (todoItem.isItemDone) Icons.Default.CheckBox else Icons.Default.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(
                        imageVector = Icons.Default.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 בסיום האינטראקציה עם הפריט. גוף הקריאה החוזרת מטפל בפעולות השונות האפשריות. הפונקציה הזו מחזירה ערך בוליאני שמציין אם צריך להציג אנימציה לסגירה. במקרה כזה:
    • אם מחליקים את הפריט מההתחלה לסוף, מתבצעת קריאה ל-lambda של onToggleDone, עם העברת הערך הנוכחי של todoItem. זה תואם לעדכון של פריט המשימות.
    • אם מחליקים את הפריט מהסוף להתחלה, מתבצעת קריאה ל-lambda של 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
private 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 עם התיאורים המתאימים: 'תשלום חשבונות', 'קניית מצרכים', 'ביקור בחדר כושר' ו'ארוחת ערב'.
  • LazyColumn מציג רשימה של todoItems בגלילה אנכית.
  • onToggleDone = { todoItem -> ... } היא פונקציית קריאה חוזרת (callback) שמופיעה ב-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(
                        if (todoItem.isItemDone) Icons.Default.CheckBox else Icons.Default.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(
                        imageVector = Icons.Default.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.

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