최데브는 오늘도 프로그래밍을 한다.

안드로이드 컴포즈 1주차 정리 본문

Android/Android Compose

안드로이드 컴포즈 1주차 정리

최데브 2021. 11. 7. 14:10
반응형

1주차 코드랩에서는 기존 view 의 리사이클러뷰에 버튼이 달려있고 버튼을 누르면 레이아웃이 늘어나는 간단한 앱을 만드는 것이였습니다.

 

 

코드랩 예시 이미지

위 이미지는 코드랩에 예시로 있던 이미지인데 제가 만든것과는 조금 다르지만 거의 비슷합니다.

 

진행하면서 저는 제 맘대로 조금씩 변경해가면서 만들어 봤는데요. 

일단 코드 전문부터 보시죠.

 

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            Week1JetpackComposeBasics2Theme {
                // A surface container using the 'background' color from the theme
                Surface(color = MaterialTheme.colors.background) {
                   // MyProject()
                    Greetings()
                }
            }
        }
    }
}

@Composable
private fun MyProject(names : List<String> = listOf("supreme" , "Hyo")) {
    Surface(color = MaterialTheme.colors.background) {
        Column(modifier =  Modifier.padding(4.dp)) {
            for(name in names){
                Greeting(name)
                Greeting(name)
            }
            Text("First 열")
            Text("Second 열")
        }
    }
}

@Composable
private fun Greetings(names: List<String> = List(1000) { "$it" } ) {
    //recycler랑 비슷하다 훨씬 간편하게 레이아웃을 구성하게 해준다.
    LazyColumn(modifier = Modifier.padding(vertical = 4.dp)) {
        items(items = names) { name ->
            Greeting(name = name)
        }
    }
}

@Composable
fun Greeting(name: String) {
    //var expanded = false 컴포저블 내부의 변수에 할당할 수는 없다.
    //재구성이 일어나면 값이 새상태로 재설정 될 수 있다.
    // 재구성 상태에서 값을 유지하려면 remember를 사용해야한다.
   // val expanded = remember { mutableStateOf(false) }

    //rememberSaveable 은 화면이 돌아가거나 다크모드로 변경되는 상황에도 값을 유지한다.
    var expanded by remember { mutableStateOf(false) }
    //펼치기 접기할때 변경될 padding 의 값 48dp가 됐다가 expanded 변하면 0dp가 된다.
    //val extraPadding = if (expanded.value) 48.dp else 0.dp

    //애니메이션 효과 추가하기
    val extraPadding by animateDpAsState(
        if (expanded) 48.dp else 0.dp,
        animationSpec = spring(
            dampingRatio = Spring.DampingRatioMediumBouncy,
            stiffness = Spring.StiffnessLow
        )
    )

    Surface(
        color = MaterialTheme.colors.primary
        ,modifier = Modifier.padding(vertical = 4.dp , horizontal = 8.dp))
    {
        Row(modifier =  Modifier.padding(12.dp)){
            Column(modifier = Modifier.weight(1f)
                .padding(bottom = extraPadding.coerceAtLeast(0.dp))) //여기서 extraPadding이 쓰이면서 재구성할때
            //패딩값이 변하게 된다.  coerceAtLeast은 패딩이 0dp 이하로 가지 않게 제약을 주는것
            {
                Text(text = "Hello, ")
                Text(text = name , style = MaterialTheme.typography.h4)
            }
            OutlinedButton(
                onClick = { expanded = !expanded }
            )
            {
                Text(if (expanded)  "접기" else "펼치기")
            }
        }

    }
}

@Preview(showBackground = true , widthDp = 200)
@Composable
fun DefaultPreview() {
    Week1JetpackComposeBasics2Theme {
        Greetings()
     //   MyProject()
    }
}

일단 제 기준에서 이번 코드랩을 통해 알아두면 좋겠다고 느낀 키워드부터 나열해보겠습니다.

 

1.  Column

2. Modifier

3. for

4. LazyColumn

5. animateDpAsState

 

1번부터 알아봅시다.  Column은 레이아웃입니다.

view에서 볼 수 있는 리니어레이아웃의 수직방향인데  Column 내부에 있는 요소들은 수직으로 정렬이 됩니다.

비슷한 개념으로 Row도 있는데 이는 수평 방향으로 정렬을 합니다.

 

2번은 많은 요소들에서 사용되는데 xml로 속성을 넣어서 패딩값이나 size 를 조정할때 넣는것들을 Column(modifier =  Modifier.padding(4.dp)) 이런 식으로 사용할 수 있습니다. 

 

3번은 우리가 잘 알고있는 그 for문이 맞습니다. 조금 신기했던건 

 

@Composable
private fun MyProject(names : List<String> = listOf("supreme" , "Hyo")) {
    Surface(color = MaterialTheme.colors.background) {
        Column(modifier =  Modifier.padding(4.dp)) {
            for(name in names){
                Greeting(name)
                Greeting(name)
            }
            Text("First 열")
            Text("Second 열")
        }
    }
}

그냥 이런식으로 for문을 정해진 갯수만큼 돌면 그 갯수만큼 화면에 그려진다는 점입니다. 

정말 간단하게 원하는만큼 그려낼수있다는게 저한테는 정말 편하게 다가 왔습니다.

 

4번은 LazyColumn 입니다. Column과 비슷한 역할을 하지만 기존 view를 공부할때도 리사이클러뷰라는 개념이 있습니다. 그냥 세로로 많은 요소들을 그리고 싶다면 Column에 for을 써서 1000개 10000개를 그려도 그려는지겠지만 앱을 돌려보면 정말 느려지고 앱이 꺼지기도 합니다.

이런 것 때문에 리사이클러뷰 라는 개념이 등장한것인데 한번에 모든 요소를 그리지 않고 재활용하거나 스크롤이

그 요소를 보여줘야할때 그리기 시작해서 많은 요소들이 준비되어 있지만 실제로 그려지는건 화면에 보이는 것들만 그리는 기법? 입니다.  그리고 LazyColumn  이 컴포즈에서는 이런 역할을 합니다.

 

 

5번은 animateDpAsState 입니다. 기존 xml 로 화면을 그릴때는 애니메이션을 만드는게 여간 귀찮은 일이 아니였습니다.

간단한걸 만들고 싶어도 생성해야하는 파일도 늘어나고 신경써야할게 한두개가 아닙니다.

그러나 컴포즈는 자체적으로 이런것들을 쉽게 만들 수 있게 지원합니다. 

 

    //애니메이션 효과 추가하기
    val extraPadding by animateDpAsState(
        if (expanded) 48.dp else 0.dp,
        animationSpec = spring(
            dampingRatio = Spring.DampingRatioMediumBouncy,
            stiffness = Spring.StiffnessLow
        )
    )

위 코드처럼 애니메이션이 어떤 애니메이션인지 미리 설정을 해줄 수 있습니다. expanded 라는 변수에 맞춰서 true라면 48.dp의 값을 false 라면 0.dp 의 값을 가집니다. 그리고 밑에 애니메이션 스펙에 spring 그러니까 스프링이 움직이는것 처럼 출렁이는 애니메이션을 사용한다고 적어줄수도 있죠. 

  Row(modifier =  Modifier.padding(12.dp)){
            Column(modifier = Modifier.weight(1f)
                .padding(bottom = extraPadding.coerceAtLeast(0.dp))) //여기서 extraPadding이 쓰이면서 재구성할때
            //패딩값이 변하게 된다.  coerceAtLeast은 패딩이 0dp 이하로 가지 않게 제약을 주는것
            {
                Text(text = "Hello, ")
                Text(text = name , style = MaterialTheme.typography.h4)
            }
            OutlinedButton(
                onClick = { expanded = !expanded }
            )
            {
                Text(if (expanded)  "접기" else "펼치기")
            }
        }

그리고 이렇게 extraPadding 이라는 변수로 Column의 padding에 적용 시켜줄 수 있습니다. 이렇게 되면 버튼을 onClick 할때 expanded 의 값을 바꿔주고 그것에 맞게 애니메이션 실행되는것입니다. 

 

 

추가적으로 알아할것이 있습니다. 컴포즈는 모든 요소들을 데이터의 상태로 관리합니다. 

예를 들어서 체크박스를 컴포즈에서 만들어서 누른다고 해도  데이터의 상태가 변하지 않는다면 체크 박스는 체크상태로 변하지 않습니다. 기본 view 에서는 아무런 값이 변하지 않아도 클릭은 됐었는데 컴포즈는 그렇지 않습니다.

불편하다고 생각할 수도 있지만 이것이 컴포즈의 기본 핵심이고 철학입니다. 

 

데이터의 변화를 체크해서  화면을 그리기 때문에 개발자는 화면과 데이터관리를 조금 더 편하게 구분해서 코드를 짤 수 있습니다. 선언형 UI 의 특징이기도 하죠. 더 간단하게 설명드리고 싶은데 말재주가 부족하네요 ㅠㅠ 좋은 글들이 많으니 찾아서 읽어보시면 크게 어려운 개념은 아닙니다.

 

이 이야기를 한 이유는 제 코드에서도 볼 수 있습니다. 

 

expanded 변수를 선언할때 by remember 라는게 보이는데 이는 컴포즈의 재구성 떄문입니다.

재구성이 뭐냐면 Greeting 이라는 composable 이 여러번 호출된다고 가정합니다. 그럼 그떄마다 내부에 할당된 변수들을 초기화 하게 되는데 이러면 데이터의 상태관리가 되지 않습니다. 이렇게 재구성될때도 데이터를 유지해야하는 상황(위 예제에서는 버튼이 눌려서 레이아웃이 확장이 되었나 안되었나 체크)에서는 remember 를 사용해서 해당 데이터의 상태를 기억하게 합니다. 

 

 비슷한 개념으로 rememberSaveable 이 있는데 이건 화면이 돌아가거나 다크모드로 변경됐을때도 상태를 유지시키는 기능을 하고 있습니다. 

 

저도 제대로 공부하는건 이제 막 시작한 셈이라 정확하지 않은 내용이 있을지도 모릅니다.

공부를 할때 이게 이런건가 고민이 될때 참고 정도로만 봐주셨으면 감사하겠습니다 :)

 

반응형
Comments