Coil로 이미지 로드
Compose에서 이미지를 비동기적으로 로드합니다.
Coil로 이미지 로드은(는) CoddyKit의 무료 Android Academy 강의입니다. 이것은 4개 중 1번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Android Academy 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Android Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
이미지 로더가 필요한 이유
대부분의 최신 앱은 인터넷에서 이미지를 표시합니다. 예를 들어 프로필 사진, 상품 사진, 기사 썸네일 등이 있습니다. 이러한 이미지를 올바르게 다운로드하고 디코딩하는 작업은 보기보다 어렵습니다.
백그라운드 스레드에서 네트워크를 통해 바이트를 가져오고, 이를 비트맵으로 디코딩하고, 다시 가져오지 않도록 캐시하며, 사용자가 스크롤하여 지나가면 요청을 취소해야 합니다. 이 작업을 직접 처리하면 오류가 발생하기 쉽습니다.
Coil(Coroutine Image Loader)은 작고 Compose에 친화적인 API로 이 모든 작업을 대신 처리합니다. 이 레슨에서는 첫 번째 원격 이미지를 로드해 봅니다.
프로젝트에 Coil 추가하기
Coil은 Gradle 의존성으로 제공됩니다. Jetpack Compose에서는 coil-compose 아티팩트를 사용해야 하며, 이 아티팩트가 AsyncImage 컴포저블을 제공합니다.
모듈의 build.gradle.kts에 추가합니다. Coil 3은 현재 주요 버전이며 여러 플랫폼에서 작동합니다. 여기서는 Compose 통합 기능을 사용합니다.
// build.gradle.kts (module level)
dependencies {
implementation("io.coil-kt.coil3:coil-compose:3.0.4")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.0.4")
}인터넷 권한
URL에서 이미지를 다운로드하려면 앱에 INTERNET 권한이 필요합니다. 이는 일반적인 설치 시 권한이므로 매니페스트에 선언하기만 하면 됩니다. 런타임에 권한을 요청하는 창은 표시되지 않습니다.
이 권한이 없으면 모든 네트워크 요청이 보안 예외와 함께 실패합니다.
<!-- AndroidManifest.xml -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<application ...>
...
</application>
</manifest>첫 번째 AsyncImage
Compose에서 원격 이미지를 표시하는 가장 간단한 방법은 AsyncImage입니다. model(일반적으로 URL 문자열)과 contentDescription을 지정하면 Coil이 나머지 작업인 백그라운드 가져오기, 디코딩, 표시를 처리합니다.
컴포지션에 연결된 코루틴을 자동으로 사용하므로 컴포저블이 화면에서 사라지면 요청이 취소됩니다.
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
@Composable
fun Avatar() {
AsyncImage(
model = "https://example.com/avatar.png",
contentDescription = "User avatar",
modifier = Modifier.size(96.dp)
)
}contentScale: 이미지 맞추기
이미지가 레이아웃 슬롯의 정확한 크기와 일치하는 경우는 드뭅니다. ContentScale은 비트맵을 해당 영역 안에서 어떻게 크기 조정할지 제어합니다.
ContentScale.Crop은 상자를 가득 채우고 넘치는 부분을 잘라냅니다(아바타와 썸네일에 적합합니다).ContentScale.Fit은 이미지 전체가 보이도록 유지하며, 빈 공간이 남을 수 있습니다.ContentScale.FillBounds는 영역을 채우도록 늘립니다(이미지가 왜곡될 수 있습니다).
고정된 크기의 슬롯에는 Crop이 가장 일반적인 선택입니다.
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
@Composable
fun CircleAvatar(url: String) {
AsyncImage(
model = url,
contentDescription = "Avatar",
contentScale = ContentScale.Crop,
modifier = Modifier
.size(80.dp)
.clip(CircleShape)
)
}ImageRequest로 요청 만들기
URL 문자열을 전달하는 것은 빠르게 구현하는 방법입니다. 더 세밀하게 제어하려면 ImageRequest를 만듭니다. 이를 사용하면 크로스페이드, 헤더, 변환, 캐시 키와 같은 옵션을 설정할 수 있습니다.
Context가 필요하며, Compose에서는 LocalContext.current를 통해 제공합니다.
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import coil3.request.crossfade
@Composable
fun ProductImage(url: String) {
val context = LocalContext.current
val request = ImageRequest.Builder(context)
.data(url)
.crossfade(true)
.build()
AsyncImage(
model = request,
contentDescription = "Product photo"
)
}부드러운 로드를 위한 크로스페이드
기본적으로 이미지는 로딩이 끝나는 순간 즉시 나타나므로 갑작스럽게 느껴질 수 있습니다. crossfade(true)를 사용하면 짧은 애니메이션으로 이미지가 서서히 나타납니다.
더 느리거나 빠른 페이드 효과를 위해 밀리초 단위의 지속 시간도 전달할 수 있습니다. 이 작은 요소만으로도 목록이 훨씬 더 세련되게 느껴집니다.
import coil3.request.ImageRequest
import coil3.request.crossfade
import android.content.Context
fun smoothRequest(context: Context, url: String) =
ImageRequest.Builder(context)
.data(url)
.crossfade(durationMillis = 300)
.build()다양한 소스에서 로드하기
AsyncImage의 model은 유연합니다. Coil은 원격 URL뿐 아니라 다양한 소스 유형에서 이미지를 로드할 수 있습니다.
- 네트워크 또는 로컬 파일을 위한
String또는Uri - 드로어블 리소스 ID(
R.drawable.logo) - 기기의
File - 사진 선택기에서 가져온 콘텐츠
Uri
따라서 동일한 컴포저블이 원격 이미지와 로컬 이미지를 자연스럽게 처리할 수 있습니다.
import android.net.Uri
import androidx.compose.runtime.Composable
import coil3.compose.AsyncImage
@Composable
fun FlexibleImage(source: Any) {
// source can be a URL String, a Uri, a File, or a resource id
AsyncImage(
model = source,
contentDescription = null
)
}
// Examples:
// FlexibleImage("https://example.com/pic.jpg")
// FlexibleImage(Uri.parse("content://media/external/images/1"))로드 상태에 반응하기
이미지가 로드 중인지, 성공했는지, 실패했는지에 따라 다른 UI를 표시하고 싶을 때가 있습니다. AsyncImagePainter는 이 상태를 노출하지만, 가장 간단한 방법은 상태별 콘텐츠를 제공할 수 있는 SubcomposeAsyncImage를 사용하는 것입니다.
다음 레슨에서 플레이스홀더를 더 자세히 살펴보겠습니다. 여기서는 상태를 인식하는 로딩의 형태만 알아봅니다.
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import coil3.compose.SubcomposeAsyncImage
@Composable
fun StatefulImage(url: String) {
SubcomposeAsyncImage(
model = url,
contentDescription = "Photo",
loading = { CircularProgressIndicator() }
)
}자동으로 처리되는 취소
Coil의 가장 큰 장점 중 하나는 자동 취소입니다. AsyncImage은 컴포지션 내부에서 요청을 시작하므로 컴포저블이 화면에서 사라지면(예를 들어 행이 LazyColumn 밖으로 스크롤되면) 진행 중인 요청이 취소됩니다.
이를 통해 대역폭과 CPU를 절약하고 불필요한 작업을 방지합니다. 취소 코드를 직접 작성할 필요가 없기 때문에 Coil은 긴 목록에서도 매우 잘 확장됩니다.
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
@Composable
fun PhotoFeed(urls: List<String>) {
LazyColumn {
items(urls) { url ->
AsyncImage(
model = url,
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
)
}
}
}완성된 이미지 카드
지금까지 배운 내용을 결합해 보겠습니다. 원격 이미지가 상단을 채우고, 크로스페이드를 사용하며, 고정된 높이에 맞춰 잘리는 카드입니다. 계속해서 재사용하게 될 기본 패턴입니다.
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Card
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import coil3.request.ImageRequest
import coil3.request.crossfade
@Composable
fun ImageCard(title: String, imageUrl: String) {
val context = LocalContext.current
Card(modifier = Modifier.fillMaxWidth().padding(16.dp)) {
Column {
AsyncImage(
model = ImageRequest.Builder(context)
.data(imageUrl)
.crossfade(true)
.build(),
contentDescription = title,
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxWidth().height(180.dp)
)
Text(text = title, modifier = Modifier.padding(16.dp))
}
}
}빠른 확인
Jetpack Compose에서 URL로 원격 이미지를 표시하는 가장 간단한 coil-compose 컴포저블은 무엇입니까?
복습
Coil을 사용해 첫 번째 원격 이미지를 로드했습니다. 핵심 내용은 다음과 같습니다.
coil-compose와 OkHttp 네트워크 의존성을 추가하고INTERNET권한도 추가합니다.AsyncImage(model = url, contentDescription = ...)은 빠르게 구현하는 방법입니다.- 아바타와 썸네일에는 고정된 크기와 함께
ContentScale.Crop을 사용합니다. - 크로스페이드와 더 세밀한 제어가 필요하면
ImageRequest를 만듭니다. - 컴포저블이 화면에서 사라지면 Coil이 진행 중인 요청을 자동으로 취소합니다.
다음 내용에서는 플레이스홀더와 오류 상태를 사용해 로딩을 자연스럽게 처리하는 방법을 살펴봅니다.
AI 튜터와 함께 Kotlin을(를) 배우세요 — 무료
브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.
- 코스
- 36
- 레슨
- 152
자주 묻는 질문
“Coil로 이미지 로드” 강의는 무료인가요?
네 — “Coil로 이미지 로드” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Android Academy 강의 전체를 잠금 해제할 수 있습니다. Android Academy 강의에는 총 4개의 강의가 포함되어 있습니다.
“Coil로 이미지 로드”에서 뭘 배우나요?
Compose에서 이미지를 비동기적으로 로드합니다. 브라우저에서 직접 실행하는 실습 코드로 Android Academy을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Android Academy을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Android Academy은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 1번째 강의입니다.
“Coil로 이미지 로드” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Android Academy 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Android Academy 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- Coil로 이미지 로드
- 자리표시자와 오류 상태
- 캐싱과 성능
- 오디오와 동영상 재생