안드로이드 코틀린

[Kotlin][Android] Jetpack Compose 텍스트 아이템 리스트 swipe 하여 삭제하기

teamnova 2024. 11. 9. 14:58
728x90

 

안녕하세요 오늘은 컴포즈를 사용해 만들었던 아이템 리스트에서 
스와이프로 아이템 삭제하는 기능을 추가하려고 합니다 

 

 

먼저 여러 아이템이 담겨져 있는 리스트를 나타내는 LazyColumn  안에 SwipeToDismissBox 컴포저블(Composable)을 추가해줍니다.

이는 각 리스트 아이템을 스와이프로 삭제할 수 있게 해줍니다  

SwipeToDismissBox(
    state = dismissState,
    backgroundContent = {
        Box(
            modifier = Modifier
                .fillMaxSize()
                .background(Color.LightGray)
                .padding(16.dp)
        ) {
            Icon(
                imageVector = Icons.Default.Close,
                contentDescription = "삭제",
                tint = Color.Red
            )
        }
    },
    content = {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(vertical = 8.dp)
                .clickable {
                    selectedItems[index] = !selectedItems[index] // 체크박스 상태 토글
                }
                .padding(16.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Checkbox(
                checked = selectedItems[index],
                onCheckedChange = { checked -> selectedItems[index] = checked }
            )
            Text(
                text = item,
                color = Color.Black,
                modifier = Modifier.padding(start = 16.dp)
            )
            Spacer(modifier = Modifier.weight(1f)) // X 표시를 오른쪽으로 정렬하기 위한 spacer

            // 기존 삭제 버튼 유지
            IconButton(onClick = {
                items.removeAt(index)
                selectedItems.removeAt(index)
            }) {
                Icon(
                    imageVector = Icons.Default.Close,
                    contentDescription = "삭제",
                    tint = Color.Red
                )
            }
        }
    }
)

 

 

아이템의 현재 상태 current value 가 변경되면 특정 상태가 변경되었을 때 실행되는  LaunchedEffect 가 실행됩니다. 

여기서는 아이템이 왼쪽에서 오른쪽, 혹은 오른쪽에서 왼쪽으로 스와이프 되었을때 해당 아이템이 삭제되도록 조건을 걸어줍니다. 

if (dismissState.currentValue != SwipeToDismissBoxValue.Settled) {
    LaunchedEffect(dismissState.currentValue) {
        if (dismissState.currentValue == SwipeToDismissBoxValue.EndToStart ||
            dismissState.currentValue == SwipeToDismissBoxValue.StartToEnd) {
            // 삭제할 때 리스트 인덱스가 변경되지 않도록 지연시키는 방식 사용
            items.removeAt(index)
            selectedItems.removeAt(index)
        }
    }
}

 

 

 

 

아래는 전체 코드 입니다. 

package com.example.kotlin_example

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.example.kotlin_example.ui.theme.Kotlin_exampleTheme

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Kotlin_exampleTheme {
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    ItemList(cnt = 3)
                }
            }
        }
    }
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ItemList(cnt: Int) {
    // 아이템 리스트 초기화
    val items = remember { mutableStateListOf<String>().apply { repeat(cnt) { add("Item ${it + 1}") } } }

    // 체크 박스 상태 리스트
    val selectedItems = remember { mutableStateListOf<Boolean>().apply { repeat(cnt) { add(false) } } }

    // 새로운 아이템 입력 상태
    var newItemText by remember { mutableStateOf("") }

    Column {
        // 새로운 아이템 추가 입력 필드
        Row(modifier = Modifier.padding(16.dp)) {
            TextField(
                value = newItemText,
                onValueChange = { newItemText = it },
                label = { Text("새 아이템 입력") },
                modifier = Modifier.weight(1f)
            )
            Spacer(modifier = Modifier.width(8.dp))
            Button(onClick = {
                if (newItemText.isNotBlank()) {
                    items.add(newItemText)
                    selectedItems.add(false) // 새로운 아이템에 대해 체크박스 상태 추가
                    newItemText = "" // 입력 필드 초기화
                }
            }) {
                Text("추가")
            }
        }

        // 선택된 아이템 개수를 표시
        Text(
            text = "선택된 아이템: ${selectedItems.count { it }}",
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp),
            color = Color.Blue
        )

        // 체크박스 리스트 표시
        LazyColumn {
            itemsIndexed(items, key = { _, item -> item }) { index, item ->
                val dismissState = rememberSwipeToDismissBoxState(
                    initialValue = SwipeToDismissBoxValue.Settled,
                    confirmValueChange = {
                        if (it != SwipeToDismissBoxValue.Settled) {
                            true
                        } else {
                            false
                        }
                    }
                )

                if (dismissState.currentValue != SwipeToDismissBoxValue.Settled) {
                    LaunchedEffect(dismissState.currentValue) {
                        if (dismissState.currentValue == SwipeToDismissBoxValue.EndToStart ||
                            dismissState.currentValue == SwipeToDismissBoxValue.StartToEnd) {
                            // 삭제할 때 리스트 인덱스가 변경되지 않도록 지연시키는 방식 사용
                            items.removeAt(index)
                            selectedItems.removeAt(index)
                        }
                    }
                }

                SwipeToDismissBox(
                    state = dismissState,
                    backgroundContent = {
                        Box(
                            modifier = Modifier
                                .fillMaxSize()
                                .background(Color.LightGray)
                                .padding(16.dp)
                        ) {
                            Icon(
                                imageVector = Icons.Default.Close,
                                contentDescription = "삭제",
                                tint = Color.Red
                            )
                        }
                    },
                    content = {
                        Row(
                            modifier = Modifier
                                .fillMaxWidth()
                                .padding(vertical = 8.dp)
                                .clickable {
                                    selectedItems[index] = !selectedItems[index] // 체크박스 상태 토글
                                }
                                .padding(16.dp),
                            verticalAlignment = Alignment.CenterVertically
                        ) {
                            Checkbox(
                                checked = selectedItems[index],
                                onCheckedChange = { checked -> selectedItems[index] = checked }
                            )
                            Text(
                                text = item,
                                color = Color.Black,
                                modifier = Modifier.padding(start = 16.dp)
                            )
                            Spacer(modifier = Modifier.weight(1f)) // X 표시를 오른쪽으로 정렬하기 위한 spacer

                            // 기존 삭제 버튼 유지
                            IconButton(onClick = {
                                items.removeAt(index)
                                selectedItems.removeAt(index)
                            }) {
                                Icon(
                                    imageVector = Icons.Default.Close,
                                    contentDescription = "삭제",
                                    tint = Color.Red
                                )
                            }
                        }
                    }
                )
            }
        }
    }
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    Kotlin_exampleTheme {
        ItemList(cnt = 100)
    }
}

 

 

시연 영상입니다 

 

 

감사합니다