Kotlin Apache-2.0

Zoomable

Compose multiplatform library that enables the content to be zoomable by gestures

U

usuiat

Dernière activité 28 sept. 2026
usuiat/Zoomable

755

étoiles

36

forks

9

issues ouvertes

androidcomposecompose-multiplatformioskotlin

Ce README est souvent en anglais.

Zoomable

Zoomable is a Compose Multiplatform library that enables the content zoomable by pinch gesture, double-tap, tap-and-drag gesture, or mouse scroll wheel.

Pinch Double-tap Double-tap and drag
Pinch Double-tap Double-tap and drag

Zoomable provides a simple Modifier extension function Modifier.zoomable.

Here is the simplest sample code. You can make image zoomable by just adding one line code.

Image(
    painter = painterResource(id = R.drawable.penguin),
    contentDescription = null,
    modifier = Modifier.zoomable(rememberZoomState()),
)

Zoomable can be used in the following ways:

  • combined with any composable components such as Image or Text
  • combined with asynchronous image loading such as coil's rememberAsyncImagePainter
  • used within a HorizontalPager or VerticalPager

Platforms

Zoomable supports Android, iOS, macOS, Desktop, and Web (Wasm and JS).

Platform Pinch Double-tap Tap-and-drag Mouse scroll wheel
Android ✅ ✅ ✅ ✅
iOS ✅ ✅ ✅ ✅
macOS - 🧪 *2 🧪 *2 🧪 *3
Desktop - *1 ✅ ✅ ✅
Wasm ✅ ✅ ✅ ✅
JS ✅ ✅ ✅ ✅

API Reference

API Reference🔎

Usage

Download

Zoomable is available on Maven Central.

repositories {
    mavenCentral()
}

dependencies {
    implementation "net.engawapg.lib:zoomable:$version"
}

The latest version: version badge

If your app targets macOS (native), add the following to gradle.properties, since Compose for macOS is experimental.

org.jetbrains.compose.experimental.macos.enabled=true

Basic Usage

You can use Modifier.zoomable to make contents such as an image zoomable. The zoom state is managed in a ZoomState object that can be created via rememberZoomState. If contentSize is set, the range of offset will be optimized for the specified size.

val painter = painterResource(id = R.drawable.penguin)
val zoomState = rememberZoomState(contentSize = painter.intrinsicSize)
Image(
    painter = painter,
    contentDescription = "Zoomable image",
    contentScale = ContentScale.Fit,
    modifier = Modifier
        .fillMaxSize()
        .zoomable(zoomState),
)

Asynchronous Image Loading

You can use Modifier.zoomable with Coil's rememberAsyncImagePainter.

val painter = rememberAsyncImagePainter("https://example.com/image.jpg")
val zoomState = rememberZoomState(contentSize = painter.intrinsicSize)
Image(
    painter = painter,
    contentDescription = "Zoomable image",
    contentScale = ContentScale.Fit,
    modifier = Modifier
        .fillMaxSize()
        .zoomable(zoomState),
)

If you want to use AsyncImage, you need to call setContentSize when the image is successfully loaded.

val zoomState = rememberZoomState()
AsyncImage(
    model = "https://example.com/image.jpg",
    contentDescription = "Zoomable image",
    contentScale = ContentScale.Fit,
    onSuccess = { state ->
        zoomState.setContentSize(state.painter.intrinsicSize)
    },
    modifier = Modifier
        .fillMaxSize()
        .zoomable(zoomState),
)

Double tap action

By default, every time double tap is detected, zoomable modifier switches the scale between 1.0f and 2.5f.

To change the scale set for double-tap detection, call toggleScale with desired value in onDoubleTap callback.

val targetScale = 5.0f
zoomable(
    zoomState = zoomState,
    onDoubleTap = { position -> zoomState.toggleScale(targetScale, position) }
)

If you want to implement the logic to determine the scale value yourself, you can use the changeScale function. In the example below, the scale is switched in three steps.

zoomable(
    zoomState = zoomState,
    onDoubleTap = { position ->
        val targetScale = when {
            zoomState.scale < 2f -> 2f
            zoomState.scale < 4f -> 4f
            else -> 1f
        }
        zoomState.changeScale(targetScale, position)
    }
)

To disable double tap action, set empty function to onDoubleTap.

zoomable(
    zoomState = zoomState,
    onDoubleTap = {}
)

One finger zoom

By default, one finger zoom action, tap followed by vertical drag, is enabled. If you want disable it, set false to enableOneFingerZoom.

zoomable(
    zoomState = zoomState,
    enableOneFingerZoom = false,
)

Bounce

While a zoom gesture is in progress, the content can be scaled a little beyond its normal range, and springs back once the gesture ends. Pass a Bounce to rememberZoomState to configure how far it goes.

Both values are factors applied to the boundaries of the normal scale range, not absolute scale values. Note that the two are applied to different boundaries: lower is applied to the minimum scale, which is always 1.0, while upper is applied to maxScale.

val zoomState = rememberZoomState(
    maxScale = 5f,
    // Scale can reach 0.8 while shrinking and 6.0 while enlarging.
    bounce = Bounce(lower = 0.8f, upper = 1.2f),
)

By default, the content bounces while shrinking but not while enlarging (Bounce.Default). Use Bounce.None to disable bouncing altogether.

ScrollGesturePropagation

You can choose when to propagate scroll gestures to the parent Pager composable by specifying scrollGesturePropagation.

  • ContentEdge: Scroll gesture is propagated when the content is scrolled to the edge and attempts to scroll further.
  • NotZoomed: Scroll gesture is propagated when the content is not zoomed.
ScrollGesturePropagation.ContentEdge ScrollGesturePropagation.NotZoomed
ContentEdge NotZoomed

Mouse Scroll Wheel

By default, zooming works by holding down the Ctrl key and operating the mouse wheel. This is intended to be used primarily with desktop and web, but it also works with Android and iOS. You can change the behavior by using the mouseWheelZoom argument of zoomable.

  • Disabled disables zoom by mouse wheel operation.
  • Enabled enables zoom by mouse wheel operation regardless of whether the modifier key is pressed.
  • EnabledWithCtrlKey, EnabledWithShiftKey, EnabledWithAltKey, and EnabledWithMetaKey enable zoom by mouse wheel operation while holding down the modifier key.

Snap Back Zoom

Modifier.snapBackZoomable allows you to zoom an image with a pinch gesture and automatically return it to its original size when you release all fingers. You can easily achieve Instagram-like behavior.

Snap back zoom

Zoom with Scroll Component

To zoom a component that itself scrolls, such as a LazyColumn or LazyRow, use zoomableWithScroll.

Warning

zoomableWithScroll is an experimental API and may change in the future.

LazyColumn(
    modifier = Modifier.zoomableWithScroll(rememberZoomState())
) {
    items(100) { Text("Item $it") }
}

It can also be used with Modifier.verticalScroll or Modifier.horizontalScroll. In that case, put zoomableWithScroll before verticalScroll or horizontalScroll in the Modifier chain.

Column(
    modifier = Modifier
        .fillMaxSize()
        .zoomableWithScroll(rememberZoomState())
        .horizontalScroll(rememberScrollState())
) {
    repeat(100) { Text("Item $it") }
}

Samples

You can try sample app that contains following samples.

  • Standard Image composable (source)
  • Asynchronous image loading using Coil library (source)
  • Image on HorizontalPager (source)
  • Snap back zoom (source)
  • LazyColumn (source)
  • Row with Modifier.horizontalScroll (source)

License

Copyright 2022 usuiat

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Projets similaires

Building blocks for designing media experiences in Compose UI

Kotlinandroidcomposeimage
Ssaket
1,6 k étoiles56

Image zoom library specially designed for Compose Multiplatform and Android View. Supported scale, pan, locate, rotation, and super-large image subsampling.

Kotlinandroidcomposecompose-multiplatform
Ppanpf
660 étoiles37

Compose Multiplatform, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.

Kotlinandroidawtcompose
JJetBrains
19,4 k étoiles1,4 k