Skip to content

Recipes

Observing pan & zoom

val state = rememberZoomableState()
Box(
  Modifier.zoomable(state)
)

LaunchedEffect(state.contentTransformation) {
  println("Pan = ${state.contentTransformation.offset}")
  println("Zoom = ${state.contentTransformation.scale}")
  println("Zoom fraction = ${state.zoomFraction}")
}

// Example use case: Hide system bars when image is zoomed in.
val systemUi = rememberSystemUiController()
val isZoomedOut = (zoomState.zoomFraction ?: 0f) < 0.1f
LaunchedEffect(isZoomedOut) {
  systemUi.isSystemBarsVisible = isZoomedOut
}

Controlling pan & zoom

val state = rememberZoomableState()
Box(
  Modifier.zoomable(state)
)

Button(onClick = { state.zoomBy(zoomFactor = 1.2f) }) {
  Text("+")
}
Button(onClick = { state.zoomBy(zoomFactor = 1 / 1.2f) }) {
  Text("-")
}
Button(onClick = { state.panBy(offset = 50.dp) }) {
  Text(">")
}
Button(onClick = { state.panBy(offset = -50.dp) }) {
  Text("<")
}

Resetting zoom

Modifier.zoomable() will automatically retain its pan & zoom across state restorations. You may want to prevent this in lazy layouts such as a Pager(), where each page is restored every time it becomes visible.

val pagerState = rememberPagerState()
HorizontalPager(
  state = pagerState,
  pageCount = 3,
) { pageNum ->
  val zoomableState = rememberZoomableState()
  ZoomableContent(
    state = zoomableState
  )

  if (pagerState.settledPage != pageNum) {
    // Page is now off-screen. Prevent restoration of 
    // current zoom when this page becomes visible again.
    LaunchedEffect(Unit) {
      zoomableState.resetZoom(animationSpec = SnapSpec())
    }
  }
}

Warning

A bug in Pager() previously caused settledPage to reset to 0 upon state restoration. This issue has been resolved in androidx.compose.foundation:foundation:1.5.0-alpha02.