[Coil/번역] 2.3.0

📌 implementation 'io.coil-kt:coil-compose:2.3.0’

인트로

SubcomposeAsyncImage을 다음과 같이 사용했을 때 LazyColumn 동작에서 버벅이는 이슈가 있었다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SubcomposeAsyncImage(
model = imageUrl,
imageLoader = CoilImageLoader.getImageLoader(context = context),
error = {
Image(
modifier = Modifier.size(50.dp),
painter = painterResource(id = R.drawable.none),
contentDescription = ""
)
},
contentDescription = "",
modifier = Modifier
.size(50.dp)
.clip(CircleShape)
.zIndex(1f),
contentScale = ContentScale.Crop
)
1
2
3
4
5
6
7
8
9
10
11
12
13
object CoilImageLoader {
@Composable
fun getImageLoader(context: Context): ImageLoader {
val imageLoader = remember {
ImageLoader.Builder(context)
.memoryCache { MemoryCache.Builder(context).maxSizePercent(0.3).build() }
.crossfade(true)
.allowHardware(false)
.build()
}
return imageLoader
}
}

테스트 해보니, Coil에서는 url에 빈값이 들어올 경우 error로 넘어간다.(Glide는 빈값일 경우 네트워크를 통하지 않고 바로 placeholder를 그대로 보여준다) 그래서 스크롤 할 때마다 로딩이미지도 깜빡거리면서 다시 그려지는 모션을 보이게 되었다.

여기서 문제는 Subcomposition이었다.

1
2
3
4
5
6
7
8
9
10
11
12
AsyncImage(
model = memberImage,
contentDescription = stringResource(id = R.string.description_profile_image),
imageLoader = CoilImageLoader.getImageLoader(context = context),
modifier = Modifier
.size(50.dp)
.clip(CircleShape)
.zIndex(1f),
placeholder = painterResource(id = R.drawable.none_profile_02),
error = painterResource(id = R.drawable.none_profile_02),
contentScale = ContentScale.Crop,
)

평범하게 AsyncImage를 사용하여 어느정도 예상대로의 움직임을 보이도록 성공했다.

📌 이하는 Coil 공식 문서를 번역한 것입니다.

AsyncImage 컴포저블을 사용하는 예시는 다음과 같다.

1
2
3
4
AsyncImage(
model = "https://example.com/iamge.jpg",
contentDescription = null
)

model은 ImageRequest.data 값이나 ImageRequest 그 자체를 넘길 수 있다.

AsyncImage

AsyncImage는 비동기적으로 image를 요청하여 결과값을 렌더링하는 컴포저블이다. 이는 Image 컴포저블의 표준 매개변수를 동일하게 지원하고, 부가적으로 placeholder, error, fallback painters와 onLoading, onSuccess, onError 콜백을 추가 지원한다.

1
2
3
4
5
6
7
8
9
10
AsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data("https://example.com/image.jpg")
.crossfade(true)
.build(),
placeholder = painterResource(R.drawable.placeholder),
contentDescription = stringResource(R.string.description),
contentScale = ContentScale.Crop,
modifier = Modifier.clip(CircleShape)
)

SubcomposeAsyncImage

SubcomposeAsyncImage는 AsyncImage의 변형이다. 이것은 Painter를 사용하는 대신 AsyncImagePainter의 상태를 API 슬롯(아래 예제에서 loading 와 같은 것)에 제공하는 subcomposition을 사용하는 데에 쓰인다. 그 예제는 다음과 같다.

1
2
3
4
5
6
7
SubcomposeAsyncImage(
model = "https://example.com/image.jpg",
loading = {
CircularProgressIndicator() // subcomposition?
},
contentDescription = straintResource(R.string.description)
)

거기에 더해, 현재 상태에 따라 렌더링하는 항목이 달라지도록 복잡한 로직을 만들 수도 있다.

1
2
3
4
5
6
7
8
9
10
11
12
SubcomposeAsyncImage(
model = "https://example.com/image.jpg",
contentDescription = stringResource(R.string.description)
) {
val state = painter.state
if (state is AsyncImagePainter.State.Loading
|| state is AsyncImagePainter.State.Error) {
CirclularProgressIndicator()
} else {
SubcomposeAsyncImageContent()
}
}

Subcomposition은 정규 composition보다 효율이 낮다. 그래서 이 컴포저블은 높은 퍼포먼스(부드러운 스크롤 동작 등)를 요구하는 UI에서는 맞지 않을 수 있다.

If you set a custom size for the ImageRequest using ImageRequest.Builder.size (e.g. size(Size.ORIGINAL)), SubcomposeAsyncImage will not use subcomposition since it doesn’t need to resolve the composable’s constraints.

ImageRequest.Builder.size 를 사용하여 ImageRequest의 맞춤 크기를 설정하면 SubcomposeAsyncImage는 컴포저블의 제약 조건을 해결할 필요가 없으므로 하위 컴포지션을 사용하지 않습니다.

AsyncImagePainter

내부적으로 AsyncImage와 SubcomposeAsyncImage는 model을 로드할 때 AsyncImagePainter를 사용한다. 만약 Painter가 필요한데 AsyncImage를 사용할 수 없다면 rememberAsyncImagePainter를 사용할 수 있을 것이다.

1
val painter = rememberAsyncImagePainter("https://example.com/image.jpg")

단, rememberAsyncImagePainter는 모든 경우에서 예상대로 동작하지 않을 수 있는 하위 수준 API다.

만약 AsyncImagePainter를 렌더링하는 Image 컴포저블에 커스텀 ContentScale를 적용한다면 rememberAsyncImagePainter도 함께 세팅해야 한다. 이건 로드할 이미지의 크기를 결정하는 데에 필수적이기 때문이다.

Author

LEEJS

Posted on

2024-06-26

Updated on

2026-01-10

Licensed under

댓글