이번에는 이미지를 누르면 좋아요 표시가되는걸 만들어볼꺼다
앞서 말했던 @Composable를 이용해서 만들자
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ImageCard()
}
}
}
@Composable
fun ImageCard() {
...
일단 setContent에 이미지 카드를 구현할 컴포즈인 ImageCard()를 넣어준다.
Card(
modifier = Modifier
.fillMaxWidth(0.5f)
.padding(16.dp),
shape = RoundedCornerShape(8.dp),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 5.dp),
)
Card는 xml에서 카드뷰 같은 것 이라고 보면된다,
radius를 정해주고 elevation을 넣어줬다.
Image(
modifier = Modifier.fillMaxSize(),
painter = painterResource(id = R.drawable.img_winter),
contentDescription = "설명을 작성 하는곳",
contentScale = ContentScale.Crop
)
Image는 이미지 뷰 같은거라고 보면 된다.
이미지는 PainterResource를 이용해 res파일에 있는 이미지를 불러와주고
contentScale = ContentScale.Crop로 CenterCrop같은 효과를 내준다.
이제 좋아요 버튼을 누를 하트 표시를 만들어보자
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.TopEnd,
) {
IconButton(onClick = { }) {
Icon(imageVector = Icons.Default.FavoriteBorder,
contentDescription = "무조건 작성해야함",
tint = Color.White
)
}
}
IconButton(onClick = { })을 만들었는데 저기 onClick에 변경될 로직을 넣어주면 좋아요 버튼을 구현 할 수 있다.
그리고 밑에 Image도 가능한데 Icon() 에서 imageVector를 사용해 벡터 이미지를 따로 drawable 만들지 않고 구성 할 수 있다.
fun ImageCard() {
Card(
modifier = Modifier
.fillMaxWidth(0.5f)
.padding(16.dp),
shape = RoundedCornerShape(8.dp),
elevation = CardDefaults.elevatedCardElevation(defaultElevation = 5.dp),
) {
Box(
modifier = Modifier.height(200.dp)
) {
Image(
modifier = Modifier.fillMaxSize(),
painter = painterResource(id = R.drawable.img_winter),
contentDescription = "설명을 작성 하는곳",
contentScale = ContentScale.Crop
)
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.TopEnd,
) {
IconButton(onClick = { }) {
Icon(imageVector = Icons.Default.FavoriteBorder,
contentDescription = "무조건 작성해야함",
tint = Color.White
)
}
}
}
}
}
이제 클릭했을때 좋아요 표시가 되게 구현해보자
jetpack compose에선 리멤버가 중요하다고 한다.
좋아요를 눌렀는지 안눌렀는지 상태를 기억해야하는데
이때 remember를 쓴다
val isFavorite = remember {
mutableStateOf(false)
}
이제 false라는 상태를 기억하는데, 이제 이 값이 바뀔때마다 UI를 갱신시켜줄거다
IconButton(onClick = {
isFavorite.value = !isFavorite.value
}) {
Icon(imageVector = if (isFavorite.value) {
Icons.Default.Favorite
} else {
Icons.Default.FavoriteBorder
} ,
contentDescription = "무조건 작성해야함",
tint = Color.White
)
}
아까 작성한 좋아요 버튼에 조건문을 걸어주자
IconButton(onClick = {
isFavorite.value = !isFavorite.value
})
그리고 onClick에 상태를 변경시켜주는 로직을 넣어주기만 하면된다
그런데 이렇게 하면 계속 value를 써줘야하는 불편함이 있는데
by를 써서 이를 해결 가능하다
var isFavorite by remember {
mutableStateOf(false)
}
...
IconButton(onClick = {
isFavorite = isFavorite.not()
}) {
Icon(imageVector = if (isFavorite) {
...
getValue와 setValue를 임포트하면 이런식으로 표현이 가능하다
그런데 이런식으로 구현할 경우 화면을 회전 시키는등 뷰가 파괴될때 데이터 저장이 안된다
컴포즈에선 rememberSaveable를 사용하면 손쉽게 해결이 가능하다
var isFavorite by rememberSaveable {
mutableStateOf(false)
}
이미지 카드를 이렇게 빼서 컴포즈로 만들어준 이유는
컴포즈를 재사용기 위함인데 그러면
remember 가 컴포즈 안에 있으면 안된다
아키텍처 및 컴포넌트 설계의 원칙때문인데
컴포저블을 보다 범용적으로 만들어서 어떤 상태 관리 방식에도 유연하게 대응할 수 있도록 재사용성을 높이고
소유권을 상위 컴포저블이 가지게되서 흐름 제어나 상태를 다른 컴포저블에도 전달하는등 관리하기 용이해진다
그렇기 때문에 이걸 밖으로 빼내줘야하는데
setContent {
var isFavorite by rememberSaveable {
mutableStateOf(false)
}
ImageCard(
isFavorite = isFavorite
)
setContent에서 이런식으로 선언해준다
onClick같은 경우 리사이클러뷰처럼 파라미터를 콜백으로 받게 만들자
fun ImageCard(
isFavorite: Boolean,
onTabFavorite: (Boolean) -> Unit,
) {
...
IconButton(onClick = {
onTabFavorite.invoke(isFavorite.not())
})
...
setContent {
var isFavorite by rememberSaveable {
mutableStateOf(false)
}
ImageCard(
isFavorite = isFavorite
) { favorite ->
isFavorite = favorite
}
}
onTabFavorite은 인보크라 생략해서 onTabFavorite(isFavorite.not())로 써주면 된다.
Modifier같은 경우도 외부에서 값을 넣어주는게 좋다고 한다. 이것도 빼주자
@Composable
fun ImageCard(
modifier: Modifier = Modifier,
isFavorite: Boolean,
onTabFavorite: (Boolean) -> Unit,
) {
Card(
modifier = modifier,
기본 값을 설정해주고
setContent {
...
ImageCard(
modifier = Modifier
.fillMaxWidth(0.5f)
.padding(16.dp),
이런식으로 외부에서 값을 넣어주면 된다