offset.zh.md

August 8, 2025 · View on GitHub

偏移

翻译:English

Tip

ZoomImage 支持单指拖动、惯性滑动、键盘拖动,以及 offset() 方法来移动图像。

单指拖动

ZoomImage 默认开启单指拖动手势,但你可以关闭它,如下:

val zoomState: ZoomState by rememberSketchZoomState()

zoomState.zoomable.setDisabledGestureTypes(
    zoomState.zoomable.disabledGestureTypes or GestureType.ONE_FINGER_DRAG
)

SketchZoomAsyncImage(
    uri = "https://sample.com/sample.jpeg",
    contentDescription = "view image",
    modifier = Modifier.fillMaxSize(),
    zoomState = zoomState,
)

键盘拖动

ZoomImage 支持通过键盘拖动图像,支持短按和长按两种操作。默认注册了以下按键:

  • move up: Key.DirectionUp
  • move down: Key.DirectionDown
  • move left: Key.DirectionLeft
  • move right: Key.DirectionRight

由于键盘拖动功能必须依赖焦点,而焦点管理又非常复杂,所以默认没有开启它,需要你主动配置并请求焦点,如下:

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_DRAG
)

SketchZoomAsyncImage(
    uri = "https://sample.com/sample.jpeg",
    contentDescription = "view image",
    modifier = Modifier.fillMaxSize(),
    zoomState = zoomState,
)

offset()

ZoomImage 提供了 offset() 方法用来移动图像到指定位置,它有两个参数:

  • targetOffset: Offset: 目标偏移位置,offset 原点是组件的左上角
  • animated: Boolean = false: 是否使用动画,默认为 false

示例:

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 {
            val targetOffset = zoomState.zoomable.transform.offset + Offset(x = 100, y = 200)
            zoomState.zoomable.offset(targetOffset = targetOffset, animated = true)
        }
    }
) {
    Text(text = "offset + Offset(100, 200)")
}

Button(
    onClick = {
        coroutineScope.launch {
            val targetOffset = zoomState.zoomable.transform.offset - Offset(x = 100, y = 200)
            zoomState.zoomable.offset(targetOffset = targetOffset, animated = true)
        }
    }
) {
    Text(text = "offset - Offset(100, 200)")
}

限制偏移边界

ZoomImage 默认不管你设置的是什么 ContentScale 都可以拖动查看图像的全部内容,例如你设置了 ContentScale 为 Crop,Alignment 为 Center,那么默认只显示图像中间的部分,然后你还可以单指或双指拖动来查看图像的全部内容

如果你希望图像只能在 ContentScaleAlignment 所限制的区域内移动,不能查看全部内容,这时你可以修改 limitOffsetWithinBaseVisibleRect 参数为 true 来达到此目的

示例:

val zoomState: ZoomState by rememberSketchZoomState()

zoomState.zoomable.setLimitOffsetWithinBaseVisibleRect(true)

SketchZoomAsyncImage(
    uri = "https://sample.com/sample.jpeg",
    contentDescription = "view image",
    modifier = Modifier.fillMaxSize(),
    zoomState = zoomState,
)

容器空白区域

ZoomImage 默认在拖动图像时图像的边缘始终和容器的边缘对齐,它们中间不会存在空白区域(图像初始状态时除外),当你需要在图像和容器之间留有空白区域时,你可以通过设置 containerWhitespacecontainerWhitespaceMultiple 参数为来达到此目的

示例:

val zoomState: ZoomState by rememberSketchZoomState()

// 通过 containerWhitespace 属性设置具体的大小
zoomState.zoomable.setContainerWhitespace(
    ContainerWhitespace(left = 4f, top = 3f, right = 2f, bottom = 1f)
)
// 或
zoomState.zoomable.setContainerWhitespace(ContainerWhitespace(horizontal = 2f, vertical = 1f))
// 或
zoomState.zoomable.setContainerWhitespace(ContainerWhitespace(size = 1f))

// 在图像边缘和容器边缘之间留有 50% 容器大小的空白区域
zoomState.zoomable.setContainerWhitespaceMultiple(0.5f)

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.alignment: Alignment: content 在 container 中的对齐方式,默认是 Alignment.TopStart
  • zoomable.limitOffsetWithinBaseVisibleRect: Boolean: 是否将偏移限制在 contentBaseVisibleRect 内,默认是 false
  • zoomable.containerWhitespaceMultiple: Float: 基于容器尺寸的倍数为容器四周添加空白区域,默认是 0f
  • zoomable.containerWhitespace: ContainerWhitespace: 容器四周空白区域的配置,优先级高于 containerWhitespaceMultiple,默认是 ContainerWhitespace.Zero
  • zoomable.disabledGestureTypes: Int: 配置禁用的手势类型,默认是 0(不禁用任何手势),可以使用 GestureType 的位或操作来组合多个手势类型
  • zoomable.transform.offset: Offset: 当前偏移量(baseTransform.offset + userTransform.offset)
  • zoomable.baseTransform.offset: Offset: 当前基础偏移量,受 alignment 参数和 rotate 方法影响
  • zoomable.userTransform.offset: Offset: 当前用户偏移量,受 offset()、locate() 以及用户手势拖动影响
  • 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.scrollEdge: ScrollEdge: 当前偏移的边界状态

交互方法:

  • zoomable.setLimitOffsetWithinBaseVisibleRect(Boolean): 设置是否将偏移限制在 contentBaseVisibleRect 内
  • zoomable.setContainerWhitespaceMultiple(Float): 设置基于容器尺寸的倍数为容器四周添加空白区域
  • zoomable.setContainerWhitespace(ContainerWhitespace): 设置容器四周空白区域的配置,优先级高于 containerWhitespaceMultiple
  • zoomable.setDisabledGestureTypes(Int): 设置禁用的手势类型,可以使用 GestureType 的位或操作来组合多个手势类型
  • zoomable.offset(): 偏移 content 到指定的位置
  • zoomable.offsetBy(): 以增量的方式偏移 content 指定的偏移量
  • zoomable.touchPointToContentPoint(): IntOffset: 将触摸点转换为 content 上的点,原点是 content 的左上角
  • zoomable.touchPointToContentPointF(): Offset: 将触摸点转换为 content 上的点,原点是 content 的左上角
  • zoomable.sourceToDraw(Offset): Offset: 将原图上的点转换为绘制时的点,原点是 container 的左上角
  • zoomable.sourceToDraw(Rect): Rect: 将原图上的矩形转换为绘制时的矩形,原点是 container 的左上角
  • zoomable.canScroll(): Boolean: 判断当前 content 在指定方向上是否可以滚动

监听属性变化

  • compose 版本的相关属性是用 State 包装的,在 Composable 函数中直接读取它即可实现监听
  • view 的相关属性是用 StateFlow 包装,调用其 collect 函数即可实现监听