레시피
이 페이지는 Coil에서 자주 사용되는 몇 가지 일반적인 사용 사례를 처리하는 방법에 대한 지침을 제공합니다. 이 코드를 정확한 요구 사항에 맞게 수정해야 할 수도 있지만, 올바른 방향으로 나아가는 데 도움이 되기를 바랍니다!
다루지 않은 일반적인 사용 사례가 있나요? 새로운 섹션과 함께 PR을 자유롭게 제출해주세요.
Palette
Palette를 사용하면 이미지에서 주요 색상을 추출할 수 있습니다. Palette를 생성하려면 이미지의 Bitmap에 접근해야 합니다. 이는 여러 가지 방법으로 수행할 수 있습니다:
ImageRequest.Listener를 설정하고 ImageRequest를 큐에 넣어 이미지의 비트맵에 접근할 수 있습니다:
imageView.load("https://example.com/image.jpg") {
// Palette가 이미지의 픽셀을 읽어야 하므로 하드웨어 비트맵을 비활성화합니다.
allowHardware(false)
listener(
onSuccess = { _, result ->
// 백그라운드 스레드에서 팔레트를 생성합니다.
Palette.Builder(result.image.toBitmap()).generate { palette ->
// 팔레트를 사용합니다.
}
}
)
}메모리 캐시 키를 플레이스홀더로 사용하기
이전 요청의 MemoryCache.Key를 후속 요청의 플레이스홀더로 사용하는 것은 두 이미지가 동일하지만 다른 크기로 로드될 때 유용할 수 있습니다. 예를 들어, 첫 번째 요청이 이미지를 100x100으로 로드하고 두 번째 요청이 이미지를 500x500으로 로드하는 경우, 첫 번째 이미지를 두 번째 요청의 동기식 플레이스홀더로 사용할 수 있습니다.
샘플 앱에서 이 효과는 다음과 같습니다:
목록의 이미지는 의도적으로 매우 낮은 디테일로 로드되었으며, 시각적 효과를 강조하기 위해 크로스페이드가 느리게 설정되었습니다.
이 효과를 얻으려면 첫 번째 요청의 MemoryCache.Key를 두 번째 요청의 ImageRequest.placeholderMemoryCacheKey로 사용합니다. 다음은 예시입니다:
// 첫 번째 요청
listImageView.load("https://example.com/image.jpg")
// 두 번째 요청 (첫 번째 요청이 완료되면)
detailImageView.load("https://example.com/image.jpg") {
placeholderMemoryCacheKey(listImageView.result.memoryCacheKey)
}공유 요소 전환
공유 요소 전환을 사용하면 Activities와 Fragments 간에 애니메이션을 적용할 수 있습니다. 다음은 Coil과 함께 작동하도록 하는 방법에 대한 몇 가지 권장 사항입니다:
공유 요소 전환은 하드웨어 비트맵과 호환되지 않습니다. 애니메이션을 시작하는
ImageView와 애니메이션을 적용할 뷰 모두에 대해allowHardware(false)를 설정하여 하드웨어 비트맵을 비활성화해야 합니다. 그렇지 않으면 전환 시java.lang.IllegalArgumentException: Software rendering doesn't support hardware bitmaps예외가 발생합니다.시작 이미지의
MemoryCache.Key를 종료 이미지의placeholderMemoryCacheKey로 사용하세요. 이렇게 하면 시작 이미지가 종료 이미지의 플레이스홀더로 사용되어, 이미지가 메모리 캐시에 있는 경우 흰색 플래시 없이 부드러운 전환이 가능합니다.최적의 결과를 위해
ChangeImageTransform과ChangeBounds를 함께 사용하세요.
Compose를 사용하시나요? AsyncImage로 공유 요소 전환을 수행하는 방법에 대한 이 글을 확인해보세요.
원격 뷰
Coil은 기본적으로 RemoteViews용 Target을 제공하지 않지만, 다음과 같이 직접 생성할 수 있습니다:
class RemoteViewsTarget(
private val context: Context,
private val componentName: ComponentName,
private val remoteViews: RemoteViews,
@IdRes private val imageViewResId: Int
) : Target {
override fun onStart(placeholder: Image?) = setImage(placeholder)
override fun onError(error: Image?) = setImage(error)
override fun onSuccess(result: Image) = setImage(result)
private fun setImage(image: Image?) {
remoteViews.setImageViewBitmap(imageViewResId, image?.toBitmap())
AppWidgetManager.getInstance(context).updateAppWidget(componentName, remoteViews)
}
}그 다음 요청을 평소처럼 enqueue/execute 합니다:
val request = ImageRequest.Builder(context)
.data("https://example.com/image.jpg")
.target(RemoteViewsTarget(context, componentName, remoteViews, imageViewResId))
.build()
imageLoader.enqueue(request)Painter 변환하기
AsyncImage와 AsyncImagePainter는 모두 Painter를 허용하는 placeholder/error/fallback 인수를 가집니다. Painter는 컴포저블을 사용하는 것보다 유연성이 떨어지지만, Coil이 서브컴포지션(subcomposition)을 사용할 필요가 없으므로 더 빠릅니다. 하지만 원하는 UI를 얻기 위해 Painter를 삽입(inset), 늘이기(stretch), 색조 변경(tint) 또는 변환(transform)해야 할 수도 있습니다. 이를 수행하려면 이 Gist를 프로젝트에 복사하고 다음과 같이 Painter를 감싸세요:
AsyncImage(
model = "https://example.com/image.jpg",
contentDescription = null,
placeholder = forwardingPainter(
painter = painterResource(R.drawable.placeholder),
colorFilter = ColorFilter(Color.Red),
alpha = 0.5f,
),
)다음과 같이 후행 람다(trailing lambda)를 사용하여 onDraw를 재정의할 수 있습니다:
AsyncImage(
model = "https://example.com/image.jpg",
contentDescription = null,
placeholder = forwardingPainter(painterResource(R.drawable.placeholder)) { info ->
inset(50f, 50f) {
with(info.painter) {
draw(size, info.alpha, info.colorFilter)
}
}
},
)요청 변환하기
이미지를 가져오는 데 사용되는 HTTP 요청을 변환해야 할 수도 있습니다. 이 예시에서는 Interceptor를 사용하여 요청 URL에 width 및 height 쿼리 파라미터를 추가하겠습니다.
class UrlSizeInterceptor : Interceptor {
override suspend fun intercept(chain: Chain): ImageResult {
val request = chain.request
val uri = request.uri
if (uri == null || uri.scheme !in setOf("https", "http")) {
// HTTP가 아닌 요청은 무시합니다.
return chain.proceed()
}
val (width, height) = chain.size
return if (width is Pixels && height is Pixels) {
val transformUri = uri.newBuilder()
.query("width=${width.px}&height=${height.px}")
.build()
val transformedRequest = request.newBuilder()
.data(transformUri)
.build()
return chain.withRequest(transformedRequest).proceed()
} else {
// 너비와 높이를 사용할 수 없는 경우(예: 무한 제약 조건으로 인해).
chain.proceed()
}
}
private val ImageRequest.uri: Uri?
get() = when (val data = data) {
is Uri -> data
is coil3.Uri -> data.toAndroidUri()
is String -> data.toUri()
else -> null
}
}ImageLoader에 인터셉터를 등록하는 것을 잊지 마세요!
ImageLoader.Builder(context)
.components {
add(UrlSizeInterceptor())
}
.build()