Android SwipeToDismiss

  • SwipeToDismiss(밀어서 삭제) 방식은 리스트에서 아이템을 삭제하거나, 좋아요나 플레이리스트 등 특정 그룹에 추가하는 기능을 구현할 때 용이하다.

    Jetpack Compose에서는 이를 쉽게 제작할 수 있도록 SwipeToDismiss 라는 Composable 함수를 지원한다.

    작성 방법이 쉽기 때문에 금방 따라 작성할 수 있다.

    val dismissState = rememberDismissState(confirmStateChange = {               
        if (it == DismissValue.DismissedToStart) {                              
            /**                                                                 
             * Dismiss 되었을 때 작업할 코드 작성                                          
             */                                                                 
        }                                                                       
        true                                                                     
    })                                                                          
                                                                                
    SwipeToDismiss(state = dismissState, directions = setOf(                    
        DismissDirection.EndToStart                                             
    ), dismissThresholds = { direction ->                                       
        FractionalThreshold(0.4f)                                               
    }, background = {                                                            
        val color by animateColorAsState(                                       
            when (dismissState.targetValue) {                                   
                DismissValue.Default -> Color.White                             
                else -> Color.Red                                               
            }                                                                   
        )                                                                       
        val scale by animateFloatAsState(                                       
            if (dismissState.targetValue == DismissValue.Default) 0.75f else 1f 
        )                                                                       
        Box(                                                                    
            modifier = Modifier                                                 
                .fillMaxSize()                                                  
                .background(color)                                              
                .padding(horizontal = Dp(20f)),                                 
            contentAlignment = Alignment.CenterEnd                              
        ) {                                                                      
            Icon(                                                               
                Icons.Default.Delete,                                           
                contentDescription = "Delete Icon",                             
                modifier = Modifier.scale(scale)                                
            )                                                                   
        }                                                                       
    }, dismissContent = {                                                        
        Card(                                                                   
            shape = RoundedCornerShape(0.dp),                                   
            elevation = animateDpAsState(                                       
                if (dismissState.dismissDirection != null) 4.dp else 0.dp       
            ).value                                                             
        ) {                                                                     
            /**                                                                 
             * 원본 콘텐츠 구성가능한함수 작성                                                
             */                                                                 
        }                                                                       
    })                                                                          

    state : 앞선 코드에서 선언한 DismissState 에서 confirmStateChange 콜백 함수를 통해 SwipeToDismiss Composable 함수의 Dismiss 상태를 추적할 수 있다.

    DismissValue.DismissedToStart : 오른쪽에서 왼쪽으로 Dismiss 됨

    DismissValue.DismissedToEnd : 왼쪽에서 오른쪽으로 Dismiss 됨

    Default : Dismiss 되지 않은 상태

    의도한 방향으로 Dismiss 된 경우 리스트 아이템 삭제 등 작업을 수행하면 된다.

    dismissThresholds : Dismiss 되는 임계치 이다. 0.0f ~ 1.0f, ex) 0.5f 로 설정한 경우 50% 이상 Swipe 된 경우 Dismiss 된 것으로 간주

    background : Dismiss 된 경우 후방에 위치하는(본 컨텐츠 뒤에 위치하는) Composable 함수. 이전에 선언한 DismissState에 따라 애니메이션을 삽입 할 수 있다.

    dismissContent : 본 컨텐츠가 되는 Composable 함수 (Dismiss 되기 전까지 보여지는 컨텐츠)

    아이템 삭제시 자연스럽게 하기

    DismissedToStart등 State 반응시 즉시 아이템을 제거하는 등의 작업을 하게 되면 UI 가 부자연스럽다.

    이를 해결하기 위해, SwipeToDismiss의 modifier에 animateItemPlacement()를 적용하면 리스트에서 아이템이 제거 되었을 때 보다 자연스럽게 반응한다.

    또한 다음과 같이 일정 시간 동안 딜레이를 준 뒤 본 작업을 수행 하는 것이 더 보기 좋다.

    val dismissState = rememberDismissState(confirmStateChange = {               
        if (it == DismissValue.DismissedToStart) {                              
            CoroutineScope(Dispatchers.Main).launch {                            
                delay(400)                                                      
                viewModel.remove(resource)                                      
            }                                                                   
        }                                                                       
        true                                                                     
    })