offset.md

August 8, 2025 · View on GitHub

Offset

Translations: 简体中文

Tip

ZoomImage supports one-finger drag, inertial swipe, keyboard drag, and the offset() method to move the image.

One Finger Drag

ZoomImage enables one finger drag gestures 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_DRAG
)

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

Keyboard drag

ZoomImage supports drag images through the keyboard, supports both short press and long press operations. And the following keys are registered by default:

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

Since the keyboard drag 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_DRAG
)

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

offset()

ZoomImage provides a modified offset() method to move the image to a specified position, which has two parameters:

  • targetOffset: Offset: The target offset, with the offset origin being the upper-left corner of the component
  • animated: Boolean = false: Whether to use animation, the default is false

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 {
            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)")
}

Limit the bounds of offset

By default, zoomImage can drag to view the entire content of the image regardless of what you set ContentScale, for example, if you set ContentScale to Crop and Alignment to Center, then only the middle part of the image is displayed by default, and then you can also drag with one or two fingers to view the entire content of the image

If you want the image to be moved only within the area restricted by ContentScale and Alignment, and not the entire content, you can modify the limitOffsetWithinBaseVisibleRect parameter to true to achieve this

Example:

val zoomState: ZoomState by rememberSketchZoomState()

zoomState.zoomable.setLimitOffsetWithinBaseVisibleRect(true)

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

Container Whitespace

By default, ZoomImage always aligns the edge of the image with the edge of the container when dragging the image, and there will be no white space between them (except in the initial state of the image). When you need to leave a white space between the image and the container, you can pass To achieve this, set the containerWhitespace or containerWhitespaceMultiple parameter to

Example:

val zoomState: ZoomState by rememberSketchZoomState()

// Set the specific size through the containerWhitespace property
zoomState.zoomable.setContainerWhitespace(
  ContainerWhitespace(left = 4f, top = 3f, right = 2f, bottom = 1f)
)
// or
zoomState.zoomable.setContainerWhitespace(ContainerWhitespace(horizontal = 2f, vertical = 1f))
// or
zoomState.zoomable.setContainerWhitespace(ContainerWhitespace(size = 1f))

// Leave 50% of the container size white space between the edge of the image and the edge of the container
zoomState.zoomable.setContainerWhitespaceMultiple(0.5f)

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

Public Properties

// 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.alignment: Alignment: The alignment of content in container is Alignment.TopStart by default
  • zoomable.limitOffsetWithinBaseVisibleRect: Boolean: Whether to limit the offset to contentBaseVisibleRect, the default is false
  • zoomable.containerWhitespaceMultiple: Float: Add blank space around the container based on multiples of the container size, the default is 0f
  • zoomable.containerWhitespace: ContainerWhitespace: The configuration of blank areas around the container has higher priority than containerWhitespaceMultiple, and the default is ContainerWhitespace.Zero
  • zoomable.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 GestureType
  • zoomable.transform.offset: Offset: Current offset (baseTransform.offset + userTransform.offset)
  • zoomable.baseTransform.offset: Offset: The current base offset, affected by the alignment parameter and the rotate method
  • zoomable.userTransform.offset: Offset: The current user offset, affected by offset(), locate(), and user gesture dragging
  • zoomable.contentBaseDisplayRectF: Rect: The content region in the container after the baseTransform transformation
  • zoomable.contentBaseDisplayRect: IntRect: The content region in the container after the baseTransform transformation
  • zoomable.contentBaseVisibleRectF: Rect: The content is visible region to the user after the baseTransform transformation
  • zoomable.contentBaseVisibleRect: IntRect: The content is visible region to the user after the baseTransform transformation
  • zoomable.contentDisplayRectF: Rect: The content region in the container after the final transform transformation
  • zoomable.contentDisplayRect: IntRect: The content region in the container after the final transform transformation
  • zoomable.contentVisibleRectF: Rect: The content is visible region to the user after the final transform transformation
  • zoomable.contentVisibleRect: IntRect: The content is visible region to the user after the final transform transformation
  • zoomable.sourceVisibleRectF: Rect: contentVisibleRect maps to the area on the original image
  • zoomable.sourceVisibleRect: IntRect: contentVisibleRect maps to the area on the original image
  • zoomable.scrollEdge: ScrollEdge: Edge state for the current offset

Interactive methods:

  • zoomable.setLimitOffsetWithinBaseVisibleRect(Boolean): Set whether to limit offsets to contentBaseVisibleRect
  • zoomable.setContainerWhitespaceMultiple(Float): Set multiples based on container size to add blank areas around the container
  • zoomable.setContainerWhitespace(ContainerWhitespace): Set the configuration of blank areas around the container, with priority higher than containerWhitespaceMultiple
  • zoomable.setDisabledGestureTypes(Int): Set the disabled gesture type, you can use the bits or actions of GestureType to combine multiple gesture types
  • zoomable.offset(): Offset content to the specified location
  • zoomable.offsetBy(): Offset as incremental content specified offset
  • zoomable.touchPointToContentPoint(): IntOffset: Convert the touch point to a point on the content, the origin is the upper left corner of the content
  • zoomable.touchPointToContentPointF(): Offset: Convert the touch point to a point on the content, the origin is the upper left corner of the content
  • zoomable.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 container
  • zoomable.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
  • zoomable.canScroll(): Boolean: Determine whether the current content can scroll in the specified direction

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