scale.zh.md
August 8, 2025 · View on GitHub
缩放
翻译:English
Tip
- 以下示例优先用 Compose 版本的组件来演示
- ZoomState.zoomable 等价于 ZoomImageView.zoomable
- ZoomState.subsampling 等价于 ZoomImageView.subsampling
ZoomImage 支持多种方式缩放图片,例如双指缩放、单指缩放、双击缩放、鼠标滚轮缩放、键盘缩放、scale() 等
特点
- 支持单指缩放、双指缩放、双击缩放、 鼠标滚轮缩放、键盘缩放 、以及通过 scale()、scaleBy()、scaleByPlus() 方法缩放到指定的倍数
- 支持橡皮筋效果. 手势连续缩放时(单指/双指缩放)超过最大或最小范围时可以继续缩放,但有阻尼效果,松手后会回弹到最大或最小缩放倍数
- 动态缩放范围. 默认根据 containerSize、contentSize、contentOriginSize 动态计算 mediumScale 和 maxScale
- 支持动画. scale() 方法和双击缩放均支持动画
- 支持全部的 ContentScale, 和 Alignment,ZoomImageView 也支持 ContentScale 和 Alignment
- 禁用手势. 支持分别禁用双击缩放、双指缩放、单指缩放、鼠标滚轮缩放、拖动等手势
- 仅 containerSize 改变时(桌面平台上拖动调整窗口大小),ZoomImage 会保持缩放比例和 content 可见中心点不变
- 页面重建时(屏幕旋转、App 在后台被回收)会重置缩放和偏移
- 支持读取相关信息. 支持读取当前缩放倍数、最小/中间/最大缩放倍数等缩放相关信息
ContentScale, Alignment
ZoomImage 支持所有的 ContentScale 和 Alignment,并且得益于 compose 版本和 view 版本使用的是同一套算法,view 版本的组件在支持 ScaleType 之外也支持 ContentScale 和 Alignment
示例:
val sketchZoomImageView = SketchZoomImageView(context)
sketchZoomImageView.zoomable.setContentScale(ContentScaleCompat.None)
sketchZoomImageView.zoomable.setAlignment(AlignmentCompat.BottomEnd)
minScale, mediumScale, maxScale
ZoomImage 在缩放的过程中始终受 minScale、mediumScale、maxScale 三个参数的控制:
minScale:最小缩放倍数,用于限制 ZoomImage 在缩放过程中的最小值,计算公式为:ContentScale.computeScaleFactor(srcSize, dstSize).scaleXmediumScale:中间缩放倍数,专门用于双击缩放,取值受 scalesCalculator 参数控制maxScale:最大缩放倍数,用于限制 ZoomImage 在缩放过程中的最大值,取值受 scalesCalculator 参数控制
ScalesCalculator
ScalesCalculator 专门用来计算 mediumScale 和 maxScale,ZoomImage 有两个内置的 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 通常由 ReadMode 计算
- multiple 默认值为 3f
- ScalesCalculator.Dynamic:
- mediumScale 计算规则如下:
- 如果 contentScale 是 FillBounds,则始终是 minMediumScale
- 如果 initialScale 大于 minScale 则始终是 initialScale
- 否则在 minMediumScale、fillContainerScale、contentOriginScale 当中取最大的
- maxScale 计算规则如下:
- 如果 contentScale 是 FillBounds,则始终是
mediumScale * multiple - 否则在
mediumScale * multiple, contentOriginScale 当中取最大的
- 如果 contentScale 是 FillBounds,则始终是
- mediumScale 计算规则如下:
- ScalesCalculator.Fixed:
- mediumScale 计算规则如下:
- 如果 contentScale 是 FillBounds,则始终是 minMediumScale
- 如果 initialScale 大于 minScale 则始终是 initialScale
- 否则始终是 minMediumScale
- maxScale 始终是
mediumScale * multiple
- mediumScale 计算规则如下:
scalesCalculator 默认值为 ScalesCalculator.Dynamic,你可以将它修改为 Fixed 或自定义的实现
示例:
val zoomState: ZoomState by rememberSketchZoomState()
zoomState.zoomable.setScalesCalculator(ScalesCalculator.Fixed)
// 或
zoomState.zoomable.setScalesCalculator(MyScalesCalculator())
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
双指缩放
你可以通过双指捏合收拾缩放图像,ZoomImage 会根据双指的距离来计算缩放倍数。双指缩放功能默认开启,但你可以关闭它,如下:
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,
)
双击缩放
ZoomImage 支持双击图像切换缩放倍数
threeStepScale
默认总是在 minScale 和 mediumScale 之间循环,如果你想在 minScale、mediumScale 和 maxScale 之间循环,可以修改 threeStepScale 属性为 true,如下:
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()
双击缩放调用的是 ZoomImage 的 switchScale() 方法,你也可以在需要的时候调用 switchScale()
方法来切换缩放倍数,它有两个参数:
centroidContentPoint: IntOffset = contentVisibleRect.center:content 上的缩放中心点,原点是 content 的左上角,默认是 content 当前可见区域的中心animated: Boolean = false:是否使用动画,默认为 false
Tip
注意:centroidContentPoint 一定要是 content 上的点
示例:
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()
你还可以调用 getNextStepScale() 方法来获取下一个缩放倍数
示例:
val zoomState: ZoomState by rememberSketchZoomState()
zoomState.zoomable.getNextStepScale()
关闭双击缩放
双击缩放功能默认开启,但你可以关闭它,如下:
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,
)
单指缩放
ZoomImage 支持单指缩放图像,双击后按住屏幕上下滑动即可缩放图像。此功能默认开启,但你可以关闭它,如下:
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,
)
鼠标滚轮缩放
ZoomImage 支持通过鼠标滚轮缩放图像,ZoomImage 以当前鼠标位置为缩放中心并根据鼠标滚轮的滚动方向和距离来计算缩放倍数。
你可以通过设置 reverseMouseWheelScale 属性来反转鼠标滚轮缩放,如下:
val zoomState: ZoomState by rememberSketchZoomState()
zoomState.zoomable.setReverseMouseWheelScale(true)
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
鼠标滚轮缩放功能默认开启,但你可以关闭它,如下:
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,
)
你还可以通过 mouseWheelScaleCalculator 属性来自定义鼠标滚轮缩放的计算方式,默认是
MouseWheelScaleCalculator.Default,如下:
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,
)
键盘缩放
ZoomImage 支持通过键盘缩放图像,支持短按和长按两种操作。默认注册了以下按键:
- 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
由于键盘缩放功能必须依赖焦点,而焦点管理又非常复杂,所以默认没有开启它,需要你主动配置并请求焦点,如下:
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
在 HorizontalPager 中请求焦点时需要注意只能为当前页请求焦点,否则会导致意想不到的意外
你还可以通过手势控制动态关闭它,如下:
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 提供了 scale() 方法用来缩放图像到指定的倍数,它有三个参数:
targetScale: Float: 目标缩放倍数centroidContentPoint: IntOffset = contentVisibleRect.center: content 上的缩放中心点,原点是 content 的左上角, 默认是 content 当前可见区域的中心animated: Boolean = false: 是否使用动画,默认为 false
Tip
注意:centroidContentPoint 一定要是 content 上的点
示例:
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 提供了 scaleBy() 方法用来以乘法的方式增量缩放图像到指定的倍数,它有三个参数:
addScale: Float: 增量缩放倍数centroidContentPoint: IntOffset = contentVisibleRect.center: content 上的缩放中心点,原点是 content 的左上角, 默认是 content 当前可见区域的中心animated: Boolean = false: 是否使用动画,默认为 false
Tip
注意:centroidContentPoint 一定要是 content 上的点
示例:
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 提供了 scaleByPlus() 方法用来以加法的方式缩放图像到指定的倍数,它有三个参数:
addScale: Float: 增量缩放倍数centroidContentPoint: IntOffset = contentVisibleRect.center: content 上的缩放中心点,原点是 content 的左上角, 默认是 content 当前可见区域的中心animated: Boolean = false: 是否使用动画,默认为 false
Tip
注意:centroidContentPoint 一定要是 content 上的点
示例:
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")
}
橡皮筋效果
ZoomImage 会将缩放倍数限制在 minScale 和 maxScale之间,单指或双指缩放时如果超过了这个范围依然可以继续缩放,
但会有类似橡皮筋的阻尼效果,松手后会回弹到 minScale或 maxScale
,此功能默认开启,你可通过 rubberBandScale 属性关闭它
示例:
val zoomState: ZoomState by rememberSketchZoomState()
zoomState.zoomable.setRubberBandScale(false)
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
动画
ZoomImage 提供了 animationSpec 参数用来修改缩放动画的时长、Easing 以及初始速度
示例:
val zoomState: ZoomState by rememberSketchZoomState()
val animationSpec = ZoomAnimationSpec(
durationMillis = 500,
easing = LinearOutSlowInEasing,
initialVelocity = 10f
)
zoomState.setAnimationSpec(animationSpec)
// 或者在默认值的基础上修改部分参数
zoomState.setAnimationSpec(ZoomAnimationSpec.Default.copy(durationMillis = 500))
SketchZoomAsyncImage(
uri = "https://sample.com/sample.jpeg",
contentDescription = "view image",
modifier = Modifier.fillMaxSize(),
zoomState = zoomState,
)
可访问属性和方法
// 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
- 注意:view 版本的相关属性用 StateFlow 包装,所以其名字相比 compose 版本都以 State 为后缀
只读属性:
zoomable.contentScale: ContentScale: content 的缩放方式,默认是 ContentScale.Fitzoomable.readMode: ReadMode?: 阅读模式配置,默认是 nullzoomable.scalesCalculator: ScalesCalculator: minScale、mediumScale 和 maxScale 计算器,默认是 ScalesCalculator.Dynamiczoomable.threeStepScale: Boolean: 双击缩放时是否在 minScale、mediumScale 和 maxScale 之间循环缩放,默认是 falsezoomable.rubberBandScale: Boolean: 缩放超出 minScale 或 maxScale 后是否使用橡皮筋效果,默认是 truezoomable.oneFingerScaleSpec: OneFingerScaleSpec: 单指缩放配置,默认是 OneFingerScaleSpec.Defaultzoomable.animationSpec: ZoomAnimationSpec: 缩放、偏移等动画配置,默认是 ZoomAnimationSpec.Defaultzoomable.disabledGestureTypes: Int: 配置禁用的手势类型,默认是 0(不禁用任何手势),可以使用 GestureType 的位或操作来组合多个手势类型zoomable.reverseMouseWheelScale: Boolean: 是否反转鼠标滚轮的方向,默认是 falsezoomable.mouseWheelScaleCalculator: MouseWheelScaleCalculator: 鼠标滚轮缩放计算器,默认是 MouseWheelScaleCalculator.Defaultzoomable.transform.scale: ScaleFactor: 当前缩放比例(baseTransform.scale * userTransform.scale)zoomable.baseTransform.scale: ScaleFactor: 当前基础缩放比例,受 contentScale 和 alignment 参数影响zoomable.userTransform.scale: ScaleFactor: 当前用户缩放比例,受 scale()、用户手势缩放、双击等操作影响zoomable.minScale: Float: 最小缩放比例,用于缩放时限制最小缩放比例以及双击缩放时的一个循环缩放比例zoomable.mediumScale: Float: 中间缩放比例,用于双击缩放时的一个循环缩放比例zoomable.maxScale: Float: 最大缩放比例,用于缩放时限制最大缩放比例以及双击缩放时的一个循环缩放比例zoomable.continuousTransformType: Int: 当前正在进行的连续变换的类型zoomable.contentBaseDisplayRectF: Rect: content 经过 baseTransform 变换后在 container 中的区域zoomable.contentBaseDisplayRect: IntRect: content 经过 baseTransform 变换后在 container 中的区域zoomable.contentBaseVisibleRectF: Rect: content 经过 baseTransform 变换后自身对用户可见的区域zoomable.contentBaseVisibleRect: IntRect: content 经过 baseTransform 变换后自身对用户可见的区域zoomable.contentDisplayRectF: Rect: content 经过 transform 变换后在 container 中的区域zoomable.contentDisplayRect: IntRect: content 经过 transform 变换后在 container 中的区域zoomable.contentVisibleRectF: Rect: content 经过 transform 变换后自身对用户可见的区域zoomable.contentVisibleRect: IntRect: content 经过 transform 变换后自身对用户可见的区域zoomable.sourceVisibleRectF: Rect: contentVisibleRect 映射到原图上的区域zoomable.sourceVisibleRect: IntRect: contentVisibleRect 映射到原图上的区域zoomable.sourceScaleFactor: ScaleFactor: 以原图为基准的缩放比例
交互方法:
zoomable.setReadMode(ReadMode?): 设置阅读模式配置zoomable.setScalesCalculator(ScalesCalculator): 设置 minScale、mediumScale 和 maxScale 的计算器zoomable.setThreeStepScale(Boolean): 设置双击缩放时是否在 minScale、mediumScale 和 maxScale 之间循环缩放zoomable.setRubberBandScale(Boolean): 设置缩放超出 minScale 或 maxScale 后是否使用橡皮筋效果zoomable.setOneFingerScaleSpec(OneFingerScaleSpec): 设置单指缩放配置zoomable.setAnimationSpec(ZoomAnimationSpec): 设置缩放、偏移等动画配置zoomable.setDisabledGestureTypes(Int): 设置禁用的手势类型,可以使用 GestureType 的位或操作来组合多个手势类型zoomable.setReverseMouseWheelScale(Boolean): 设置是否反转鼠标滚轮的方向zoomable.setMouseWheelScaleCalculator(MouseWheelScaleCalculator): 设置鼠标滚轮缩放计算器zoomable.scale(): 缩放 content 到指定的倍数zoomable.scaleBy(): 以乘法的方式增量缩放 content 指定的倍数zoomable.scaleByPlus(): 以加法的方式增量缩放 content 指定的倍数zoomable.switchScale(): 切换 content 的缩放倍数,默认在 minScale 和 mediumScale 之间循环, 如果 threeStepScale 为 true 则在 minScale、mediumScale 和 maxScale 之间循环zoomable.getNextStepScale(): Float: 获取下一个缩放倍数,默认在 minScale 和 mediumScale 之间循环, 如果 threeStepScale 为 true 则在 minScale、mediumScale 和 maxScale 之间循环zoomable.touchPointToContentPoint(): IntOffset: 将触摸点转换为 content 上的点,原点是 content 的左上角zoomable.touchPointToContentPointF(): Offset: 将触摸点转换为 content 上的点,原点是 content 的左上角zoomable.sourceToDraw(Offset): Offset: 将原图上的点转换为绘制时的点,原点是 container 的左上角zoomable.sourceToDraw(Rect): Rect: 将原图上的矩形转换为绘制时的矩形,原点是 container 的左上角
监听属性变化
- compose 版本的相关属性是用 State 包装的,在 Composable 函数中直接读取它即可实现监听
- view 的相关属性是用 StateFlow 包装,调用其 collect 函数即可实现监听