Zoomable Image¶
A drop-in replacement for async Image()
composables featuring support for pan & zoom gestures and automatic sub-sampling of large images. This ensures that images maintain their intricate details even when fully zoomed in, without causing any OutOfMemory
exceptions.
Features
- Sub-sampling of bitmaps
- Pinch to zoom and flings
- Double tap to zoom
- Single finger zoom (double tap and hold)
- Haptic feedback for over/under zoom
- Compatibility with nested scrolling
- Click listeners
Image requests¶
For complex scenarios, ZoomableImage
can also take full image requests:
ZoomableAsyncImage(
model = ImageRequest.Builder(LocalContext.current)
.data("https://example.com/image.jpg")
.listener(
remember {
object : ImageRequest.Listener {
override fun onSuccess(…) {}
override fun onError(…) {}
}
}
)
.crossfade(1_000)
.memoryCachePolicy(CachePolicy.DISABLED)
.build(),
imageLoader = LocalContext.current.imageLoader, // Optional.
contentDescription = …
)
ZoomableGlideImage(
model = "https://example.com/image.jpg",
contentDescription = …
) {
it.addListener(object : RequestListener<Drawable> {
override fun onResourceReady(…): Boolean = TODO()
override fun onLoadFailed(…): Boolean = TODO()
})
.transition(withCrossFade(1_000))
.skipMemoryCache(true)
.disallowHardwareConfig()
.timeout(30_000),
}
Placeholders¶
If your images are available in multiple resolutions, telephoto
highly recommends using their lower resolutions as placeholders while their full quality equivalents are loaded in the background.
When combined with a cross-fade transition, ZoomableImage
will smoothly swap out placeholders when their full quality versions are ready to be displayed.
ZoomableAsyncImage(
modifier = Modifier.fillMaxSize(),
model = ImageRequest.Builder(LocalContext.current)
.data("https://example.com/image.jpg")
.placeholderMemoryCacheKey(…)
.crossfade(1_000)
.build(),
contentDescription = …
)
placeholderMemoryCacheKey()
can be found on Coil's website.
ZoomableGlideImage(
modifier = Modifier.fillMaxSize(),
model = "https://example.com/image.jpg",
contentDescription = …
) {
it.thumbnail(…) // or placeholder()
.transition(withCrossFade(1_000)),
}
thumbnail()
can be found on Glide's website.
Content alignment¶
Alignment.TopCenter |
Alignment.BottomCenter |
When images are zoomed, they're scaled with respect to their alignment
until they're large enough to fill all available space. After that, they're scaled uniformly. The default alignment
is Alignment.Center
.
Content scale¶
ContentScale.Inside |
ContentScale.Crop |
Images are scaled using ContentScale.Fit
by default, but can be customized. A visual guide of all possible values can be found here.
Unlike Image()
, ZoomableImage
can pan images even when they're cropped. This can be useful for applications like wallpaper apps that may want to use ContentScale.Crop
to ensure that images always fill the screen.
Warning
Placeholders are visually incompatible with ContentScale.Inside
.
Click listeners¶
For detecting double taps, ZoomableImage
consumes all tap gestures making it incompatible with Modifier.clickable()
and Modifier.combinedClickable()
. As an alternative, its onClick
and onLongClick
parameters can be used.
Sharing hoisted state¶
For handling zoom gestures, Zoomablemage
uses Modifier.zoomable()
underneath. If your app displays different kinds of media, it is recommended to hoist the ZoomableState
outside so that it can be shared with all zoomable composables: