본문 바로가기
안드로이드 코틀린

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

by teamnova 2024. 11. 9.
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)
    }
}

 

 

시연 영상입니다 

 

 

감사합니다