ABOUT ME

-

  • Android) CodeLab - Jetpack Compose basics
    Android 2021. 11. 2. 23:32


    Start Compose

    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent { 
                BasicsCodelabTheme {
                    // A surface container using the 'background' color from the theme
                    Surface(color = MaterialTheme.colors.background) {
                        Greeting("Android")
                    }
                }
            }
        }
    }
    
    @Composable
    fun Greeting(name: String) {
        Text(text = "Hello $name!")
    }
    
    @Preview(showBackground = true)
    @Composable
    fun DefaultPreview() {
        BasicsCodelabTheme {
            Greeting("Android")
        }
    }
    • setContent 내에서 사용되는 앱 테마는 프로젝트 이름에 따라 지정된다. 
    • setContent 안에 XML 대신 레이아웃을 구성할 Composable 함수가 들어갈 수 있다.
    • @Composable을 사용하면 Composable function으로 만들 수 있다.
    • TextView 대신 사용할 수 있는 Text 같은 Composable function을 넣을 수 있다.
    • showBackground : 기본 배경색 없을 때 흰색으로 채움

     

     

    Tweaking th UI 

    @Composable
    fun Greeting(name: String) {
        Surface(color = MaterialTheme.colors.primary) {
            Text(text = "Hello $name!")
        }
    }
    • surface는 뷰 그룹의 역할을 한다.
    • Text는 Surface 내부에 중첩되어 있는데, 중첩될 때 백그라운드 색상이 먼저 화면에 그려진다.
    • 결과를 보면 텍스트(Hello Android)의 색이 따로 지정해 주지 않았는데 흰색으로 변경되어 있다.
    • 이유는 Surface는 텍스트에 적절한 색상을 선택하는 것과 같이 더 나은 경험을 제공하도록 제작되었기 때문이다.

     

     

     

    Modifiers

    @Composable
    fun Greeting(name: String) {
        Surface(color = MaterialTheme.colors.primary) {
            Text(text = "Hello $name!", modifier = Modifier.padding(24.dp))
        }
    }
    • Surface 및 Text와 같은 대부분의 Compose UI 요소는 선택적으로 modifier 매개변수를 허용한다.
    • modifier는 UI 요소에서 화면에 어느 위치에 배치할 것인지를 의미한다.

     

     

     

    Reusing Composables

    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                BasicsCodelabTheme {
                    BasicsCodelabTheme {
                        MyApp()
                    }
                }
            }
        }
    }
    
    @Composable
    fun Greeting(name: String) {
        Surface(color = MaterialTheme.colors.primary) {
            Text(text = "Hello $name!", modifier = Modifier.padding(24.dp))
        }
    }
    
    @Preview(showBackground = true)
    @Composable
    fun DefaultPreview() {
        BasicsCodelabTheme {
            MyApp()
        }
    }
    
    @Composable
    private fun MyApp() {
        Surface(color = MaterialTheme.colors.background) {
            Greeting(name = "Android")
        }
    }
    • UI에 구성 요소를 추가할수록 중첩이 쌓인다. 
    • 재사용 가능한 요소를 만들어 사용하는 것이 좋다.
    • 예제에서 MyApp() Composable을 재사용하여 코드 중복을 방지할 수 있다.
    • Composable을 다른 액티비티에서도 사용하도록 하려면 top level 함수로 작성하는 것이 좋다. 
    • 현재는 MyApp()에서 Greeting() 함수를 포함하기 때문에 재사용의 취지에 적합하지 않다.

     

    Creating Columns and Rows

    @Composable
    fun Greeting(name: String) {
        Surface(color = MaterialTheme.colors.primary) {
            Column(modifier = Modifier.padding(24.dp)) {
                Text(text = "Hello $name!")
                Text(text = name)
            }
        }
    }
    • Column, Row, Box를 사용해서 배열을 정할 수 있다.
    • Column은 Linear Layout의 vertical, Row는 Linear Layout의 horizontal, Box는 Frame Layout과 비슷하다.

     

     

    @Composable
    private fun MyApp(names: List<String> = listOf("World", "Compose")) {
        Column {
            for (name in names) {
                Greeting(name = name)
            }
        }
    }
    • Composable 함수는 코틀린 함수처럼 호출할 수 있다.

     

    @Composable
    fun Greeting(name: String) {
        Surface(
            color = MaterialTheme.colors.primary,
            modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
        ) {
            Column(modifier = Modifier
                .fillMaxWidth()
                .padding(24.dp)) {
                Text(text = "Hello $name!")
                Text(text = name)
            }
        }
    }
    
    @Composable
    private fun MyApp(names: List<String> = listOf("World", "Compose")) {
        Column(modifier = Modifier.padding(vertical = 4.dp)) {
            for (name in names) {
                Greeting(name = name)
            }
        }
    }
    • modifier를 사용하여 구성 요소의 크기를 지정할 수 있다.

     

     

    @Composable
    fun Greeting(name: String) {
        Surface(
            color = MaterialTheme.colors.primary,
            modifier = Modifier.padding(vertical = 4.dp, horizontal = 8.dp)
        ) {
            Row(modifier = Modifier.padding(24.dp)) {
                Column(
                    modifier = Modifier
                        .weight(1f)
                ) {
                    Text(text = "Hello $name!")
                    Text(text = name)
                }
                OutlinedButton(onClick = { /*TODO*/ }) {
                    Text(text = "Show more")
                }
            }
        }
    }
    • 컴포즈는 Material Desing Button의 spec에 따라 여러 타입의 버튼을 제공한다. (Button, OutlinedButton, TextButton)
    • 예제에서는 Text를 감싸는 OutlinedButton을 사용했다.

     

     

    State in Compose

    • 사용자 변경 사항에 반응하는 상호 작용 요소를 추가해보자.
    • Compose는 데이터가 변경될 때마다 Composable 함수를 호출해 새로운 데이터를 UI에 반영한다.
    • 코틀린 컴파일러 플러그인을 이용해 내부적으로 데이터가 변경되었을 때 Compose는 변경사항을 옵저빙 하여 업데이트한다.
    @Composable
    fun Greeting(name: String) {
        val expanded = remember { mutableStateOf(false) }
    
        val extraPadding = if (expanded.value) 48.dp else 0.dp
    
        Surface(
            color = MaterialTheme.colors.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 $name!")
                    Text(text = name)
                }
                OutlinedButton(onClick = {
                    expanded.value = !expanded.value
                }) {
                    Text(if (expanded.value) "Show less" else "Show more")
                }
            }
        }
    }
    • 버튼을 클릭 가능하게 만드는 방법을 추가하기 전에 각 항목이 확장되었는지 여부를 나타내는 값(상태)을 저장해야 한다.
    • mutableStateOf 함수를 사용해 Compose가 해당 상태를 재구성을 추적할 수 있도록 구성해야 한다.
    • State 및 MutableState는 일부 값을 보유하고 해당 값이 변경될 때마다 UI 업데이트(재구성)를 트리거하는 인터페이스이다.
    • 또한, 재구성에서 상태를 유지하려면 remember 함수를 사용한다.
    • state에 의존하고 간단한 계산을 수행하기 때문에 extraPadding에 remember를 사용하지 않아도 된다.

     

     

    State hoisting

    • Composable 함수에서 읽거나 수정한 상태는 공통 조상에 있어야 한다. 이를 hoisting이라 부른다.
    • state hoisting은 Composable 함수가 재호출 될 때 재구성의 여부를 결정하는 상태를 함수 내부가 아닌 외부로 노출시키기 위함이다.
    • Composable 상태를 저장하지 않는 방식으로 만들기 위해 호출자에게 상태를 이동시킨다.
    • 상태를 hoist 가능하게 만들면 상태 복제 및 버그 도입을 방지하고 Composable 재사용에 용이하다.
    • 반대로 Composable의 부모에 의해 제어될 필요가 없는 상태는 hoisting 돼서는 안 된다.
    class MainActivity : ComponentActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                BasicsCodelabTheme {
                    BasicsCodelabTheme {
                        MyApp()
                    }
                }
            }
        }
    }
    
    @Composable
    fun MyApp() {
    	// shouldShowOnboarding으로 state hoisting을 한다.
        var shouldShowOnboarding by remember { mutableStateOf(true) }
    
        if (shouldShowOnboarding) {
        	// 버튼을 클릭했을 때 shouldShowOnboarding의 값을 false로 바꿔준다.
            OnboardingScreen(onContinueClicked = { shouldShowOnboarding = false })
        } else {
            Greetings()
        }
    }
    
    @Composable
    fun OnboardingScreen(onContinueClicked: () -> Unit) {
    
        Surface {
            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(names: List<String> = listOf("World", "Compose")) {
        Column(modifier = Modifier.padding(vertical = 4.dp)) {
            for (name in names) {
                Greeting(name = name)
            }
        }
    }
    
    @Preview(showBackground = true, widthDp = 320, heightDp = 320)
    @Composable
    fun OnboardingPreview() {
        BasicsCodelabTheme {
            OnboardingScreen(onContinueClicked = {})
        }
    }
    
    @Composable
    private fun Greeting(name: String) {
    
        val expanded = remember { mutableStateOf(false) }
    
        val extraPadding = if (expanded.value) 48.dp else 0.dp
    
        Surface(
            color = MaterialTheme.colors.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)
                }
                OutlinedButton(
                    onClick = { expanded.value = !expanded.value }
                ) {
                    Text(if (expanded.value) "Show less" else "Show more")
                }
            }
        }
    }
    
    @Preview(showBackground = true, widthDp = 320)
    @Composable
    fun DefaultPreview() {
        BasicsCodelabTheme {
            Greetings()
        }
    }

     

     

     

    Creating a Performant lazy list

    • 이전 예제에서 2 개의 텍스트를 생성하여 보여주었다. 하지만, 1000 개의 텍스트를 생성한다면 성능에 좋지 않다.
    • LazyColumn, LazyRow를 사용해 개선하자. 이것은 RecyclerView와 동일한 역할을 한다.
    • 리사이클 러뷰는 성능을 위해 스크롤 시 화면에서 사라지는 뷰를 재활용하는데, LazyColumn은 재활용하지 않는다.
    • 그럼에도 불구하고 성능이 좋다. Composable은 안드로이드 뷰에 비해 비용이 저렴하기 때문이다.
    @Composable
    private fun Greetings(names: List<String> = List(1000) { "$it" } ) {
        LazyColumn(modifier = Modifier.padding(vertical = 4.dp)) {
            items(items = names) { name ->
                Greeting(name = name)
            }
        }
    }

     

     

     

    Persisting State

    • 현재 리스트가 보이는 화면에서 화면 회전 같은 Configuration Change 또는 앱 종료가 발생하면 첫 화면이 다시 보인다.
    • remember는 Composable이 Composition이 유지되는 동안에만 작동하기 때문이다.
    • 화면 회전이 발생했을 때, 액티비티는 재시작되고 모든 state를 잃는다.
    • 이 경우 remeberSaveable을 사용해 state를 보존할 수 있다.
    • var shouldShowOnboarding by rememberSaveable { mutableStateOf(true) }

     

    Animating your list

    • Compose에는 애니메이션 효과를 줄 수 있는 다양한 방법이 있다.
    • 예제에서는 animateDpAsState(애니메이션이 끝날 때까지 계속해서 값이 업데이트되는 State 객체를 반환)와 animationSpec(animateDpAsState에서 선택적으로 사용할 수 있는 매개변수)을 사용했다.
    • spring은 애니메이션을 보다 자연스럽게 만들기 위한 속성이다.
    @Composable
    private fun Greeting(name: String) {
    
        val expanded = rememberSaveable { mutableStateOf(false) }
    
        val extraPadding by animateDpAsState(
            if (expanded.value) 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(24.dp)) {
                Column(
                    modifier = Modifier
                        .weight(1f)
                        .padding(bottom = extraPadding.coerceAtLeast(0.dp))
                ) {
                    Text(text = "Hello, ")
                    Text(text = name)
                }
                OutlinedButton(
                    onClick = { expanded.value = !expanded.value }
                ) {
                    Text(if (expanded.value) "Show less" else "Show more")
                }
            }
        }
    }

     

    Styling and theming your app

    • MaterialTheme은 Material 디자인 스타일링 원칙을 반영해 여러 스타일을 제공한다.  

     

    반응형

    댓글

Designed by Me.