비율 계산 코드 : 이동

문제 상황

문제상황

이미지를 클릭하면 다이얼로그로 크롭되지 않은 원본 비율의 사진을 보여줘야 한다.

하지만 이미지가 불러와지기 전에 화면을 가득 채우는 현상이 있었다. (그 와중에 태극기는 바로 불러와지는 ㅋㅋ 🇰🇷 펄럭)

문제 상황

Dialog(onDismissRequest = { }) {  
    Surface(  
        modifier = Modifier.fillMaxWidth().wrapContentHeight(),  // <--
        shape = RoundedCornerShape(8.dp),  
        color = MaterialTheme.colorScheme.background,  
    ) {  
        PhotoDetail(  
            photo = photo,
            // ... 
        )  
    }  
}

다이얼로그를 띄우는 부분이다. Surface 로 width를 가득 채우고, height는 wrapContentHeight 로 설정하였다.

Column(  
    modifier = modifier  
        .wrapContentHeight()  
        .padding(bottom = 30.dp),  
    horizontalAlignment = Alignment.CenterHorizontally,  
) {  
    Box(  
        modifier = Modifier  
            .fillMaxWidth()  
            .wrapContentHeight(),  
    ) {
	    // 사진이 들어가는 부분
	    AsyncImage(  
		    model = photo.loadUrlMedium,  
		    contentDescription = "Photo detail : ${photo.title}",  
		    contentScale = ContentScale.FillWidth,  
		    modifier = Modifier.fillMaxWidth(),  
		)
	}

	// 텍스트와 버튼 그리고 공백
	

Column 을 wrapContentHeight 로 만들었다.

사진이 로드되기 전 wrapContentHeight 로 높이를 계산하지 못해 생기는 문제였다

해결 방법

  • 사진 목록을 표시할 때 이미 한 번 사진을 로드한다. 이때 비율을 계산할 수 있다
  • 비율을 계산한다
  • 클릭 이벤트에서 계산된 비율이 반영된 모델을 넘겨준다
  • 넘겨 받은 비율로 다이얼로그의 사진 부분의 비율을 미리 설정한다

그 결과 불필요한 계산 작업(wrapContentHeight)을 하지 않고도 깔끔하게 다이얼로그를 띄울 수 있다

Photo 모델에 ratio 프로퍼티 추가

data class PhotoUIModel(  
    val id: String = "",  
    val owner: String = "",  
    val secret: String = "",  
    val server: String = "",  
    val title: String = "",  
) {  
    val loadUrlSmall: String // 검색화면 표시용  
        get() = "https://live.staticflickr.com/$server/${id}_${secret}_n.jpg"  
    val loadUrlMedium: String // 다운로드 확인용  
        get() = "https://live.staticflickr.com/$server/${id}_$secret.jpg"  
    val loadUrlOriginal: String // 다운로드용  
        get() = "https://live.staticflickr.com/$server/${id}_${secret}_o.jpg"  
    val photoId: String  
        get() = "$owner/$id"  
  
    var ratio: Float = 1f  // <-- 새로 추가함
}

ratio 를 프로퍼티로 추가했고, 1f로 비율을 초기화하였다.

Coil 로 이미지 로드

Coil에서 이미지 UI를 표시하기 위해 보통 AsyncImage 컴포저블을 사용한다. 이렇게 하면 자체적으로 이미지를 표시할 수 있기 때문에 편리하지만 이미지의 속성이나 로딩 상태를 컨트롤 할 수 없다.

그래서 내부적으로 사용하는 rememberAsyncImagePainter 를 사용하게 되었다

단, rememberAsyncImagePainter 는 low-level API이기 때문에 변경될 가능성이 있다

비율 계산

val painter = rememberAsyncImagePainter(  
    model = photo.loadUrlSmall,  
    placeholder = painterResource(id = R.drawable.charlezzicon),  
)  
val imageRatio = remember(painter.state) {  
    val imageSize = painter.intrinsicSize  
    imageSize.width / imageSize.height  
}

rememberAsyncImagePainter 의 반환 값은 Coil의 AsyncImagePainter 타입이며, 이는 컴포즈의 Painter 클래스의 하위 타입이다.

AsyncImagePainter 는 이미지 로딩 State 를 가지고 있으며, State 는 다음의 속성들을 가지고 있다

imageRatio 에서는 remember 의 key 로 patiner.state 를 사용했다. 즉, 이미지 로딩이 완료되면 state가 Success 가 되고, 이때 imageRatio 를 계산한 값으로 업데이트 되기 때문에 최신의 비율을 받아볼 수 있게 된다.

Coil AsyncImagePainter 의 State

sealed class State {  
  
    /** The current painter being drawn by [AsyncImagePainter]. */  
    abstract val painter: Painter?  
  
    /** The request has not been started. */  
    object Empty : State() {  
        override val painter: Painter? get() = null  
    }  
  
    /** The request is in-progress. */  
    data class Loading(  
        override val painter: Painter?,  
    ) : State()  
  
    /** The request was successful. */  
    data class Success(  
        override val painter: Painter,  
        val result: SuccessResult,  
    ) : State()  
  
    /** The request failed due to [ErrorResult.throwable]. */  
    data class Error(  
        override val painter: Painter?,  
        val result: ErrorResult,  
    ) : State()  
}

이미지 표시하기 및 비율 반영

Box(  
    modifier = Modifier  
        .width(minSize)  
        .aspectRatio(1f)  
        .fillMaxSize(),  
) {  
    Image(  
        painter = painter,  
        contentDescription = null,  
        contentScale = ContentScale.Crop,  
        modifier = Modifier  
            .fillMaxSize()  
            .clickable { onClick(photo.apply { ratio = imageRatio }) },  
    )  
}

AsyncImagePainter 는 앞서 말했듯이 컴포즈의 Painter 타입이기 때문에 Image 컴포저블의 painter 파라미터로 사용할 수 있다.

클릭이 일어날 때 해당 모델의 ratio 속성으로 앞서 계산한 imageRatio 값을 적용하고 넘겨주게 된다.

다이얼로그 비율 설정하기

Column(  
    modifier = modifier  
        .padding(bottom = 30.dp)  
        .wrapContentHeight(),  
    horizontalAlignment = Alignment.CenterHorizontally,  
) {  
    Box(  
        modifier = Modifier.aspectRatio(photo.ratio),  
    ) { 
		// 사진이 들어가는 부분
        AsyncImage(  
            model = photo.loadUrlMedium,  
            contentDescription = "Photo detail : ${photo.title}",  
            contentScale = ContentScale.FillWidth,  
            modifier = Modifier.fillMaxWidth(),  
        )
	}

	// 텍스트와 버튼 그리고 공백
}

사진을 감싸는 BoxModifieraspectRatiophoto.ratio 로 지정한다.

결과

그러면 위와 같이 아직 사진이 불러와지지 않은 상황에서도 불러와질 사진의 비율대로 미리 다이얼로그의 크기가 설정되어 표시된다.