scale.md
August 8, 2025 · View on GitHub
Scaling Image
Translations: 简体中文
Tip
- The following example takes precedence over the Compose version component for demonstration
- ZoomState.zoomable is equivalent to ZoomImageView.zoomable
- ZoomState.subsampling is equivalent to ZoomImageView.subsampling
ZoomImage supports multiple ways to scale images, such as two-finger scale, single-finger scale, double-click scale, mouse wheel scale, keyboard scale, scale(), etc.
Features
- Support One-Finger Scale, Two-Finger Scale, Double-click Scale, Mouse Wheel Scale, Keyboard Scale and scaling to a specified multiple by the scale(), scaleBy(), scaleByPlus() method
- Supports rubber band effect. When the gesture is continuously zoomed (one-finger/two-finger scale) exceeds the maximum or minimum range, zooming can continue, but there is a damping effect, and it will rollback to the maximum or minimum scale multiplier when released
- Dynamic scaling range. Default based on containerSize, contentSize, contentOriginSize dynamically calculate mediumScale and maxScale
- Support for animation. Both the scale() method and double-click scaling support animation
- All ContentScale and Alignment are supported,ZoomImageView also supports ContentScale and Alignment
- Disabling gestures. Supports disabling gestures such as double-click scale, two-finger scale, one-finger scale, mouse wheel scale, and drag
- Only when the containerSize changes (dragging to resize the window on the desktop), ZoomImage will keep the scale factor and content visible center point unchanged
- When the page is rebuilt (the screen rotates, the app is recycled in the background), the scale and offset are reset
- Supports reading related information. You can read scale-related information such as the current scale multiplier and the minimum, middle, and maximum scale multiples
ContentScale, Alignment
ZoomImage supports all ContentScale and Alignment, and because the compose version and the view version use the same algorithm, view The version of the component supports ContentScale and Alignment in addition to ScaleType
Example:
val sketchZoomImageView = SketchZoomImageView(context)
sketchZoomImageView.zoomable.setContentScale(ContentScaleCompat.None)
sketchZoomImageView.zoomable.setAlignment(AlignmentCompat.BottomEnd)
minScale, mediumScale, maxScale
The ZoomImage is always controlled by three parameters in the process of scaling: minScale, mediumScale, and maxScale:
minScale:The minimum scale multiplier, which limits the minimum value of ZoomImage during scaling, is calculated as:ContentScale.computeScaleFactor(srcSize, dstSize).scaleXmediumScale:The intermediate scale multiplier is specially used for double-click scaling, and the value is controlled by the scalesCalculator parametermaxScale:The maximum scale multiplier is used to limit the maximum value of ZoomImage during scaling, and the value is controlled by the scalesCalculator parameter
ScalesCalculator
ScalesCalculator is specially used to calculate mediumScale and maxScale. ZoomImage has two built-in ScalesCalculator:
Tip
- minMediumScale =
minScale * multiple - fillContainerScale =
max(containerSize.width / contentSize.width.toFloat(), containerSize.height / contentSize.height.toFloat()) - contentOriginScale =
max(contentOriginSize.width / contentSize.width.toFloat(), contentOriginSize.height / contentSize.height.toFloat()) - initialScale usually calculated by ReadMode
- multiple default value is 3f
- ScalesCalculator.Dynamic:
- mediumScale calculation rules are as follows:
- If contentScale is FillBounds, it is always minMediumScale
- Always initialScale if initialScale is greater than minScale
- Otherwise, take the largest among minMediumScale, fillContainerScale, and contentOriginScale.
- maxScale calculation rules are as follows:
- If contentScale is FillBounds, it is always
mediumScale * multiple - Otherwise, take the largest among
mediumScale * multiple, contentOriginScale
- If contentScale is FillBounds, it is always
- mediumScale calculation rules are as follows:
- ScalesCalculator.Fixed:
- mediumScale calculation rules are as follows:
- If contentScale is FillBounds, it is always minMediumScale
- Always initialScale if initialScale is greater than minScale
- Otherwise always minMediumScale
- maxScale is always
mediumScale * multiple
- mediumScale calculation rules are as follows:
scalesCalculator defaults to ScalesCalculator. Dynamic, which you can modify into a Fixed or custom implementation
Example:
val zoomState: ZoomState by rememberSketchZoomState()
zoomState.zoomable.setScalesCalculator(ScalesCalculator.Fixed)
// or
zoomState.zoomable.setScalesCalculator(MyScalesCalculator())
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
Two-Finger Scale
You can pinch the scale image with two fingers, and ZoomImage will calculate the scale factor based on the distance between the two fingers. The pinch-to-scale feature is on by default, but you can turn it off as follows:
val zoomState: ZoomState by rememberSketchZoomState()
zoomState.zoomable.setDisabledGestureTypes(
zoomState.zoomable.disabledGestureTypes or GestureType.TWO_FINGER_SCALE
)
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
Double-click Scale
ZoomImage supports double-clicking the image to switch the scale factor
threeStepScale
By default, it always cycles between minScale and mediumScale. If you want to cycle between minScale, mediumScale and maxScale, you can modify it. The threeStepScale property is true, as follows:
val zoomState: ZoomState by rememberSketchZoomState()
zoomState.zoomable.setThreeStepScale(true)
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
switchScale()
Double-clicking to scale invokes ZoomImage's switchScale() method, or you can call switchScale()
when
needed The method toggles the scale factor, which has two parameters:
centroidContentPoint: IntOffset = contentVisibleRect.center: The scale center point on Content, the origin is the upper-left corner of Content, and the default is the center of Content's currently visible areaanimated: Boolean = false: Whether to use animation, the default is false
Tip
Note: centroidContentPoint must be a point on content
Example:
val zoomState: ZoomState by rememberSketchZoomState()
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
val coroutineScope = rememberCoroutineScope()
Button(
onClick = {
coroutineScope.launch {
zoomState.zoomable.switchScale(animated = true)
}
}
) {
Text(text = "switch scale")
}
getNextStepScale()
You can also call the getNextStepScale() method to get the next scale multiplier
Example:
val zoomState: ZoomState by rememberSketchZoomState()
zoomState.zoomable.getNextStepScale()
Turn off double-click scale
The double-click scale feature is on by default, but you can turn it off as follows:
val zoomState: ZoomState by rememberSketchZoomState()
zoomState.zoomable.setDisabledGestureTypes(
zoomState.zoomable.disabledGestureTypes or GestureType.DOUBLE_TAP_SCALE
)
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
One Finger Scale
ZoomImage supports zooming images with one finger. Double-click and hold the screen and slide up and down to scale the image. This feature is enabled by default, but you can turn it off as follows:
val zoomState: ZoomState by rememberSketchZoomState()
zoomState.zoomable.setDisabledGestureTypes(
zoomState.zoomable.disabledGestureTypes or GestureType.ONE_FINGER_SCALE
)
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
Mouse Wheel Scale
ZoomImage supports scaling images through the mouse wheel. ZoomImage takes the current mouse position as the scale center and calculates the scale factor based on the rolling direction and distance of the mouse wheel.
You can reverse mouse wheel scaling by setting the reverseMouseWheelScale property, as follows:
val zoomState: ZoomState by rememberSketchZoomState()
zoomState.zoomable.setReverseMouseWheelScale(true)
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
The mouse wheel scale function is enabled by default, but you can turn it off as follows:
val zoomState: ZoomState by rememberSketchZoomState()
zoomState.zoomable.setDisabledGestureTypes(
zoomState.zoomable.disabledGestureTypes or GestureType.MOUSE_WHEEL_SCALE
)
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
You can also customize the calculation method of mouse wheel scaling through the
mouseWheelScaleCalculator property. The default is MouseWheelScaleCalculator.Default, as
follows:
val zoomState: ZoomState by rememberSketchZoomState()
val mouseWheelScaleCalculator = MouseWheelScaleCalculator { currentScale, scrollDelta ->
// return new scale
}
zoomState.zoomable.setMouseWheelScaleCalculator(mouseWheelScaleCalculator)
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
Keyboard scale
ZoomImage supports scaling images through the keyboard, supports both short press and long press operations. And the following keys are registered by default:
- scale in: Key.ZoomIn, Key.Equals + (meta/ctrl)/alt, Key.DirectionUp + (meta/ctrl)/alt
- scale out: Key.ZoomOut, Key.Minus + (meta/ctrl)/alt, Key.DirectionDown + (meta/ctrl)/alt
Since the keyboard zoom function must rely on focus, and focus management is very complex, it is not enabled by default. You need to actively configure and request focus, as follows:
val focusRequester = remember { FocusRequester() }
val zoomState = rememberSketchZoomState()
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
zoomState = zoomState,
modifier = Modifier.fillMaxSize()
.focusRequester(focusRequester)
.focusable()
.keyZoom(zoomState.zoomable),
)
LaunchedEffect(Unit) {
focusRequester.requestFocus()
}
Tip
When requesting focus in HorizontalPager, you need to note that you can only request focus for the current page, otherwise it will cause unexpected accidents.
You can also turn it off dynamically via gesture control, as follows:
val zoomState: ZoomState by rememberSketchZoomState()
zoomState.zoomable.setDisabledGestureTypes(
zoomState.zoomable.disabledGestureTypes or GestureType.KEYBOARD_SCALE
)
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
scale()
ZoomImage provides the scale() method to scale the image to a specified multiple, which has three parameters:
targetScale: Float: Target scale multiplecentroidContentPoint: IntOffset = contentVisibleRect.center: The scale center point on the content, the origin is the upper-left corner of the content, and the default is the center of the currently visible area of the contentanimated: Boolean = false: Whether to use animation, the default is false
Tip
Note: centroidContentPoint must be a point on content
Example:
val zoomState: ZoomState by rememberSketchZoomState()
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
val coroutineScope = rememberCoroutineScope()
Button(
onClick = {
coroutineScope.launch {
zoomState.zoomable.scale(targetScale = 8f, animated = true)
}
}
) {
Text(text = "scale to 8f")
}
Button(
onClick = {
coroutineScope.launch {
zoomState.zoomable.scale(targetScale = 4f, animated = true)
}
}
) {
Text(text = "scale to 4f")
}
scaleBy()
ZoomImage provides the scaleBy() method used to incrementally scale the image to a specified multiple by multiplication. It has three parameters:
addScale: Float: Incremental scaling multiplecentroidContentPoint: IntOffset = contentVisibleRect.center: The scale center point on the content, the origin is the upper-left corner of the content, and the default is the center of the currently visible area of the contentanimated: Boolean = false: Whether to use animation, the default is false
Tip
Note: centroidContentPoint must be a point on content
Example:
val zoomState: ZoomState by rememberSketchZoomState()
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
val coroutineScope = rememberCoroutineScope()
Button(
onClick = {
coroutineScope.launch {
zoomState.zoomable.scaleBy(addScale = 1.5f, animated = true)
}
}
) {
Text(text = "scale * 1.5")
}
Button(
onClick = {
coroutineScope.launch {
zoomState.zoomable.scaleBy(addScale = 0.67f, animated = true)
}
}
) {
Text(text = "scale * 0.67")
}
scaleByPlus()
ZoomImage provides the scaleByPlus() method used to scale the image to a specified multiple by addition, and it has three parameters:
addScale: Float: Incremental scaling multiplecentroidContentPoint: IntOffset = contentVisibleRect.center: The scale center point on the content, the origin is the upper-left corner of the content, and the default is the center of the currently visible area of the contentanimated: Boolean = false: Whether to use animation, the default is false
Tip
Note: centroidContentPoint must be a point on content
Example:
val zoomState: ZoomState by rememberSketchZoomState()
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
val coroutineScope = rememberCoroutineScope()
Button(
onClick = {
coroutineScope.launch {
zoomState.zoomable.scaleByPlus(addScale = 0.2f, animated = true)
}
}
) {
Text(text = "scale + 0.2")
}
Button(
onClick = {
coroutineScope.launch {
zoomState.zoomable.scaleByPlus(addScale = -0.2f, animated = true)
}
}
) {
Text(text = "scale - 0.2")
}
Rubber Band Scale
ZoomImage will limit the zoom factor to between minScale and maxScale. If you zoom beyond this
range with one or two fingers, you can still zoom, but there will be a damping effect similar to a
rubber band, and it will rebound to minScale or maxScale after releasing the finger. This
feature is enabled by default, and you can turn it off with the rubberBandScale property
Example:
val zoomState: ZoomState by rememberSketchZoomState()
zoomState.zoomable.setRubberBandScale(false)
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
Animation
ZoomImage provides animationSpec parameters to modify the duration, Ease, and initial speed of the
scale animation
Example:
val zoomState: ZoomState by rememberSketchZoomState()
val animationSpec = ZoomAnimationSpec(
durationMillis = 500,
easing = LinearOutSlowInEasing,
initialVelocity = 10f
)
zoomState.setAnimationSpec(animationSpec)
// Or modify some parameters based on the default values
zoomState.setAnimationSpec(ZoomAnimationSpec.Default.copy(durationMillis = 500))
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
Public Properties and Methods
// compose
val zoomState: ZoomState by rememberSketchZoomState()
SketchZoomAsyncImage(zoomState = zoomState)
val zoomable: ZoomableState = zoomState.zoomable
// view
val sketchZoomImageView = SketchZoomImageView(context)
val zoomable: ZoomableEngine = sketchZoomImageView.zoomable
Tip
Note: The relevant properties of the view version are wrapped in StateFlow, so its name is suffixed with State compared to the compose version
Readable properties:
zoomable.contentScale: ContentScale: The default scaling method of content is ContentScale.Fitzoomable.readMode: ReadMode?: Reading mode configuration, default is nullzoomable.scalesCalculator: ScalesCalculator: The minScale, mediumScale, and maxScale calculators, the default is ScalesCalculator.Dynamiczoomable.threeStepScale: Boolean: Whether to zoom in between minScale, mediumScale and maxScale when double-clicking to zoom, default is falsezoomable.rubberBandScale: Boolean: Whether to enable the rubber band effect, the default is truezoomable.oneFingerScaleSpec: OneFingerScaleSpec: Single-referential scaling configuration, default is OneFingerScaleSpec.Defaultzoomable.animationSpec: ZoomAnimationSpec: Animation configurations such as zoom, offset, etc., default is ZoomAnimationSpec.Defaultzoomable.disabledGestureTypes: Int: Configure the disabled gesture type, the default is 0 (no gesture is disabled), and multiple gesture types can be combined using the bits or actions of GestureTypezoomable.reverseMouseWheelScale: Boolean: Whether to reverse the direction of the mouse wheel, the default is falsezoomable.mouseWheelScaleCalculator: MouseWheelScaleCalculator: Mouse wheel zoom calculator, the default is MouseWheelScaleCalculator.Defaultzoomable.transform.scale: ScaleFactor: Current scaling (baseTransform.scale * userTransform.scale)zoomable.baseTransform.scale: ScaleFactor: The current underlying scale, affected by the contentScale and alignment parameterzoomable.userTransform.scale: ScaleFactor: The current user scaling factor is affected by scale(), locate(), user gesture scale, double-click and other operationszoomable.minScale: Float: Minimum scale factor, for limits the final scale factor, and as a target value for one of when switch scalezoomable.mediumScale: Float: Medium scale factor, only as a target value for one of when switch scalezoomable.maxScale: Float: Maximum scale factor, for limits the final scale factor, and as a target value for one of when switch scalezoomable.continuousTransformType: Int: If true, a transformation is currently in progress, possibly in a continuous gesture operation, or an animation is in progresszoomable.contentBaseDisplayRectF: Rect: The content region in the container after the baseTransform transformationzoomable.contentBaseDisplayRect: IntRect: The content region in the container after the baseTransform transformationzoomable.contentBaseVisibleRectF: Rect: The content is visible region to the user after the baseTransform transformationzoomable.contentBaseVisibleRect: IntRect: The content is visible region to the user after the baseTransform transformationzoomable.contentDisplayRectF: Rect: The content region in the container after the final transform transformationzoomable.contentDisplayRect: IntRect: The content region in the container after the final transform transformationzoomable.contentVisibleRectF: Rect: The content is visible region to the user after the final transform transformationzoomable.contentVisibleRect: IntRect: The content is visible region to the user after the final transform transformationzoomable.sourceVisibleRectF: Rect: contentVisibleRect maps to the area on the original imagezoomable.sourceVisibleRect: IntRect: contentVisibleRect maps to the area on the original imagezoomable.sourceScaleFactor: ScaleFactor: Scaling ratio based on the original image
Interactive methods:
zoomable.setReadMode(ReadMode?): Setting up reading mode configurationzoomable.setScalesCalculator(ScalesCalculator): Set minScale, mediumScale, and maxScale calculatorzoomable.setThreeStepScale(Boolean): Set whether to cyclically scale between minScale, mediumScale, and maxScale when double-clicking to zoomzoomable.setRubberBandScale(Boolean): Set whether to use rubber band effect after scaling exceeds minScale or maxScalezoomable.setOneFingerScaleSpec(OneFingerScaleSpec): Set single finger zoom configurationzoomable.setAnimationSpec(ZoomAnimationSpec): Set animation configurations such as zoom, offset, etc.zoomable.setDisabledGestureTypes(Int): Set the disabled gesture type, you can use the bits or actions of GestureType to combine multiple gesture typeszoomable.setReverseMouseWheelScale(Boolean): Set whether to reverse the direction of the mouse wheelzoomable.setMouseWheelScaleCalculator(MouseWheelScaleCalculator): Setting up the mouse wheel zoom calculatorzoomable.scale(): Scaling content to the specified multiplezoomable.scaleBy(): Incrementally scale the multiple specified by content by multiplicationzoomable.scaleByPlus(): Incrementally scale content specified multiples by additionzoomable.switchScale(): Switch the scaling multiple of the content, loop between minScale and mediumScale by default, if threeStepScale is true, loop between minScale, mediumScale and maxScalezoomable.getNextStepScale(): Float: Get the next scaling multiple, loop between minScale and mediumScale by default, if threeStepScale is true, loop between minScale, mediumScale and maxScalezoomable.touchPointToContentPoint(): IntOffset: Convert the touch point to a point on the content, the origin is the upper left corner of the contentzoomable.touchPointToContentPointF(): Offset: Convert the touch point to a point on the content, the origin is the upper left corner of the contentzoomable.sourceToDraw(Offset): Offset: Convert the points on the original image to the points at the time of drawing, the origin is the upper left corner of the containerzoomable.sourceToDraw(Rect): Rect: Convert the rectangle on the original image to the rectangle when drawing, the origin is the upper left corner of the container
Listen property changed
- The relevant properties of the compose version are wrapped in State and can be read directly in the Composable function to implement listening
- The relevant properties of the view are wrapped in StateFlow, and its collect function can be called to implement the listening