Jetpack Compose 기초

LeeEunJae·2022년 10월 11일
0

Study Kotlin

목록 보기
19/20
post-custom-banner

📌Jetpack Compose??


Jetpack Compse 를 간단하게 요약하자면 이렇다.

기존에 UI 를 구성하기 위해서 XML 코드를 작성했었던 것을
Jetpack Compose 를 사용하면 오로지 코드로만 UI 를 빠르고, 간략하게 만들 수 있다.

🙄 그래서 왜 xml 안쓰고, 굳이?

처음 compose 를 접했을 때 '그래서 왜 굳이 그렇게 하는데?' 하는 의문이 들었다.

구글에서는 다음과 같은 이유로 Compose 를 사용할 것을 적극 권장하고 있다.

  1. 코드 감소
    기존의 방식으로 RecyclerView 를 사용할 때를 생각해보면 겨우 리스트 하나 띄우려고 온갖 복잡한 과정들을 거쳐야한다.. viewHolder, diffUtil, onCreateViewHolder 등등 작성해야할 코드들이 너무 많다.
    하지만 Compose 의 LazyColumn 이라는걸 사용하면 단 몇줄만에 리스트를 띄울 수 있다!!!
  2. 직관적
    이 부분은 아직 동의하지 못한다... ㅋㅋ XML 작성이 익숙한 필자에겐 아직 너무나도 낯설다
  3. 빠른 개발 과정
    코드 감소로 빠른 개발이 가능할 듯 하다.
  4. 강력한 성능
    애니메이션을 설정하는 부분에서 기존 방식에 비해 강력한 것 같다.

📌 Jetpack compose 간단한 예제

이제 간단한 예제를 해보면서 Compose와 친해져보자


예제의 완성본은 다음과 같다.
사진 오른쪽 상단에 하트모양 버튼을 두고 버튼을 누르면 흰색으로 채워진 하트가 나오도록 한다. 다시 클릭시 원래 모양으로 돌아간다.


먼저 프로젝트 생성창에서 Empty Compose Activity 를 선택해서 프로젝트를 생성해주자

프로젝트 빌드가 끝나면 늘 보던 MainActivity 와는 약간 다른 모습일 것이다.
다 지우고, 이 코드를 복붙해주자.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ImageCard()
        }
    }
}

@Composable
fun ImageCard(){
    
}

원래 있던 setContentView() 가 사라지고 setContent{} 가 보인다. 비슷한 개념이라고 생각하면 될 것 같다. 화면에 보여줄 view 를 setContent{} 안에 넣는다.
ImageCard() 는 함수인데 위에 @Composable 이라는 어노테이션이 붙어있다.
@Composable 어노테이션으로 우리가 화면에 그릴 view를 만든다고 생각하자.

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ImageCard()
        }
    }
}

@Composable
fun ImageCard(){
    // 모서리가 둥근 것을 간단하게 구현하기 위해서 Card 사용
    Card(
        modifier = Modifier // 수정자
            .fillMaxWidth(0.5f) // fillMaxWidth() 의 매개변수 fraction에 0.5f 를 지정하면 화면의 1/2 만큼 차지
            .padding(16.dp),
        shape = RoundedCornerShape(8.dp),
        elevation = 5.dp
    ){
        Box( // FrameLayout 과 동일함
            modifier = Modifier
                .height(200.dp)
        ){
            Image(
                painter = painterResource(id = R.drawable.dog),
                contentDescription = "dog",
                contentScale = ContentScale.Crop // 이미지 centerCrop
            )
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.TopEnd
            ){
                IconButton(onClick = { /** 이곳에 아이콘 클릭 이벤트 구현 **/ }) {
                    Icon(
                        imageVector = Icons.Default.FavoriteBorder,
                        contentDescription = "favorite",
                        tint = Color.White
                    )
                }
            }
        }

    }
}

여기까지 작성하고 실행하면 화면구성은 끝났다.
이제 IconButton 의 onClick 을 구현하면 된다.
IconButton 을 클릭했을 때, 어떤 상태를 저장하는 변수가 필요하다. 그 상태에 따라 Icon을 변경시켜주면 된다.

@Composable
fun ImageCard(){

    var isFavorite by remember { mutableStateOf(false) }
    // 모서리가 둥근 것을 간단하게 구현하기 위해서 Card 사용
    Card(
        modifier = Modifier // 수정자
            .fillMaxWidth(0.5f) // fillMaxWidth() 의 매개변수 fraction에 0.5f 를 지정하면 화면의 1/2 만큼 차지
            .padding(16.dp),
        shape = RoundedCornerShape(8.dp),
        elevation = 5.dp
    ){
        Box( // FrameLayout 과 동일함
            modifier = Modifier
                .height(200.dp)
        ){
            Image(
                painter = painterResource(id = R.drawable.dog),
                contentDescription = "dog",
                contentScale = ContentScale.Crop // 이미지 centerCrop
            )
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.TopEnd
            ){
                IconButton(onClick = { isFavorite = !isFavorite }) {
                    Icon(
                        imageVector = if(isFavorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder,
                        contentDescription = "favorite",
                        tint = Color.White
                    )
                }
            }
        }

    }
}

IconButton 을 클릭했을 때 상태를 저장하기 위해서 remember 와 mutableStateOf 함수를 사용합니다.

onClick 에는 isFavorite 을 변경하고, 상태에 따라 Icon 을 다르게 보이도록 합니다.

이렇게 구현하고 실행해보면, 정상적으로 동작한다.
하지만, 아이콘을 한 번 클릭하고, 화면을 회전해보면, 아이콘이 다시 원래 상태로 돌아가있다.
화면이 회전됨에 따라 액티비티가 초기화가 되는데, 그래서 그 상태도 같이 초기화 되는 것이다.

이 문제를 해결하기 위해 remember 에서 rememberSaveable 로 변경한다.

var isFavorite by rememberSaveable { mutableStateOf(false) }

ImageCard를 재사용하기 위한 코드 수정

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            var isFavorite by rememberSaveable { mutableStateOf(false) }
            ImageCard(
                modifier = Modifier // 수정자
                    .fillMaxWidth(0.5f) // fillMaxWidth() 의 매개변수 fraction 에 0.5f 를 지정하면 화면의 1/2 만큼 차지
                    .padding(16.dp),
                isFavorite = isFavorite
            ){ favorite ->
                isFavorite = favorite
            }
        }
    }
}

@Composable
fun ImageCard(
    modifier: Modifier,
    isFavorite: Boolean,
    onTabFavorite: (Boolean) -> Unit
){
    // 모서리가 둥근 것을 간단하게 구현하기 위해서 Card 사용
    Card(
        modifier = modifier,
        shape = RoundedCornerShape(8.dp),
        elevation = 5.dp
    ){
        Box( // FrameLayout 과 동일함
            modifier = Modifier
                .height(200.dp)
        ){
            Image(
                painter = painterResource(id = R.drawable.dog),
                contentDescription = "dog",
                contentScale = ContentScale.Crop // 이미지 centerCrop
            )
            Box(
                modifier = Modifier.fillMaxSize(),
                contentAlignment = Alignment.TopEnd
            ){
                IconButton(onClick = { onTabFavorite(!isFavorite) }) {
                    Icon(
                        imageVector = if(isFavorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder,
                        contentDescription = "favorite",
                        tint = Color.White
                    )
                }
            }
        }

    }
}

상태를 저장하는 변수와 modifier 를 외부로 빼내서 코드를 재사용 할 수 있도록 한다.

profile
매일 조금씩이라도 성장하자
post-custom-banner

0개의 댓글