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

عنصر 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 لامبدا را فرا می‌خواند و todoItem کنونی را ارسال می‌کند. این با به‌روزرسانی مورد کار مطابقت دارد.
    • اگر محصول از انتها به ابتدا تند کشیده شود، تابع لامبدای onRemove را فرا می‌خواند و todoItem فعلی را ارسال می‌کند. این کار با حذف کردن مورد کارها مطابقت دارد.
    • ‫it != StartToEnd: اگر جهت تند کشیدن StartToEnd نباشد، این خط true را برمی‌گرداند، و درغیراین‌صورت 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 شیء را در خود جای دهد. وقتی موردی به این فهرست اضافه یا از آن برداشته می‌شود، «ترکیب» بخش‌هایی از واسط کاربر را که به آن بستگی دارد دوباره ترکیب می‌کند.
    • در mutableStateListOf()، چهار شیء TodoItem با شرح‌های مربوطه مقداردهی اولیه می‌شوند: «پرداخت صورت‌حساب»، «خرید مواد غذایی»، «رفتن به باشگاه»، و «تهیه شام».
  • 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 را ببینید.

منابع بیشتر