Skip to content

Recipes

Modifier.zoomable()

Setting zoom limits

val zoomableState = rememberZoomableState(
  zoomSpec = ZoomSpec(maxZoomFactor = 4f)
)

ZoomableAsyncImage(
  state = rememberZoomableImageState(zoomableState),
  model = "https://example.com/image.jpg",
  contentDescription = ,
)
val zoomableState = rememberZoomableState(
  zoomSpec = ZoomSpec(maxZoomFactor = 4f)
)

ZoomableGlideImage(
  state = rememberZoomableImageState(zoomableState),
  model = "https://example.com/image.jpg",
  contentDescription = ,
)

Observing image loads

val imageState = rememberZoomableImageState()

// Whether the full quality image is loaded. This will be false for placeholders
// or thumbnails, in which case isPlaceholderDisplayed can be used instead.
val showLoadingIndicator = imageState.isImageDisplayed

AnimatedVisibility(visible = showLoadingIndicator) {
  CircularProgressIndicator()    
}

Grabbing downloaded images

Low resolution drawables can be accessed by using request listeners. These images are down-sampled by your image loading library to fit in memory and are suitable for simple use-cases such as color extraction.

ZoomableAsyncImage(
  model = ImageRequest.Builder(LocalContext.current)
    .data("https://example.com/image.jpg")
    .listener(onSuccess = { _, result ->
      // TODO: do something with result.drawable.
    })
    .build(),
  contentDescription = 
)
ZoomableGlideImage(
  model = "https://example.com/image.jpg",
  contentDescription = 
) {
  it.addListener(object : RequestListener<Drawable> {
    override fun onResourceReady(resource: Drawable, ): Boolean {
      // TODO: do something with resource.
    }
  })
}

Full resolutions must be obtained as files because ZoomableImage streams them directly from disk. The easiest way to do this is to load them again from cache.

val state = rememberZoomableImageState()
ZoomableAsyncImage(
  model = imageUrl,
  state = state,
  contentDescription = ,
)

if (state.isImageDisplayed) {
  Button(onClick = { downloadImage(context, imageUrl) }) {
    Text("Download image")
  }
}
suspend fun downloadImage(context: Context, imageUrl: HttpUrl) {
  val result = context.imageLoader.execute(
    ImageRequest.Builder(context)
      .data(imageUrl)
      .build()
  )
  if (result is SuccessResult) {
    val cacheKey = result.diskCacheKey ?: error("image wasn't saved to disk")
    val diskCache = context.imageLoader.diskCache!!
    diskCache.openSnapshot(cacheKey)!!.use { 
      // TODO: copy to Downloads directory.           
    }
  }
}

val state = rememberZoomableImageState()
ZoomableGlideImage(
  model = imageUrl,
  state = state,
  contentDescription = ,
)

if (state.isImageDisplayed) {
  Button(onClick = { downloadImage(context, imageUrl) }) {
    Text("Download image")
  }
}
fun downloadImage(context: Context, imageUrl: Uri) {
  Glide.with(context)
    .download(imageUrl)
    .into(object : CustomTarget<File>() {
      override fun onResourceReady(resource: File, ) {
        // TODO: copy file to Downloads directory.
      }

      override fun onLoadCleared(placeholder: Drawable?) = Unit
    )
}