✅ 코드 및 설명에 들어가기에 앞서 현재 내 코드의 상황을 설명하자면 다음과 같다!
- 업로드된 이미지를 포함한 Box 컴포저블이 있음 ( Box(...) { Image(...) {} }
- 내가 업로드한 이미지를 포함해서 Box 컴포저블 화면을 캡쳐한듯 그려서 공유하고 싶음
생각보다 이를 Compose로 구현한 래퍼런스가 많지 않고, 이미지를 포함한 경우도 드물어서 글을 작성하게 되었다!
문제상황이 하나 있어서 [이미지 업로드] - [화면 그리기] - [공유] 순서가 아닌,
[화면 그리기] - [이미지 업로드] - [공유] 순서로 글을 작성하겠다.
💡.drawWithCache
Compose의 Modifier 라이브러리중 .drawWichCache를 이용해서 화면을 그리는 작업을 수행할 것이며, 사용방법은 아래와 같다.
class MyViewModel : ViewModel() {
private val _picture = Picture(null)
val picture: Picture = _picture
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(360.dp)
.drawWithCache {
val width = this.size.width.toInt()
val height = this.size.height.toInt()
onDrawWithContent {
val pictureCanvas = androidx.compose.ui.graphics.Canvas(
picture.beginRecording(width, height)
)
draw(this, this.layoutDirection, pictureCanvas, this.size) {
this@onDrawWithContent.drawContent()
}
picture.endRecording()
drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) }
}
}
) {
// 포함하고싶은 UI가 담겨있는 부분
...
Image() // 내가 업로드할 이미지
}
내가 이해한 바로는 .drawWichCache는 다음과 같이 진행된다.
- 그리고자 하는 화면의 width, height 값 정의
- 내가 정의한 width, height 만큼의 View 화면을 내가 미리 정의한 Picture타입의 객체에 그림
- Canvas에 pictrue를 담아서 화면에 보여줌
⚠️ 여기서 첫번째 문제가 발생
초기 컴포지션에서 이미지를 업로드하지 않은 빈화면이 그려진 Canvas가 이미지를 업로드한 이후에도 변동사항 없이 비어있다는 것이다.
분명 Box컴포저블 내부에서 변동사항이 생긴 것이니까, 이를 remember가 감지해서 화면이 재구성되었어야했는데 되지 않는것...
우선 해결은 했다!
Box 컴포저블 내부의 Image를 Painter로 그려왔는데, Bitmap 형식으로 바꾸니 이미지를 업로드 할 때마다 .drawWichCache가 인식해서 원하는 대로 화면이 바뀌었다. (아래 이미지 업로드 코드 확인!)
아무래도 Paitner보다 Bitmap이 화면에 더 빨리 나오기때문에 .drawWithCache와 호환이 더욱 잘되었던 건지도 모르겠다.
=> 이제 이미지를 포함한 Box 컴포저블이 picture라는 객체에 이미지가 바뀔때마다 저장되고 있다!
다음은 기기에서 이미지를 업로드한 과정을 보겠다.
💡업로드 한 이미지를 Bitmap으로 표현
나의 기기에 있는 이미지를 업로드하고 (Uri형식) 이를 비트맵으로 표현하는 과정은 다음과 같다.
val launcher = rememberLauncherForActivityResult(
contract =
ActivityResultContracts.GetContent()
) { uri: Uri? ->
if (uri != null) {
uploadImage(uri)
}
}
IconButton(onClick = {
launcher.launch("image/*")
})
launcher를 불러서 기기에 저장된 사진을 가져올 수 있도록 구성해야하며, 누르는 버튼에 함수를 걸어준다.
@Composable
fun ImageFromUri(uri: Uri) {
val context = LocalContext.current
val imageBitmap: ImageBitmap? = remember(uri) {
val inputStream = context.contentResolver.openInputStream(uri)
inputStream?.use { stream ->
val bitmap = BitmapFactory.decodeStream(stream)
bitmap.asImageBitmap()
}
}
imageBitmap?.let { bitmap ->
Image(
bitmap = bitmap,
contentDescription = "Image from URI",
modifier = Modifier.fillMaxSize()
)
}
}
이 부분은 Uri 타입으로 가져온 이미지를 Bitmap으로 바꿔주는 과정이다.
위에서 제시했던 문제상황처럼 처음에는 이 이미지를 Painter형식으로 해줬었다. 바로 반영이 되지 않는 걸 확인 후 Bitmap으로 바꿔주었다.
이 컴포저블을 작성한 후 Box안에 바로 사용해주었다.
💡이미지 공유하기 (안드로이드 기기)
이제 picture에 저장된 그림을 공유하는 기능을 완성해보겠다!
private fun createBitmapFromPicture(picture: Picture): Bitmap {
val bitmap = Bitmap.createBitmap(
picture.width,
picture.height,
Bitmap.Config.ARGB_8888
)
val canvas = android.graphics.Canvas(bitmap)
canvas.drawColor(android.graphics.Color.WHITE)
canvas.drawPicture(picture)
return bitmap
}
picture를 인자로 받은 createBitmapFromPicture함수에서는 받은 picture를 Bitmap 타입으로 변환하는 작업을 수행하며 결과로 bitmap을 반환하고 있다.
fun shareImage(context: Context, bitmap: Bitmap) {
val file = File(context.cacheDir, "shared_image.png")
file.createNewFile()
val outputStream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream)
outputStream.close()
// 이미지 파일의 Uri를 얻음
val uri = FileProvider.getUriForFile(context, context.packageName + ".provider", file)
// Intent를 생성하여 이미지를 공유
val intent = Intent(Intent.ACTION_SEND)
intent.type = "image/*"
intent.putExtra(Intent.EXTRA_STREAM, uri)
val chooser = Intent.createChooser(intent, "Share with")
context.startActivity(chooser)
}
앱 데이터에 이미지의 정보를 저장할 파일과 provider를 정의해주고 createBitmapFromPicture에서 return된 bitmap을 파일로 저장해주었다.
이후 uri를 추출하여 Intent를 통해 외부 어플과 이미지를 공유할 수 있는 기능을 작성해주었다.
provider와 관련된 부분은 여기 첨부해두겠다!
https://cherry-log.tistory.com/5
안드로이드 이미지 저장할 때 Provider 설정
https://cherry-log.tistory.com/4 이미지를 포함한 Composable 화면 비트맵으로 저장 후 공유하기 ✅ 코드 및 설명에 들어가기에 앞서 현재 내 코드의 상황을 설명하자면 다음과 같다! 업로드된 이미지를 포
cherry-log.tistory.com
onClick =
{ shareImage(context, createBitmapFromPicture(picture)) },
마지막으로 공유하기 버튼에 클릭이벤트로 위와같이 넣어주었다!
휴.. 이렇게 길고 긴 과정을 통해 내가 업로드 한 이미지를 포함한 컴포저블 화면을 기기에 저장하고 공유할 수 있게 되었다!
그동안 Compose 작업하면서 제일 어려웠던 것 같다..ㅎ
혹시 잘못된 정보가 있다면 댓글로 알려주길!!
🔥참고 자료
https://medium.com/@chiragthummar16/converting-composables-to-bitmap-using-canvas-525573abf70f
Converting Composables To Bitmap Using Canvas
When developing photo editing apps or quote maker-like apps you need to export the image with text, colours, shapes, etc. to the final…
medium.com
'Android > Compose' 카테고리의 다른 글
NavController를 이용한 데이터 전달 (0) | 2023.10.26 |
---|---|
State Hoisting & ViewModel (0) | 2023.10.24 |
State Remember & rememberSaveable (3) | 2023.10.24 |