Compose 애니메이션 효과 적용된 on / off 검색 topbar 만들기

임찬형·2022년 4월 6일
0

개인 프로젝트에 사용했던 애니메이션 효과가 적용된 on / off 검색 바를 복습 차원에서 정리해 보겠습니다.

결과물에 대한 동작은 위와 같습니다. 검색하기를 누를 경우 검색 창이 애니메이션 효과와 함께 등장하며, 뒤로 가기 버튼을 누를 경우 사라집니다.

@OptIn(ExperimentalAnimationApi::class)
@Composable
fun SearchableTopBar(
    modifier: Modifier = Modifier,
    searchMode: Boolean,
    searchText: String,
    onSearchTextChanged: (String) -> Unit,
    onSearchButtonClicked: () -> Unit
){
    TopAppBar(
        modifier = modifier,
        contentPadding = PaddingValues(8.dp),
        backgroundColor = Color.White
    ) {
        AnimatedVisibility(
            modifier = Modifier
                .weight(1f)
                .padding(4.dp),
            visible = searchMode,
            enter = scaleIn() + expandHorizontally(),
            exit = scaleOut() + shrinkHorizontally()
        ) {
            BasicTextField(
                modifier = Modifier
                    .background(
                        Color(0xDDDDDDDD),
                        RoundedCornerShape(10.dp)
                    )
                    .padding(4.dp)
                    .height(36.dp),
                value = searchText,
                onValueChange = onSearchTextChanged,
                singleLine = true,
                cursorBrush = SolidColor(MaterialTheme.colors.primary),
                textStyle = LocalTextStyle.current.copy(
                    color = MaterialTheme.colors.onSurface,
                    fontSize = MaterialTheme.typography.body2.fontSize
                ),
                decorationBox = { innerTextField ->
                    Row(
                        modifier,
                        verticalAlignment = Alignment.CenterVertically
                    ) {
                        Box(Modifier.weight(1f)) {
                            if (searchText.isEmpty()) Text(
                                text = "검색어를 입력하세요.",
                                style = LocalTextStyle.current.copy(
                                    color = MaterialTheme.colors.onSurface.copy(alpha = 0.3f),
                                    fontSize = MaterialTheme.typography.body2.fontSize
                                )
                            )
                            innerTextField()
                        }
                        IconButton(onClick = {}) {
                            Icon(
                                imageVector = Icons.Default.Search,
                                contentDescription = "Search Icon",
                                tint = LocalContentColor.current.copy(alpha = 0.5f)
                            )
                        }
                    }
                }
            )
        }
    }

    if(!searchMode) {
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .padding(top = 16.dp),
            contentAlignment = Alignment.Center
        ) {
            TextButton(
                onClick = onSearchButtonClicked
            ) {
                Icon(
                    imageVector = Icons.Filled.Search,
                    contentDescription = "",
                    tint = Color.Black
                )

                Text(
                    text = "검색하기",
                    color = Color.Black,
                    fontSize = 17.sp,
                    fontWeight = FontWeight.Bold
                )
            }
        }
    }
}

UI 요소에 해당하는 컴포저블 함수인 SearchableTopBar의 코드는 위와 같습니다.

<매개변수 설명>
1. modifier: 수정자 입니다.
2. searchMode: 검색 모드가 on / off 상태인지 확인하기 위한 Boolean 변수입니다.
3. searchText: 검색 창 텍스트에 해당합니다.
4. onSearchTextChanged: 검색 창 텍스트가 바뀔 때 호출됩니다 (onValueChanged)
5. onSearchButtonClicked: searchMode가 바뀔 때 호출됩니다. 위 예시에서는 검색하기 버튼을 누를 경우 호출됩니다.

<구현 설명>
해당 위젯은 searchMode가 false 상태일 경우 검색하기 버튼, true일 경우 검색창이 보여집니다.
따라서 먼저 TopAppbar에 searchMode가 false일 때 검색하기 버튼이 보이도록 if분으로 나누었습니다.
또한 검색하기 버튼을 누를 경우 searchMode를 true로 바꾸어야 하며, 이를 onSearchButtonClicked에서 처리할 것이므로 onClick에 해당 람다함수를 등록합니다.

검색창은 searchMode가 true일 때만 나타나고, 이에 따른 애니메이션 적용을 해야 하므로 AnimatedVisibility를 사용하였습니다.
따라서 visible 위치에 searchMode를 넣어 AnimatedVisibility를 구현하였으며
1. 들어올 때의 효과인 enter 위치에 scaleIn() + expandHorizontally()를 적용하여 양 옆으로 확대하며 보여지도록 하였고
2. 나갈 때 효과인 exit 위치에 scaleOut() + shrinkHorizontally()를 적용하여 가운데로 축소하며 사라지도록 적용하였습니다.

마지막으로 해당 위젯을 어떻게 적용하는지 정리하겠습니다.

var searchMode by remember { mutableStateOf(false) }
var searchText by remember { mutableStateOf("") }

BackHandler(searchMode) {
    searchMode = false
}

Scaffold(
    topBar = {
        SearchableTopBar(
            modifier = Modifier.fillMaxWidth(),
            searchMode = searchMode,
            searchText = searchText,
            onSearchTextChanged = { searchText = it },
            onSearchButtonClicked = { searchMode = true }
        )
    }
) {
    Text(
        text = "Screen"
    )
}

사용 방법은 생각보다 간단합니다.
검색 모드에 해당하는 상태인 searchMode와 검색창 텍스트에 해당하는 searchText만 상태로 저장하면 됩니다.

Scaffold에서 topbar로 해당 위젯을 추가하며, 람다 함수는 상태들을 적절하게 조절하면 됩니다.

추가적으로 해당 화면에서 뒤로 가기를 누를 경우 검색창을 닫기 위해 BackHandler를 사용하였습니다.
searchMode가 true 상태일 때 뒤로 가기 버튼을 누르면 searchMode가 false가 되면서 검색창이 닫히는 효과를 보입니다.

0개의 댓글