برای بستن یا به‌روزرسانی، تند بکشید

عنصر SwipeToDismissBox به کاربر اجازه می‌دهد با تند کشیدن موردی به چپ یا راست، آن را ببندد یا به‌روز کند.

سطح میانای برنامه‌سازی کاربردی

از ترکیب‌پذیر 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 وضعیت مؤلفه را مدیریت می‌کند. وقتی تعامل با مورد تمام شود، confirmValueChange را فراخوانی می‌کند. بدنه فراخوانی برگشتی کنش‌های احتمالی مختلف را مدیریت می‌کند. فراخوانی برگشتی یک مقدار بولی برمی‌گرداند که به مؤلفه می‌گوید آیا باید پویانمایی بستن را نمایش دهد یا نه. در این مورد:
    • اگر مورد از ابتدا به انتها تند کشیده شود، onToggleDone lambda را فراخوانی می‌کند و todoItem فعلی را ارسال می‌کند. این با به‌روزرسانی مورد کارها مطابقت دارد.
    • اگر مورد از انتها به ابتدا تند کشیده شود، onRemove لامبدا را فراخوانی می‌کند، todoItem فعلی را رد می‌کند. این کار با حذف کردن مورد فهرست کارهای انجام‌دادنی مطابقت دارد.
    • it != StartToEnd: اگر جهت تند کشیدن StartToEnd نباشد، این خط true را برمی‌گرداند، و درغیراین‌صورت false را برمی‌گرداند. برگرداندن false از ناپدید شدن فوری SwipeToDismissBox پس‌از تند کشیدن «تغییر وضعیت انجام شد» جلوگیری می‌کند، و امکان تأیید بصری یا پویانمایی را فراهم می‌کند.
  • ‫SwipeToDismissBox تعاملات تند کشیدن افقی را در هر مورد فعال می‌کند. در حالت استراحت، محتوای داخلی مؤلفه را نشان می‌دهد، اما وقتی کاربر تند کشیدن را شروع می‌کند، محتوا جابه‌جا می‌شود و backgroundContent ظاهر می‌شود. Both the normal content and the backgroundContent get the full constraints of the parent container to render themselves in. 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 بخش‌هایی از واسط کاربر را که به آن وابسته است بازترکیب می‌کند.
    • Inside mutableStateListOf(), four TodoItem objects are initialized with their respective descriptions: "Pay bills", "Buy groceries", "Go to gym", and "Get dinner".
  • LazyColumn فهرستی از todoItems را نمایش می‌دهد که به‌صورت عمودی پیمایش می‌شود.
  • ‫onToggleDone = { todoItem -> ... } تابع بازخوانی است که از درون TodoListItem فراخوانده می‌شود، وقتی کاربر شیئی را به‌عنوان انجام‌شده علامت‌گذاری می‌کند. این روش ویژگی isItemDone مربوط به todoItem را به‌روز می‌کند. چون todoItems یک mutableStateListOf است، این تغییر باعث بازترکیب می‌شود و واسط کاربر را به‌روز می‌کند.
  • ‫onRemove = { todoItem -> ... } تابع بازخوانی‌ای است که وقتی کاربر مورد را برمی‌دارد راه‌اندازی می‌شود. این کار todoItem خاص را از فهرست todoItems برمی‌دارد. این کار همچنین باعث بازترکیب می‌شود و مورد از فهرست نمایش‌داده‌شده برداشته خواهد شد.
  • یک اصلاح‌کننده animateItem به هر TodoListItem اعمال می‌شود تا placementSpec اصلاح‌کننده هنگام بسته شدن مورد فراخوانی شود. این برداشتن مورد و همچنین مرتب‌سازی مجدد موارد دیگر در فهرست را پویانمایی می‌کند.

نتیجه

ویدیو زیر عملکرد اصلی تند کشیدن برای رد کردن را از قطعه‌های قبلی نشان می‌دهد:

شکل ۱. پیاده‌سازی پایه «تند کشیدن برای بستن» که هم می‌تواند موردی را به‌عنوان تکمیل‌شده علامت‌گذاری کند و هم پویانمایی بستن را برای موردی در فهرست نشان دهد.

برای کد نمونه کامل، فایل منبع 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 مستقیماً در بوم پشت محتوای ترکیب‌شدنی 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()
            )
        }
    }
}

نکات کلیدی درباره کد

  • برای نکات کلیدی درباره این کد، به نکات کلیدی از بخش قبلی مراجعه کنید که تکه‌کد مشابهی را توصیف می‌کند.

نتیجه

ویدیو زیر عملکرد پیشرفته را با رنگ پس‌زمینه پویانمایی‌شده نشان می‌دهد:

شکل ۲. پیاده‌سازی تند کشیدن برای آشکار کردن یا حذف کردن، با رنگ‌های پس‌زمینه پویانمایی‌شده و آستانه طولانی‌تر قبل‌از ثبت کنش.

برای کد نمونه کامل، فایل منبع GitHub را ببینید.

منابع بیشتر