카테고리 없음

ComposeView 이미지 다운로드 기능을 구현해보자!

MJ핫산 2023. 10. 12. 18:20

💻 구현해야 하는 기능

매쉬업 앱의 마이페이지 구성요소 중에 “기수 카드”가 있는데, “다운로드” 버튼을 누르면 이 기수 카드를 갤러리에 저장해줘야 한다.

 

이미지 다운로드 기능은 심리테스트 결과지 저장이나 이체 확인증 등 꽤 자주 사용되는 것 같다. 그런데 이번에는 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() 
		..
	}
)

👻 결과

다운로드 버튼을 누르면 다운로드 폴더에 활동 카드 이미지가 저장된다!