Compose 이미지 확대, 축소, 크롭 구현하기

손현수·2024년 10월 1일

구현해야 하는 기능

  • 유저가 이미지를 선택하면 해당 이미지에서 어느 부분이 잘릴 예정인지 미리보기 제공
  • 미리보기에서 유저는 확대, 축소, 이동을 통해 잘리는 영역을 조절할 수 있음

코드

@Composable
fun ImagePreviewContent(
    selectedUri: Uri? = null,
    onClickBtnConfirm: (Float, Float, Float) -> Unit = { _, _, _ -> }
) {
    var scale by remember { mutableFloatStateOf(1f) }
    var offsetX by remember { mutableFloatStateOf(0f) }
    var offsetY by remember { mutableFloatStateOf(0f) }
    val state = rememberTransformableState { zoomChange, offsetChange, _ ->
        scale *= zoomChange
        offsetX += offsetChange.x
        offsetY += offsetChange.y
    }

    Box(
        modifier = Modifier
            .fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Image(
            painter = rememberAsyncImagePainter(model = selectedUri),
            contentDescription = null,
            modifier = Modifier
                .fillMaxWidth()
                .graphicsLayer(
                    scaleX = maxOf(1f, scale),
                    scaleY = maxOf(1f, scale),
                    translationX = offsetX,
                    translationY = offsetY
                )
                .transformable(state = state),
            contentScale = ContentScale.Crop
        )

        CropOverlay()

        FilledBtn(
            modifier = Modifier
                .fillMaxWidth()
                .padding(horizontal = 16.dp)
                .padding(bottom = 80.dp)
                .align(Alignment.BottomCenter),
            text = WORD_CONFIRM,
            onClickBtn = { onClickBtnConfirm(scale, offsetX, offsetY) }
        )
    }
}

@Composable
fun CropOverlay(
    backgroundColor: Color = GsBlack.copy(alpha = 0.7f)
) {
    Column(
        modifier = Modifier
            .fillMaxSize()
    ) {
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f)
                .background(backgroundColor)
        )

        Box(
            modifier = Modifier
                .fillMaxWidth()
                .aspectRatio(375f / 235f)
                .background(Color.Transparent)
                .clip(RectangleShape)
        )

        Box(
            modifier = Modifier
                .fillMaxWidth()
                .weight(1f)
                .background(backgroundColor)
        )
    }
}
  • CropOverlay 화면에서는 실제 이미지가 잘려서 활용되는 부분 외에는 흐리게 처리하여 어느 부분이 활용될지 유저가 인지할 수 있어야 한다.
  • 디자이너의 요구사항에 맞게 이미지의 비율은 가로 375, 세로 235 비율로 잘라야 한다. 이는 aspectRatio를 통해 설정하는 것이 가능하다.

결과물

좀 더 고민해야 할 부분

위의 코드에는 생략되어 있지만 ImagePreviewContent가 이 화면의 최상위 컴포저블이 아니다. 더 상위의 컴포저블이 존재하기 때문에 scale, offsetX, offsetY 등의 상태 변수들을 상태 호이스팅을 적용하여 상위 컴포저블로 옮길지, 현재 컴포저블에 남길지는 고민해볼 여지가 있다.

profile
안녕하세요.

0개의 댓글