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.
- Support One-Finger Scale, Two-Finger Scale, Double-click Scale, Mouse Wheel Scale, Keyboard Scale and scaling to a specified multiple by the scale() 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 spring back 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
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.contentScaleState.value = ContentScaleCompat.None
sketchZoomImageView.zoomable.alignmentState.value = AlignmentCompat.BottomEnd
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).scaleX
mediumScale
: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 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 rememberZoomState()
LaunchEffect(zoomState.zommable) {
zoomState.zoomable.scalesCalculator = ScalesCalculator.Fixed
// or
zoomState.zoomable.scalesCalculator = MyScalesCalculator()
}
SketchZoomAsyncImage(
imageUri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
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 rememberZoomState()
LaunchEffect(zoomState.zoomable) {
zoomState.zoomable.disabledGestureTypes =
zoomState.zoomable.disabledGestureTypes or GestureType.TWO_FINGER_SCALE
}
SketchZoomAsyncImage(
imageUri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
ZoomImage supports double-clicking the image to switch the scale factor
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 rememberZoomState()
LaunchEffect(zoomState.zommable) {
zoomState.zoomable.threeStepScale = true
}
SketchZoomAsyncImage(
imageUri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
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 rememberZoomState()
SketchZoomAsyncImage(
imageUri = "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")
}
You can also call the getNextStepScale()
method to get the next scale multiplier
example:
val zoomState: ZoomState by rememberZoomState()
zoomState.zoomable.getNextStepScale()
The double-click scale feature is on by default, but you can turn it off as follows:
val zoomState: ZoomState by rememberZoomState()
LaunchEffect(zoomState.zoomable) {
zoomState.zoomable.disabledGestureTypes =
zoomState.zoomable.disabledGestureTypes or GestureType.DOUBLE_TAP_SCALE
}
SketchZoomAsyncImage(
imageUri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
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 rememberZoomState()
LaunchEffect(zoomState.zoomable) {
zoomState.zoomable.disabledGestureTypes =
zoomState.zoomable.disabledGestureTypes or GestureType.ONE_FINGER_SCALE
}
SketchZoomAsyncImage(
imageUri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
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 rememberZoomState()
LaunchEffect(zoomState.zoomable) {
zoomState.zoomable.reverseMouseWheelScale = true
}
SketchZoomAsyncImage(
imageUri = "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 rememberZoomState()
LaunchEffect(zoomState.zoomable) {
zoomState.zoomable.disabledGestureTypes =
zoomState.zoomable.disabledGestureTypes or GestureType.MOUSE_WHEEL_SCALE
}
SketchZoomAsyncImage(
imageUri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
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 rememberZoomState()
LaunchEffect(zoomState.zoomable) {
zoomState.zoomable.disabledGestureTypes =
zoomState.zoomable.disabledGestureTypes or GestureType.KEYBOARD_SCALE
}
SketchZoomAsyncImage(
imageUri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
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 rememberZoomState()
SketchZoomAsyncImage(
imageUri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
val coroutineScope = rememberCoroutineScope()
Button(
onClick = {
coroutineScope.launch {
val targetScale = zoomState.zoomable.transform.scaleX + 0.2f
zoomState.zoomable.scale(targetScale = targetScale, animated = true)
}
}
) {
Text(text = "scale + 0.2")
}
Button(
onClick = {
coroutineScope.launch {
val targetScale = zoomState.zoomable.transform.scaleX - 0.2f
zoomState.zoomable.scale(targetScale = targetScale, animated = true)
}
}
) {
Text(text = "scale - 0.2")
}
ZoomImage 会将缩放倍数限制在 minScale
和 maxScale
之间,单指或双指缩放时如果超过了这个范围依然可以继续缩放,
但会有类似橡皮筋的阻尼效果,松手后会回弹到 minScale
或 maxScale
,此功能默认开启,你可通过 rubberBandScale
属性关闭它
example:
val zoomState: ZoomState by rememberZoomState()
LaunchEffect(zoomState.zommable) {
zoomState.zoomable.rubberBandScale = false
}
SketchZoomAsyncImage(
imageUri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
ZoomImage provides animationSpec
parameters to modify the duration, Ease, and initial speed of the
scale animation
example:
val zoomState: ZoomState by rememberZoomState()
LaunchEffect(zoomState.zommable) {
zoomState.animationSpec = ZoomAnimationSpec(
durationMillis = 500,
easing = LinearOutSlowInEasing,
initialVelocity = 10f
)
// Or modify some parameters based on the default values
zoomState.animationSpec = ZoomAnimationSpec.Default.copy(durationMillis = 500)
}
SketchZoomAsyncImage(
imageUri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
// compose
val zoomState: ZoomState by rememberZoomState()
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
zoomable.transform.scale: ScaleFactor
: Current scaling (baseTransform.scale * userTransform.scale)zoomable.baseTransform.scale: ScaleFactor
: The current underlying scale, affected by the contentScale 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 scale
- 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