[Android] Jetpack Compose - LazyColumn, rememberSaveable, 애니메이션(animateDpAsState)

알린·2024년 2월 23일

Android

목록 보기
10/21

LazyColumn

  • 목록 작성 시 화면에 보이는 항목만 렌더링하므로 항목이 많은 목록을 렌더링할 때 성능이 향상
  • LazyColumnLazyRow는 Android 뷰의 RecyclerView와 동일

    🚨 LazyColumn은 RecyclerView와 같은 하위 요소를 재활용하지 않는다.
    컴포저블을 방출하는 것은 Android Views를 인스턴스화하는 것보다 상대적으로 비용이 적게 들므로 LazyColumn은 스크롤 할 때 새 컴포저블을 방출하고 계속 성능을 유지한다.

  • 범위 내에서 items 요소를 제공해
    LazyListScope.item을 사용해 단일 아이템을 추가하고,
    LazyListScope.items를 사용해 아이템 리스트를 추가

코드

LazyColumn 적용 전 코드

import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
.
.
.
@Composable
private fun Greetings(
    modifier: Modifier = Modifier,
    names: List<String> = listOf("World", "Compose")
) {
    Column(modifier = modifier.padding(vertical = 4.dp)) {
        for (name in names) {
            Greeting(name = name)
        }
    }
}

LazyColumn 적용 후 코드

@Composable
private fun Greetings(
    modifier: Modifier = Modifier,
    names: List<String> = List(1000) { "$it" }
) {
    LazyColumn(modifier = modifier.padding(vertical = 4.dp)) {
        items(items = names) { name ->
            Greeting(name = name)
        }
    }
}

위의 코드 중 LazyColumn 적용 전, 매개변수 names의 기본 목록 값을

names: List<String> = List(1000) { "$it" }

으로만 수정하였을 때 프리뷰에서 목록의 상태 반응 속도가 굉장히 느려졌었는데,
LazyColumn을 적용하니 다시 목록의 상태 반응 속도가 정상으로 돌아온 것을 느낄 수 있었다.

구현 화면

상태 유지

화면 상태 유지

  • remember 함수는 컴포저블이 컴포지션에 유지되는 동안에만 작동
    👉 리스트 실행 상태에서 기기의 화면 방향을 회전하면 리스트가 아닌 온보딩 화면이 다시 반환됨
    (기기를 회전하면 전체 활동이 다시 시작되므로 모든 상태가 손실)
  • remember를 사용하는 대신 rememberSaveable을 사용
    👉 앱 종료 전 까지는 구성 변경과 프로세스 변경에도 상태 저장

목록 항목의 펼쳐진 상태 유지

  • 목록 항목을 펼친 다음 항목이 보이지 않을 때까지 목록을 스크롤하거나, 기기를 회전한 다음 펼쳐진 항목으로 돌아가면 이제 항목이 초기 상태로 돌아옴
    👉 펼쳐진 상태에도 rememberSaveable 사용

코드

rememberSaveable 적용 전 코드

@Composable
fun MyApp(
    modifier: Modifier = Modifier,  // 빈 수정자가 할당되는 수정자 매개변수를 포함
) {
    // by : = 대신 사용, 매번 .value를 입력할 필요가 없도록 해주는 속성
    var shouldShowOnboarding by remember { mutableStateOf(true) }

    Surface(modifier) {
        if (shouldShowOnboarding) {  // onBoarding이 보여질 때 (true일 때)
            // OnboardingScreen 실행해 Button이 클릭되면 false로 바꿔주기
            OnboardingScreen(onContinueClicked = {shouldShowOnboarding = false})
        } else {  // onBoarding이 보여지지 않을 때 (false일 때)
            Greetings()  // 다른 화면 출력
        }
    }
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
     var expanded by remember { mutableStateOf(false) }
    val extraPadding = if (expanded) 48.dp else 0.dp
    Surface(
        color = MaterialTheme.colorScheme.primary,
        modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
    ) {
        Row(modifier = Modifier.padding(24.dp)) {
            Column(
                modifier = Modifier
                    .weight(1f)
                    .padding(bottom = extraPadding)
            ) {
                Text(text = "Hello")
                Text(text = name)
            }
            ElevatedButton(
                onClick = { expanded = !expanded },
            ) {
                Text(if (expanded) "Show less" else "Show more")
            }
        }
    }
}

rememberSaveable 적용 후 코드

@Composable
fun MyApp(
    modifier: Modifier = Modifier,  // 빈 수정자가 할당되는 수정자 매개변수를 포함
) {
    // by : = 대신 사용, 매번 .value를 입력할 필요가 없도록 해주는 속성
    var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }

    Surface(modifier) {
        if (shouldShowOnboarding) {  // onBoarding이 보여질 때 (true일 때)
            // OnboardingScreen 실행해 Button이 클릭되면 false로 바꿔주기
            OnboardingScreen(onContinueClicked = {shouldShowOnboarding = false})
        } else {  // onBoarding이 보여지지 않을 때 (false일 때)
            Greetings()  // 다른 화면 출력
        }
    }
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
     var expanded by rememberSaveable { mutableStateOf(false) }
    val extraPadding = if (expanded) 48.dp else 0.dp
    Surface(
        color = MaterialTheme.colorScheme.primary,
        modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
    ) {
        Row(modifier = Modifier.padding(24.dp)) {
            Column(
                modifier = Modifier
                    .weight(1f)
                    .padding(bottom = extraPadding)
            ) {
                Text(text = "Hello")
                Text(text = name)
            }
            ElevatedButton(
                onClick = { expanded = !expanded },
            ) {
                Text(if (expanded) "Show less" else "Show more")
            }
        }
    }
}

구현 화면

목록 애니메이션

  • 위의 목록 클릭 시 크기 변경에 애니메이션 적용
  • animateDpAsState 컴포저블 사용
    • 애니메이션이 완료될 때까지 애니메이션에 의해 객체의 value가 계속 업데이트되는 상태 객체를 반환
    • 유형이 Dp인 '목표 값'을 사용
  • 펼쳐진 상태에 따라 달라지는 extraPadding을 만들고 스프링 기반 애니메이션을 적용
  • 더 많은 애니메이션 기능은 안드로이드 Compose의 애니메이션 관련 공식 문서 참고

코드

animateDpAsState 추가 전 코드

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
     var expanded by rememberSaveable { mutableStateOf(false) }
    val extraPadding = if (expanded) 48.dp else 0.dp
    Surface(
        color = MaterialTheme.colorScheme.primary,
        modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
    ) {
        Row(modifier = Modifier.padding(24.dp)) {
            Column(
                modifier = Modifier
                    .weight(1f)
                    .padding(bottom = extraPadding)
            ) {
                Text(text = "Hello")
                Text(text = name)
            }
            ElevatedButton(
                onClick = { expanded = !expanded },
            ) {
                Text(if (expanded) "Show less" else "Show more")
            }
        }
    }
}

animateDpAsState 추가 후 코드

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
    var expanded by rememberSaveable { mutableStateOf(false) }
    val extraPadding by animateDpAsState(
        if (expanded) 48.dp else 0.dp,
        animationSpec = spring(
            dampingRatio = Spring.DampingRatioMediumBouncy,
            stiffness = Spring.StiffnessLow
        ), label = "padding"
    )
    Surface(
        color = MaterialTheme.colorScheme.primary,
        modifier = modifier.padding(vertical = 4.dp, horizontal = 8.dp)
    ) {
        Row(modifier = Modifier.padding(24.dp)) {
            Column(
                modifier = Modifier
                    .weight(1f)
                    .padding(bottom = extraPadding.coerceAtLeast(0.dp))
            ) {
                Text(text = "Hello")
                Text(text = name)
            }
            ElevatedButton(
                onClick = { expanded = !expanded },
            ) {
                Text(if (expanded) "Show less" else "Show more")
            }
        }
    }
}

구현 화면

profile
Android 짱이 되고싶은 개발 기록 (+ ios도 조금씩,,👩🏻‍💻)

0개의 댓글