Jetpack Compose 기초 #2

BongKu·2024년 1월 30일
0

Android

목록 보기
30/30
post-thumbnail

https://developer.android.com/codelabs/jetpack-compose-basics?hl=ko#7

  • 상태 호이스팅

State Hoisting은 상태값을 자식과 공유해서 사용하는 것이 아니라, 상태 값에 액세스해야 하는 공통 상위 요소로 상태 값을 이동하는 것을 의미합니다.

예를 들어 온보딩 화면을 만드는 상황을 가정해봅시다.
온보딩 화면에서 Continue 버튼을 클릭하면 shouldShowOnboarding 값이 false로 바뀌고 이에 따라서 온보딩 화면과 인사화면을 보여주는 로직을 MyApp 에서 처리하도록 해보겠습니다.

온보딩 화면

@Composable
fun MyApp(modifier: Modifier = Modifier) {
    Surface(modifier) {
        if (shouldShowOnboarding) { // Where does this come from?
            OnboardingScreen()
        } else {
            Greetings()
        }
    }
}

@Composable
fun OnboardingScreen(modifier: Modifier = Modifier) {
    // TODO: This state should be hoisted
    var shouldShowOnboarding by remember { mutableStateOf(true) }

    Column(
        modifier = modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("Welcome to the Basics Codelab!")
        Button(
            modifier = Modifier.padding(vertical = 24.dp),
            onClick = { shouldShowOnboarding = false }
        ) {
            Text("Continue")
        }
    }
}

하지만, 위 처럼 구현을 한다면 상태변수shouldShowOnboarding를 MyApp 에서 사용할 수 없습니다.

따라서 우리는 아래와 같이 shouldShowOnboarding 을 MyApp에서 선언을 하고 이를 콜백 을 통해서 OnboardingScreen 에서 버튼 클릭 이벤트가 발생하면 변경하도록 구현합니다.

@Composable
fun MyApp(modifier: Modifier = Modifier) {

    var shouldShowOnboarding by remember { mutableStateOf(true) }

    Surface(modifier) {
        if (shouldShowOnboarding) {
            OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
        } else {
            Greetings()
        }
    }
}

@Composable
fun OnboardingScreen(
    onContinueClicked: () -> Unit,
    modifier: Modifier = Modifier
) {

    Column(
        modifier = modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("Welcome to the Basics Codelab!")
        Button(
            modifier = Modifier
                .padding(vertical = 24.dp),
            onClick = onContinueClicked
        ) {
            Text("Continue")
        }
    }

}

상태변수를 공유하지 않고, 콜백으로 전달함으로써 다른 컴포저블이 상태를 변경하지 못하게 하고 재사용성을 높일 수 있습니다.

  • 목록 만들고 스크롤

@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)
        }
    }
}

names: List = List(1000) { "$it"} 리스트를 람다식 안의 값으로 초기화 합니다.
LazyColumn과 LazyRow는 Android 뷰의 RecyclerView와 동일합니다.

  • 애니메이션 적용

animateDpAsState 컴포저블을 사용하여 애니메이션을 적용할 수 있습니다.
이 컴포저블은 애니메이션이 완료될 때까지 애니메이션에 의해 객체의 value가 계속 업데이트되는 상태 객체를 반환합니다

기존에 구현했던 Greeting 컴포저블의 extraPadding 변수를 animateDpAsState 로 만들어주면 됩니다.

@Composable
private fun Greeting(name: String) {

  var expanded by remember { mutableStateOf(false) }

  val extraPadding by animateDpAsState(
      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")
          }

      }
  }
}

애니메이션은 animationSpec 변수를 통해서 애니메이션을 커스텀 할 수 있습니다.

@Composable
private fun Greeting(name: String) {
  val extraPadding by animateDpAsState(
      if (expanded) 48.dp else 0.dp,
      animationSpec = spring(
          dampingRatio = Spring.DampingRatioMediumBouncy,
          stiffness = Spring.StiffnessLow
      )
  )

  Surface(
  ...
          Column(modifier = Modifier
              .weight(1f)
              .padding(bottom = extraPadding.coerceAtLeast(0.dp))

  ...

  )
}
  • 추가

    버튼을 아이콘으로 대체

    하위 요소인 Icon과 함께 IconButton 컴포저블을 사용

    implementation "androidx.compose.material:material-icons-extended:$compose_version"
    
    
     IconButton(onClick = { expanded = !expanded }) {
              Icon(
                  imageVector = if (expanded) Filled.ExpandLess else Filled.ExpandMore,
                  contentDescription = if (expanded) {
                      stringResource(R.string.show_less)
                  } else {
                      stringResource(R.string.show_more)
                  }
              )
          }
    

    더보기

    항목을 펼칠 때 표시되는 Greeting 내부의 Column에 새로운 Text를 추가

    extraPadding을 삭제하고 대신 animateContentSize 수정자를 Row에 적용

    고도 및 도형 추가

    shadow 수정자를 clip 수정자와 함께 사용하여 카드 스타일을 만들 수 있습니다. 그러나 이를 정확하게 실행하는 Material 컴포저블(Card)이 있습니다. CardDefaults.cardColors를 호출하고 변경하려는 색상을 재정의하여 Card의 색상을 변경할 수 있습니다.

    @Composable
    private fun Greeting(name: String) {
      Card(
          colors = CardDefaults.cardColors(
              containerColor = MaterialTheme.colorScheme.primary
          ),
          modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
      ) {
          CardContent(name)
      }
    }

@Composable
private fun CardContent(name: String) {
var expanded by remember { mutableStateOf(false) }

Row(
    modifier = Modifier
        .padding(12.dp)
        .animateContentSize(
            animationSpec = spring(
                dampingRatio = Spring.DampingRatioMediumBouncy,
                stiffness = Spring.StiffnessLow
            )
        )
) {
    Column(
        modifier = Modifier
            .weight(1f)
            .padding(12.dp)
    ) {
        Text(text = "Hello, ")
        Text(
            text = name, style = MaterialTheme.typography.headlineMedium.copy(
                fontWeight = FontWeight.ExtraBold
            )
        )
        if (expanded) {
            Text(
                text = ("Composem ipsum color sit lazy, " +
                        "padding theme elit, sed do bouncy. ").repeat(4),
            )
        }
    }
    IconButton(onClick = { expanded = !expanded }) {
        Icon(
            imageVector = if (expanded) Filled.ExpandLess else Filled.ExpandMore,
            contentDescription = if (expanded) {
                stringResource(R.string.show_less)
            } else {
                stringResource(R.string.show_more)
            }
        )
    }
}

}

profile
화이팅

0개의 댓글