💻 구현해야 하는 기능
매쉬업 앱의 마이페이지 구성요소 중에 “기수 카드”가 있는데, “다운로드” 버튼을 누르면 이 기수 카드를 갤러리에 저장해줘야 한다.
이미지 다운로드 기능은 심리테스트 결과지 저장이나 이체 확인증 등 꽤 자주 사용되는 것 같다. 그런데 이번에는 xml view가 아니라 compose view를 이미지로 저장해야해서 그 방법을 알아보려고 한다!
🌃 View를 이미지로 저장하는 기본 프로세스
기본적으로 View를 이미지로 저장하기 위해서는 다음과 같은 과정으로 진행된다. (* 틀릴 수도 있음)
View → Bitmap → Base64(String) → File → Storage에 저장
View를 Bitmap으로 변환하는 과정에서 기존의 xml view는 View.*drawToBitmap*()라는 함수가 있다.
그래서 편하게 바꿀 수 있었는데.. 그럼 compose view 는??
🤔 Compose View를 Bitmap으로 바꾸려면…
구글에 검색을 해보니 꽤 많은 결과가 나왔다. 하지만 수동으로 높이, 너비를 구하거나 캔버스 해킹 등 너무 복잡한 방법들이었다.
그래서 나는 그냥 Composable Component를 View로 만들 수 있는 가장 간단한 방법을 사용하기로 했다.
ComposeView(context).apply {
setContent {
// Composable
}
}
이 코드를 응용해서 Composable을 넘기면 View를 만들어주는(wrap하는) 함수를 만들면 이렇게 된다.
@Composable
fun CaptureBitmap(
content: @Composable () -> Unit // Composable 전달
){
val context = LocalContext.current
val composeView = remember { ComposeView(context) }
AndroidView(
factory = {
composeView.apply {
setContent {
content.invoke()
}
}
}
)
}
하지만 문제가 하나 더 있다. ㅠㅠ 코드가 실행되고 뷰를 확장하는데까지 대기 시간이 있어서 Bitmap 변환을 즉시 수행할 수 없다는 것이다. 이 문제를 해결하기 위해서 viewTree를 관찰하거나 뷰가 다 그려졌을 때 콜백을 받는 등 여러 방법이 있지만 컴포즈의 특징 중 리컴포지션 때문에 UI가 변경될 때마다 비트맵을 다시 캡처해야 한다.
그래서 콜백을 이용해서 호출했을 때 가장 최신 상태의 View를 Bitmap으로 만들어서 리턴하고자 했다.
@Composable
fun CaptureBitmap(
content: @Composable ()->Unit
) : () -> Bitmap {
val context = LocalContext.current
val composeView = remember { ComposeView(context) }
//callback (최신 상태의 View로 Bitmap을 만든다)
fun captureBitmap(): Bitmap = composeView.drawToBitmap()
AndroidView(
factory = {
composeView.apply {
setContent {
content.invoke()
}
}
}
)
return ::captureBitmap
}
💡 사용 방법
이런 식으로 다운로드 버튼을 눌렀을 때 snapShot.invoke()해서 Bitmap을 가져올 수 있다!!
val snapShot = CaptureBitmap {
// 이미지로 저장할 Composable 영역
}
Button(
text = "다운로드",
onClick = {
val bitmap = snapShot.invoke()
..
}
)
👻 결과
다운로드 버튼을 누르면 다운로드 폴더에 활동 카드 이미지가 저장된다!