본문 바로가기

개발일지

[Kotlin] Compose 입문 #2 - Image, Card, Scaffold, TextField, 구조분해, 코루틴 스코프

CardView와 동일한 기능을 하는 Card

// CardView와 동일한 기능
    Card(
        modifier = Modifier
            .fillMaxWidth(0.5f)
            .padding(16.dp),    // fraction으로 비율 지정 : 절반
        shape = RoundedCornerShape(8.dp),    // 모서리 둥글게
        elevation = 5.dp,
    ) {
         Box(
             modifier = Modifier.height(200.dp)
         ) {
             // Image 소스를 가져올 때는 painter 사용
            Image(
                painter = painterResource(id = R.drawable.umc),
                contentDescription = "설명 : 인생네컷",
                contentScale = ContentScale.Crop,
            )
             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,
                     )

                 }

             }
         }
    }

Image 소스를 drawable에서 직접 가져올 경우

painter를 사용한다

이미지 위에 겹쳐있는 하트 아이콘 생성 → 새로운 Box 만들기

아이콘의 상태를 기억해주고 변화시켜주기 위해 아이콘 상태 저장 변수를 따로 지정

@Composable
fun ImageCard() {
    // Icon 상태를 기억해줄 변수 - 컴포즈에서는 Boolean이 아닌 State 사용
    // value 값을 자동으로 가져오도록 getValue, setValue import
    // 자동 임포트가 안되므로 직접 해야함 -> by 사용 가능
    var isFavorite by rememberSaveable {
        mutableStateOf(false)
    }

remomberSaveable → 화면 회전시 아이콘 상태값 변화하는 상황 방지

var isFavorite = remember {} 도 가능하지만, value를 항상 명시해주어야 하는 귀찮음 발생

→ by를 사용한다 ! 그러기 위해서는 getValue, setValue 직접 임포트하기

by를 사용하면

IconButton(onClick = {
                     isFavorite = !isFavorite
                 }

isFavorite.value 가 아닌 isFavorite 만으로도 값 변경 가능.

ImageCard()를 재사용하기 위해서는

isFavorite이 외부에서 선언되어야 한다

// 컴포즈가 시작되는 영역은 setContent 부터!
        setContent {

            // Icon 상태를 기억해줄 변수 - 컴포즈에서는 Boolean이 아닌 State 사용
            // value 값을 자동으로 가져오도록 getValue, setValue import
            // 자동 임포트가 안되므로 직접 해야함 -> by 사용 가능
            var isFavorite by rememberSaveable {
                mutableStateOf(false)
            }
            
            ImageCard(
                isFavorite = isFavorite
            )
        }
    }
}

@Composable
fun ImageCard(
    isFavorite: Boolean,
) {

ImageCard에서 isFavorite 선언,

setContent에서 상태 기억 변수 선언 → 재사용 가능

이때, ImageCard에 들어오는 isFavorite은 변수가 아니라 상수이므로 값이 변화할 수 없다

IconButton(onClick = {
                     isFavorite = !isFavorite
                 }

isFavorite에서 에러 발생

콜백을 만든다 !

@Composable
fun ImageCard(
    isFavorite: Boolean,
    // 콜백 만들기 : 콜백으로 돌려줄 값은 Boolean, return은 없음
    onTabFavorite: (Boolean) -> Unit,
)
IconButton(onClick = {
                     onTabFavorite(!isFavorite)
                 }

onClick 수정

setContent {

            // Icon 상태를 기억해줄 변수 - 컴포즈에서는 Boolean이 아닌 State 사용
            // value 값을 자동으로 가져오도록 getValue, setValue import
            // 자동 임포트가 안되므로 직접 해야함 -> by 사용 가능
            var isFavorite by rememberSaveable {
                mutableStateOf(false)
            }

						ImageCard(
                isFavorite = isFavorite,
            ) {favorite ->
                isFavorite = favorite
            }

ImageCard에서 isFavorite 값을 변경해준다

Modifier 도 재사용 가능하게 외부에서 설정

fun ImageCard(
    modifier: Modifier = Modifier,
    isFavorite: Boolean,
    // 콜백 만들기 : 콜백으로 돌려줄 값은 Boolean, return은 없음
    onTabFavorite: (Boolean) -> Unit,
)
setContent {

            // Icon 상태를 기억해줄 변수 - 컴포즈에서는 Boolean이 아닌 State 사용
            // value 값을 자동으로 가져오도록 getValue, setValue import
            // 자동 임포트가 안되므로 직접 해야함 -> by 사용 가능
            var isFavorite by rememberSaveable {
                mutableStateOf(false)
            }
            ImageCard(
                modifier = Modifier
                    .fillMaxWidth(0.5f)
                    .padding(16.dp),
                isFavorite = isFavorite,
            ) {favorite ->
                isFavorite = favorite
            }
        }

 

 

 

 

 

 

TextField

setContent {
            // 텍스트 필드에 입력하면 값이 동적으로 변하도록 변수 지정
            val textValue = remember {  //  remember로 값을 저장
                mutableStateOf("")  // 값을 지정
            }

            Column (
                modifier = Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally,
                    ) {
                // 텍스트를 입력받는 TextField, 값을 지정하는 value, 값이 변했을ㄷ 때의 함수 onValueChange
                TextField(
                    value = textValue.value,
                    onValueChange = {
                        textValue.value = it
                    },
                )
                Button(onClick = { }) {
                    Text("클릭!")
                }
            }
        }

TextField의 value 값을 기억하는 변수를 만들어서 mutableState로

그리고 값이 변하면 textValue의 value를 TextField에서 볼 수 있게

MutableState의 Operator를 이용해 구조분해 하자

val (text, setValue) = remember {  //  remember로 값을 저장
                mutableStateOf("")  // 값을 지정
            }

text : String으로 할당

setValue : String을 받아서 Unit으로 리턴하는 형태

TextField(
                    value = text,
                    onValueChange = setValue,
                )

위와 같이 구조분해 할 수 있다.

Scaffold

: 머티리얼 구조의 뼈대가 되는 컴포즈

→ 스낵바, 플로팅액션버튼 등 사용할 때 Scaffold로 감싸서 사용

스낵바 사용을 위해 기억할 변수 지정

// 스낵바 사용을 위해 최근의 상태를 저장
            val scaffoldState = rememberScaffoldState()

            Scaffold(
                scaffoldState = scaffoldState,
            ) {

스낵바는 다음과 같이 띄울 수 있는데

scaffoldState.snackbarHostState.showSnackbar("Hello $text")

showSnackbar 부분에서 빨간줄의 띄워진다!

showSnackbar는 suspend 함수로, 정지함수다.

suspend 함수는 코루틴을 사용해야한다

setContent {
            // 텍스트 필드에 입력하면 값이 동적으로 변하도록 변수 지정
            // text는 String으로 할당, setValue는 String을 받아서 Unit으로 리턴하는 형태
            val (text, setValue) = remember {  //  remember로 값을 저장
                mutableStateOf("")  // 값을 지정
            }

            // 스낵바 사용을 위해 최근의 상태를 저장
            val scaffoldState = rememberScaffoldState()

            // 컴포즈에서 코루틴 스코프 얻기
            val scope = rememberCoroutineScope()

컴포즈에서는 rememberCoroutineScope()를 이용해 코루틴 스코프를 쉽게 지정할 수 있다

Button(onClick = {
                        // 코루틴 스코프 실행, suspend 함수 실행 가능
                        scope.launch{
                            scaffoldState.snackbarHostState.showSnackbar("Hello $text")
                        }
                    })

scope.launch 안에서는 suspend 함수가 작동하는 모습

키보드 내리기

// 키보드 내리기
            val keyboardController = LocalSoftwareKeyboardController.current

키보드컨트롤러 변수를 선언하면 LocalSoftwareKeyboardController.current에서 에러가 발생한다

아직 실험중인 기능이기 때문!

@OptIn(ExperimentalComposeUiApi::class)

를 선언하여 실험중인 기능을 사용한다고 명시.

Button(onClick = {
                        // null값일 수 있으므로 ? 붙여서 안전하게 호출
                        keyboardController?.hide()
                        // 코루틴 스코프 실행, suspend 함수 실행 가능
                        scope.launch {
                            scaffoldState.snackbarHostState.showSnackbar("Hello $text")
                        }
                    })

다음과 같이 키보드컨트롤러를 hide하면 버튼 클릭시 키보드가 내려간다 !