Skip to content

Commit 252a4b9

Browse files
Add crossfade between subsequent image results in AsyncImage (#3141)
* Enable crossfade between images by remembering last success painter in AsyncImagePainter * Add opt-in option for crossfade between images * Update API dump * Simplify if-condition * Enable crossfade between images by remembering last success drawable in GenericViewTarget. Additionally, opt-in option is provided. * Use crossfadeBetweenImages from target instead of ImageRequest * Annotate crossfadeBetweenImages APIs as @ExperimentalCoilApi * Expose crossfadeBetweenImages key * Enable crossfadeBetweenImages option on ImageLoader.Builder * Add documentation comment explaining crossfadeBetweenImages option * Fix failing tests after adding crossfadeBetweenImages option * Update API dump * Revert "Enable crossfade between images by remembering last success drawable in GenericViewTarget. Additionally, opt-in option is provided." This reverts commit 340a445. * Revert "Use crossfadeBetweenImages from target instead of ImageRequest" This reverts commit e887971. * refactor: Move and rename crossfadeBetweenImages to Compose-only module * Rename `crossfadeBetweenImages` to `useExistingImageAsPlaceholder` for clarity * Move from `coil-core` to `coil-compose-core` to restrict to Compose usage only * Update documentation to explicitly state Compose-only support * Remove from core module to prevent incorrect usage in XML views * Apply Spotless code formatting * Revert "Fix failing tests after adding crossfadeBetweenImages option" This reverts commit e17c262. * Update API dump --------- Co-authored-by: byungtak.lee <kyle.lee@travel-wallet.com>
1 parent 1ba80ff commit 252a4b9

File tree

5 files changed

+62
-1
lines changed

5 files changed

+62
-1
lines changed

coil-compose-core/api/android/coil-compose-core.api

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,13 @@ public final class coil3/compose/ImagePainter_androidKt {
161161
public static synthetic fun asPainter-55t9-rM$default (Lcoil3/Image;Landroid/content/Context;IILjava/lang/Object;)Landroidx/compose/ui/graphics/painter/Painter;
162162
}
163163

164+
public final class coil3/compose/ImageRequestsKt {
165+
public static final fun getUseExistingImageAsPlaceholder (Lcoil3/Extras$Key$Companion;)Lcoil3/Extras$Key;
166+
public static final fun getUseExistingImageAsPlaceholder (Lcoil3/request/ImageRequest;)Z
167+
public static final fun useExistingImageAsPlaceholder (Lcoil3/ImageLoader$Builder;Z)Lcoil3/ImageLoader$Builder;
168+
public static final fun useExistingImageAsPlaceholder (Lcoil3/request/ImageRequest$Builder;Z)Lcoil3/request/ImageRequest$Builder;
169+
}
170+
164171
public final class coil3/compose/LocalAsyncImageModelEqualityDelegateKt {
165172
public static final fun getLocalAsyncImageModelEqualityDelegate ()Landroidx/compose/runtime/ProvidableCompositionLocal;
166173
}

coil-compose-core/api/coil-compose-core.klib.api

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,15 @@ final val coil3.compose/coil3_compose_AsyncImagePainter_State_Success$stableprop
202202
final val coil3.compose/coil3_compose_ConstraintsSizeResolver$stableprop // coil3.compose/coil3_compose_ConstraintsSizeResolver$stableprop|#static{}coil3_compose_ConstraintsSizeResolver$stableprop[0]
203203
final val coil3.compose/coil3_compose_CrossfadePainter$stableprop // coil3.compose/coil3_compose_CrossfadePainter$stableprop|#static{}coil3_compose_CrossfadePainter$stableprop[0]
204204
final val coil3.compose/coil3_compose_ImagePainter$stableprop // coil3.compose/coil3_compose_ImagePainter$stableprop|#static{}coil3_compose_ImagePainter$stableprop[0]
205+
final val coil3.compose/useExistingImageAsPlaceholder // coil3.compose/useExistingImageAsPlaceholder|@coil3.Extras.Key.Companion{}useExistingImageAsPlaceholder[0]
206+
final fun (coil3/Extras.Key.Companion).<get-useExistingImageAsPlaceholder>(): coil3/Extras.Key<kotlin/Boolean> // coil3.compose/useExistingImageAsPlaceholder.<get-useExistingImageAsPlaceholder>|<get-useExistingImageAsPlaceholder>@coil3.Extras.Key.Companion(){}[0]
207+
final val coil3.compose/useExistingImageAsPlaceholder // coil3.compose/useExistingImageAsPlaceholder|@coil3.request.ImageRequest{}useExistingImageAsPlaceholder[0]
208+
final fun (coil3.request/ImageRequest).<get-useExistingImageAsPlaceholder>(): kotlin/Boolean // coil3.compose/useExistingImageAsPlaceholder.<get-useExistingImageAsPlaceholder>|<get-useExistingImageAsPlaceholder>@coil3.request.ImageRequest(){}[0]
205209

206210
final fun (coil3.compose/SubcomposeAsyncImageScope).coil3.compose/SubcomposeAsyncImageContent(androidx.compose.ui/Modifier?, androidx.compose.ui.graphics.painter/Painter?, kotlin/String?, androidx.compose.ui/Alignment?, androidx.compose.ui.layout/ContentScale?, kotlin/Float, androidx.compose.ui.graphics/ColorFilter?, kotlin/Boolean, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // coil3.compose/SubcomposeAsyncImageContent|SubcomposeAsyncImageContent@coil3.compose.SubcomposeAsyncImageScope(androidx.compose.ui.Modifier?;androidx.compose.ui.graphics.painter.Painter?;kotlin.String?;androidx.compose.ui.Alignment?;androidx.compose.ui.layout.ContentScale?;kotlin.Float;androidx.compose.ui.graphics.ColorFilter?;kotlin.Boolean;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){}[0]
211+
final fun (coil3.request/ImageRequest.Builder).coil3.compose/useExistingImageAsPlaceholder(kotlin/Boolean): coil3.request/ImageRequest.Builder // coil3.compose/useExistingImageAsPlaceholder|useExistingImageAsPlaceholder@coil3.request.ImageRequest.Builder(kotlin.Boolean){}[0]
207212
final fun (coil3/Image).coil3.compose/asPainter(coil3/PlatformContext, androidx.compose.ui.graphics/FilterQuality = ...): androidx.compose.ui.graphics.painter/Painter // coil3.compose/asPainter|asPainter@coil3.Image(coil3.PlatformContext;androidx.compose.ui.graphics.FilterQuality){}[0]
213+
final fun (coil3/ImageLoader.Builder).coil3.compose/useExistingImageAsPlaceholder(kotlin/Boolean): coil3/ImageLoader.Builder // coil3.compose/useExistingImageAsPlaceholder|useExistingImageAsPlaceholder@coil3.ImageLoader.Builder(kotlin.Boolean){}[0]
208214
final fun coil3.compose.internal/coil3_compose_internal_AbstractContentPainterNode$stableprop_getter(): kotlin/Int // coil3.compose.internal/coil3_compose_internal_AbstractContentPainterNode$stableprop_getter|coil3_compose_internal_AbstractContentPainterNode$stableprop_getter(){}[0]
209215
final fun coil3.compose.internal/coil3_compose_internal_AsyncImageState$stableprop_getter(): kotlin/Int // coil3.compose.internal/coil3_compose_internal_AsyncImageState$stableprop_getter|coil3_compose_internal_AsyncImageState$stableprop_getter(){}[0]
210216
final fun coil3.compose.internal/coil3_compose_internal_ContentPainterElement$stableprop_getter(): kotlin/Int // coil3.compose.internal/coil3_compose_internal_ContentPainterElement$stableprop_getter|coil3_compose_internal_ContentPainterElement$stableprop_getter(){}[0]

coil-compose-core/api/jvm/coil-compose-core.api

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,13 @@ public final class coil3/compose/ImagePainter_nonAndroidKt {
161161
public static synthetic fun asPainter-55t9-rM$default (Lcoil3/Image;Lcoil3/PlatformContext;IILjava/lang/Object;)Landroidx/compose/ui/graphics/painter/Painter;
162162
}
163163

164+
public final class coil3/compose/ImageRequestsKt {
165+
public static final fun getUseExistingImageAsPlaceholder (Lcoil3/Extras$Key$Companion;)Lcoil3/Extras$Key;
166+
public static final fun getUseExistingImageAsPlaceholder (Lcoil3/request/ImageRequest;)Z
167+
public static final fun useExistingImageAsPlaceholder (Lcoil3/ImageLoader$Builder;Z)Lcoil3/ImageLoader$Builder;
168+
public static final fun useExistingImageAsPlaceholder (Lcoil3/request/ImageRequest$Builder;Z)Lcoil3/request/ImageRequest$Builder;
169+
}
170+
164171
public final class coil3/compose/LocalAsyncImageModelEqualityDelegateKt {
165172
public static final fun getLocalAsyncImageModelEqualityDelegate ()Landroidx/compose/runtime/ProvidableCompositionLocal;
166173
}

coil-compose-core/src/commonMain/kotlin/coil3/compose/AsyncImagePainter.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,10 @@ class AsyncImagePainter internal constructor(
277277
return request.newBuilder()
278278
.target(
279279
onStart = { placeholder ->
280-
val painter = placeholder?.asPainter(request.context, filterQuality)
280+
var painter = placeholder?.asPainter(request.context, filterQuality)
281+
if (request.useExistingImageAsPlaceholder && painter == null && this.painter != null) {
282+
painter = this.painter
283+
}
281284
updateState(State.Loading(painter))
282285
},
283286
)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package coil3.compose
2+
3+
import coil3.Extras
4+
import coil3.ImageLoader
5+
import coil3.annotation.ExperimentalCoilApi
6+
import coil3.getExtra
7+
import coil3.request.ImageRequest
8+
9+
/**
10+
* If enabled, crossfade animation will be applied not only between placeholder and the loaded image,
11+
* but also between consecutive images (i.e., when a new image is requested after a previous one has been successfully loaded).
12+
* This allows for smooth transitions between images, rather than an abrupt replacement or only placeholder→image crossfade.
13+
*
14+
* Note: The [coil3.request.crossfade] option must also be enabled for [useExistingImageAsPlaceholder] to take effect.
15+
* If [coil3.request.crossfade] is not enabled, this option will have no effect.
16+
*
17+
* Also note: If a placeholder is set, the crossfade will always occur between the placeholder and the result,
18+
* so consecutive image crossfading may not be observable in that scenario.
19+
*
20+
* Also note: This option is only supported in Jetpack Compose (AsyncImage, AsyncImagePainter).
21+
* It does not apply to XML-based views.
22+
*/
23+
fun ImageRequest.Builder.useExistingImageAsPlaceholder(enable: Boolean) = apply {
24+
extras[useExistingImageAsPlaceholderKey] = enable
25+
}
26+
27+
fun ImageLoader.Builder.useExistingImageAsPlaceholder(enable: Boolean) = apply {
28+
extras[useExistingImageAsPlaceholderKey] = enable
29+
}
30+
31+
@ExperimentalCoilApi
32+
val ImageRequest.useExistingImageAsPlaceholder: Boolean
33+
get() = getExtra(useExistingImageAsPlaceholderKey)
34+
35+
val Extras.Key.Companion.useExistingImageAsPlaceholder: Extras.Key<Boolean>
36+
get() = useExistingImageAsPlaceholderKey
37+
38+
private val useExistingImageAsPlaceholderKey = Extras.Key(default = false)

0 commit comments

Comments
 (0)