滑動即可關閉或更新

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(
                        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 lambda,並傳遞目前的 todoItem。這會對應至刪除待辦事項。
    • it != StartToEnd:如果滑動方向不是 StartToEnd,這行程式碼會傳回 true,否則傳回 false。傳回 false 可避免 SwipeToDismissBox 在「切換完成」滑動後立即消失,讓使用者可透過視覺確認或動畫確認。
  • SwipeToDismissBox 可讓您在每個項目上啟用水平滑動互動功能。在其餘時間,它會顯示元件的內部內容,但當使用者開始滑動時,內容會移除,並顯示 backgroundContent。一般內容和 backgroundContent 都會取得父項容器的完整約束條件,以便在其中顯示。content 會繪製在 backgroundContent 上方。在這種情況下:
    • backgroundContent 會實作為 Icon,並以 SwipeToDismissBoxValue 為基礎設定背景顏色:
    • 滑動 StartToEnd 時的 Blue:切換待辦事項。
    • 滑動 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 會重組依賴該項目的 UI 部分。
    • mutableStateListOf() 中,四個 TodoItem 物件會以各自的說明初始化:「Pay bills」、「Buy groceries」、「Go to gym」和「Get dinner」。
  • LazyColumn 會顯示 todoItems 的垂直捲動清單。
  • onToggleDone = { todoItem -> ... } 是回呼函式,會在使用者將物件標示為完成時,從 TodoListItem 內部叫用。它會更新 todoItemisItemDone 屬性。由於 todoItemsmutableStateListOf,這項變更會觸發重新組成,進而更新 UI。
  • onRemove = { todoItem -> ... } 是使用者移除項目時觸發的回呼函式。這會從 todoItems 清單中移除特定 todoItem。這也會導致重組,且項目會從顯示的清單中移除。
  • 系統會將 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 會直接繪製至 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 原始檔案

其他資源