Android Programming 기초 - Button

2023. 12. 19. 23:26Soft_Ware/Android

안드로이드 스튜디오 개발자 페이지를 기반으로 작성됨.

https://developer.android.com/courses/pathways/android-basics-compose-unit-1-pathway-1?hl=ko

 

Kotlin 프로그래밍 소개  |  Android Basics Compose - First Android app  |  Android Developers

Kotlin에서 Android 앱 빌드를 준비하기 위해 Kotlin의 입문 프로그래밍 개념을 알아봅니다.

developer.android.com

 

이번 블로그에서는 Android Studio에서 Button Composable을 추가하고 Button 클릭에 응답하는 방법에 대해 소개해보려고 한다.

 

이전까지는 텍스트 및 이미지 Composable을 통해 단순히 앱을 장식하는 방법에 대해서만 다뤘지만, 이번 블로그를 통해 앱과 상호작용하여 상태를 변화시키는 예제를 살펴볼 것이다.

 

 Android Developer 페이지에서는 버튼을 통해 랜덤으로 주사위를 굴리는 앱을 만드는 방법에 대해서 소개하고, 필자 역시 해당 예시를 기반으로 Button Composable에 대해서 소개해보고자 한다.

 

Button Composable

 

우선 새로운 프로젝트를 생성할 것이기 때문에 Android Studio를 실행시킨 후 File → New → New Project 이후 대화상자에서 Empty Activity를 선택한다.

 

적절한 프로젝트 이름을 설정 후 프로젝트를 생성한다.

 

기존에 있던 샘플 코드를 지우고 (onCreate 함수 본문의 샘플 코드도 삭제) Custom composable function과 preview function을 정의해보자.

 

@Preview
@Composable
fun DiceRollerApp(modifier: Modifier = Modifier) {
    DiceWithButtonAndImage(
        modifier = Modifier
            .fillMaxSize()
            .wrapContentSize(Alignment.Center)
    )
}

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

}

 

Modifier (수정자)에 관해서는 이전 블로그에서도 많이 다뤘었는데, UI 구성에 있어 추가적인 장식을 적용시키는 객체이다. 패딩, 레이아웃 사이즈 등등을 조절할 수 있고 위 코드에서는 UI를 화면의 최대 사이즈로 맞추고, UI 구성요소가 화면 사이즈보다 작을 경우 화면의 중앙에 정렬되도록 Alignment 매개변수를 받았다.

 

이제, DiceWithButtonAndImage function 본문을 채워야 한다. 채우기 전에 우리가 만들어야 할 UI를 확인해보자.

주사위 앱 UI

위의 화면이 우리가 구현하고 싶은 최종적인 화면이다.

 

Roll이라는 버튼을 누르면 위의 주사위 이미지가 랜덤으로 바뀌어 앱과 상호작용 할 수 있게 된다.

 

위와 같은 화면은 우리가 이전에 학습했던 Column composable을 활용할 수 있겠다.

 

fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    Column (
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {}
}

 

Column composable 함수도 후행 람다 문법이 쓰였는데, 자세한 내용은 https://heavy-rainy.tistory.com/71을 참고하기 바란다.

 

Kotlin 기초 문법 - 람다 표현식

안드로이드 스튜디오 개발자 페이지를 기반으로 작성됨. https://developer.android.com/courses/pathways/android-basics-compose-unit-1-pathway-1?hl=ko Kotlin 프로그래밍 소개 | Android Basics Compose - First Android app | Android De

heavy-rainy.tistory.com

 

modifier은 이전에 매개변수로 넘겼던 객체를 활용하기로 하고, 앱의 모습처럼 우리는 Column component들도 중앙 정렬이 되어있기를 바랄테니 Alignment.CenterHorizontally 를 설정해준다.

 

이제, Button Composable을 활용하는 방법에 대해서 확인해보기로 하자.

 

Column(
    modifier = modifier,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Button (onClick = { /*TODO*/ }) {
        Text("Roll")
    }
}

 

버튼 안에 들어가야할 특정 UI가 있어야 하므로 필자는 Text로 지정해서 기입해주었으며, 상황에 따라서 Image나 Icon 등 다른 Composable function이 들어가도 무방할 것이다.

 

이렇게 작성하고 실행시키면,

주사위 앱 중간 상태

 

위와 같이 중앙에 Button이 배치되고, 눌러도 아무 동작이 없는 상태일 것이다.

 

이유는 간단히 짐작할 수 있겠지만, Button의 매개변수에 있는 onClick 람다 표현식에 아무 동작 명령이 없기 때문이다. 

 

우리는 다음과 같은 프로세스로 이 앱을 완성시킬 것이다.

  • 버튼 상단에 Image composable 추가
  • 변수를 하나 선언하고 버튼을 클릭할 때마다 변수가 변경되도록 로직 구현
  • 변수에 따라 버튼 상단 Image가 변경되도록 구현

 

첫 번째 프로세스인 "버튼 상단에 Image Composable 추가" 는 예전에 올렸던 블로그에서 소개했으므로 생략하고 코드만 살펴보겠다. 이미지 소스는 하단 링크를 참고하면 되겠다.

https://github.com/google-developer-training/basic-android-kotlin-compose-training-dice-roller/raw/main/dice_images.zip

 

이미지를 다운받은 이후에는 Resource Manger를 통해 이미지를 안드로이드 스튜디오에 추가해주는 과정을 거친 후 아래 코드와 같이 이미지를 UI에 추가해준다.

Column(
    modifier = modifier,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Image(
        painter = painterResource(R.drawable.dice_1),
        contentDescription = "1"
    )
    Spacer(modifier = Modifier.height(16.dp))
    Button(onClick = { /*TODO*/ }) {
        Text(stringResource(id = R.string.roll))
    }
}

 

주사위 Image Resource ID만 독자의 상황에 따라서 바꾸어주면 될 것이고, Button 안의 Text Composable을 보면 string 리터럴이 string resource로 바뀐 것을 확인할 수 있을 것이다. 이러한 부분도 코드의 재사용성 때문에 수정한 부분이다.

 

이제 변수를 하나 만들고, 버튼을 클릭했을 때 이 변수가 변경되도록 코드를 수정해보자.

 

fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    var result = 1
    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(
            painter = painterResource(R.drawable.dice_1,
            contentDescription = "1"
        )
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { result = (1..6).random() }) {
            Text(stringResource(R.string.roll))
        }
    }
}

 

DiceWithButtonAndImage Composable 함수 안에서 result 변수를 정의해주고, Button 안의 onClick 함수도 원하는대로 변경된 것을 확인할 수 있다. 코틀린에서 랜덤 값을 얻기 위해서 위와 같이 작성할 수 있다. 

 

아직은 버튼을 클릭했을 경우, UI가 변하지 않는다. 왜냐하면 result 변수에 따라서 이미지가 변경되도록 우리가 코드를 작성한 것이 없기 때문이다.

 

우리가 UI를 구성하기 위해 사용한 composable function들은 기본적으로 "Stateless"이다. Stateless는 앱에서 상태가 없는 구성요소이다. 시스템에서 언제든지 재설정 될 수 있는 요소이고, 위의 코드처럼 설령 값을 가지고 있고 버튼을 눌렀을 때 값이 바뀌더라도 재구성될 경우 즉시 값이 다시 초기 값으로 재설정되기 때문에 값을 가진다고 할 수 없다.

 

Compose를 활용하면 remember composable을 사용하여 메모리에 객체를 저장할 수 있다. 즉, 위에서 설정한 변수를 메모리에 저장시켜놓고 시스템에서 UI가 재구성되더라도 값이 재설정되는 것을 막을 수 있는 것이다. 따라서 result 변수를 remember 컴포저블로 바꾸어주면 된다.

 

var result by remember { mutableStateOf(1) }

 

mutableStateOf() 함수는 observable을 반환하는데, 이 부분은 간단히 result 변수 값이 업데이트 되면 재구성이 트리거되고 result 변수가 업데이트 되어 UI에 반영한다는 것만 알아두자. (observable은 추후에 포스팅할 예정이다.)

 

이제 result 값에 따라서 imageResource를 설정하여 버튼을 클릭할 때마다 주사위 이미지가 변경되도록 하는 코드를 작성해보자.

 

fun DiceWithButtonAndImage(modifier: Modifier = Modifier) {
    var result by remember { mutableStateOf(1) }
    val imageResource = when (result) {
        1 -> R.drawable.dice_1
        2 -> R.drawable.dice_2
        3 -> R.drawable.dice_3
        4 -> R.drawable.dice_4
        5 -> R.drawable.dice_5
        else -> R.drawable.dice_6
    }

    Column(
        modifier = modifier,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Image(
            painter = painterResource(imageResource),
            contentDescription = result.toString()
        )
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = { result = (1..6).random() }) {
            Text(stringResource(id = R.string.roll))
        }
    }
}

 

imageResource ID에 해당하는 부분을 조건문을 통해 표현되도록 하고 이 값을 Image Composable 함수에 활용한 모습이다. 이제 앱이 우리가 원하는대로 작동할 것이다.

 

전체 코드는 아래 링크를 참고하기를 바란다.

https://github.com/seof622/kotlin_android_practice/tree/master/DiceRoller