Łatwe ładowanie obrazków na Androidzie w Kotlinie dzięki Coil
Dowiesz się tutaj, jak używać Coil — nowego image loadera Kotlina od Instacart. Poza omówieniem ogólnego zastosowania przyjrzymy się również transformacjom, cofaniu żądań i funkcji samplowania obrazków. Porównamy też jego działanie z Glide oraz Picasso.
Coil to nowa biblioteka do ładowania obrazków od Instacart, który używa wielu zaawansowanych funkcji, takich jak współprogramy, OkHttp oraz androidX.lifecycle
. Coil dodaje około 1.500 funkcji do Twojego APK, co jest porównywalne do Picasso, ale jest to trochę mniej od Glide, czy Fresco. Coil również zawiera takie funkcje jak samplowanie, efektywne użycie pamięci i automatyczne cofanie lub zatrzymywanie żądań.
Coil jest domyślnie w pełni kompatybilny z technikami optymalizacji R8, więc programiści nie muszą dodawać żadnych reguł ProGuard związanych z tym image loaderem.
Integracja
Coil wymaga kodu bajtowego Javy 8, więc należy dodać następujący kod to pliku build.gradle
, aby wszystko działało.
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
kotlinOptions {
jvmTarget = "1.8"
}
}
Nasz image loader posiada 4 artefakty, a każdy z nich ma swoje własne przeznaczenie. Głównie używamy jednak dwóch poniższych:
io.coil-kt
: Coil: domyślny artefakt, który zawiera singletonCoil
. Jeśli nie używasz wstrzykiwania zależności lub nie zarządzasz samodzielnie instancjami Coil w swojej apce, to lepiej użyć takiego artefaktu.io.coil-kt:coil-base
: artefakt bazowy, który nie zawiera singletona Coil. Ten artefakt jest lepszy, jeśli używasz wstrzykiwania zależności, aby dodać instancję (lub kilka instancji) Coil.
//Singleton artifact
implementation "io.coil-kt:coil:0.9.5"
//Base artifact without singleton
implementation "io.coil-kt:coil-base:0.9.5"
Funkcje
Teraz gdy już zintegrowaliśmy naszą bibliotekę, czas zacząć działać z Coil. Spróbujmy załadować prosty obraz:
imageView.load("https://www.example.com/image.jpg")
Ponieważ Coil jest pierwszym programem ładującym obrazy w Kotlinie, wykorzystano wiele funkcji tego języka, a jedną z nich są rozszerzenia. Load
jest właśnie funkcją rozszerzającą Coila, dzięki której można naprawdę prosto załadować zdalny obrazek.
Dzięki rozszerzeniu load
Coil uchronił nas od przekazywania kontekstu i tworzenia instancji imageholder
za każdym razem. Spójrzmy na rozszerzenie od Coil:
inline fun ImageView.load(
uri: String?,
imageLoader: ImageLoader = Coil.loader(),
builder: LoadRequestBuilder.() -> Unit = {}
): RequestDisposable {
return imageLoader.load(context, uri) {
target(this@load)
builder()
}
}
Musieliśmy wykorzystać kontekst z imageView oraz tworzenie loadera z Coil. To dlatego preferujemy biblioteki natywne.
Placeholder i obrazek błędu
Prawie wszystkie biblioteki (np. Glide, Picasso i Fresco) mają taką funkcję — pokazujemy sztuczny obraz (placeholder), dopóki nie zostanie załadowany właściwy obrazek lub pokazujemy obrazek błędu, jeśli żądanie się nie powiedzie.
Jest to funkcja wbudowana w bibliotekę i najbardziej ekscytująca jest tutaj składnia: wykorzystując te funkcje widzimy, jak fajnie korzysta ona ze składni Kotlina (np. funkcje let
oraz apply
w Kotlinie). Spójrzmy:
fun loadWithCoil(imageView: ImageView, imageUrl: String){
imageView.load(imageUrl){
placeholder(R.drawable.placeholder)
error(R.drawable.error)
}
}
Wstępne ładowanie obrazów
Coil używa współprogramów do lepszego pobierania obrazów. Jesteśmy w stanie pobrać zdalny obraz przy użyciu zawieszalnej funkcji get
(zdefiniowanej z suspend). Spójrzmy:
val image = Coil.get(imageUrl)
Dzięki zawieszalnej (a więc zdefiniowanej z suspend) funkcji get
możemy pobierać zdalne obrazy jako drawable
.
Wywołania zwrotne
Zdarzą się przypadki, w których będziesz potrzebować wywołania zwrotnego po pobraniu obrazu. Coil zajmuje się tym przy pomocy funkcji targets
:
fun coilWithCallbacks(){
Coil.load(context, "https://www.example.com/image.jpg") {
target { drawable ->
// Handle the successful result.
}
}
}
Istnieją trzy typy target: Target
, ViewTarget
oraz possibleViewTarget
.
Target
używamy, kiedy żądanie nie jest połączone z żadnym widokiemViewTarget
używamy, kiedy żądanie jest połączone zimageview
(np. pokazywanie placeholdera, dopóki żądanie nie zostanie ukończone).possibleViewTarget
używamy, kiedy zachodzi potrzeba bitmap pooling’u.
Transformacje
Teraz kiedy techniki ładowania mamy z głowy, czas na transformację. Transformacje są niezwykle przydatne i powinny być zawarte w każdej bibiotece do ładowania obrazków. W Coil mamy 4 transformacje: blur, circle crop, grayscale oraz rounded corners. Poniższy kod pokazuje, jak ich używać:
fun loadWithCoil(imageView: ImageView, imageUrl: String){
imageView.load(imageUrl){
placeholder(R.drawable.gradient_place_details)
error(R.drawable.gradient_place_details)
transformations(CircleCropTransformation())
}
}
Cofanie żądań
Efektywne wykorzystanie zasobów jest kluczem do wydajności aplikacji. Coil domyślnie anuluje żądanie, gdy imageview
zostanie odłączony lub kontekst zostanie zniszczony.
Coil domyślnie zajmuje się wszystkimi przypadkami wycieków pamięci, ale zdarzają się sytuacje, w których chcemy anulować całą operację. W tym przypadku nasz image loader zwraca RequestDisposable
dla każdego żądania load, za pomocą którego można anulować żądanie. Spójrzmy:
val disposable = imageView.load("https://www.example.com/image.jpg")
// Cancel the request.
disposable.dispose()
Samplowanie obrazów
Samplowanie obrazów jest najbardziej zaawansowaną techniką używaną do ładowania obrazów o jakości opartej na rozmiarze imageview
. Załóżmy, że na dysku znajduje się obraz o rozmiarze 500 x 500. Coil początkowo ładuje 100 x 100 i używa tego obrazu jako placeholdera aż do załadowania obrazu o pełnej jakości. Działa on jak progresywny JPEG. Możemy to włączyć podczas ładowania dowolnego obrazu za pomocą funkcji crossfade
. Spójrzmy:
imageView.load(imageUrl){
crossfade(true)
}
Porównanie z Glide/Picasso
Teraz porównajmy kod z Glide i Picasso.
Podstawowe użycie:
// Glide
Glide.with(context)
.load(url)
.into(imageView)
// Picasso
Picasso.get()
.load(url)
.into(imageView)
// Coil
imageView.load(url)
Żądania wywołania zwrotnego
// Glide (has optional callbacks for start and error)
Glide.with(context)
.load(url)
.into(object : CustomTarget<Drawable>() {
override fun onResourceReady(resource: Drawable, transition: Transition<Drawable>) {
// Handle the successful result.
}
override fun onLoadCleared(placeholder: Drawable) {
// Remove the drawable provided in onResourceReady from any Views and ensure no references to it remain.
}
})
// Picasso
Picasso.get()
.load(url)
.into(object : BitmapTarget {
override fun onBitmapLoaded(bitmap: Bitmap, from: Picasso.LoadedFrom) {
// Handle the successful result.
}
override fun onBitmapFailed(e: Exception, errorDrawable: Drawable?) {
// Handle the error drawable.
}
override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
// Handle the placeholder drawable.
}
})
// Coil (has optional callbacks for start and error)
Coil.load(context, url) {
target { drawable ->
// Handle the successful result.
}
}
Niestandardowe żądania
imageView.scaleType = ImageView.ScaleType.FIT_CENTER
// Glide
Glide.with(context)
.load(url)
.placeholder(placeholder)
.fitCenter()
.into(imageView)
// Picasso
Picasso.get()
.load(url)
.placeholder(placeholder)
.fit()
.into(imageView)
// Coil (autodetects the scale type)
imageView.load(url) {
placeholder(placeholder)
}
Oryginał tekstu w języku angielskim możesz przeczytać tutaj.