当前位置: 首页 > 知识库问答 >
问题:

在jetpack compose中具有更好的缩放性能

裴畅
2023-03-14

合成文档中所述的默认缩放行为会干扰拖动手势,并围绕可缩放对象的中心(而不是手指)旋转和缩放

有更好的方法吗?

共有2个答案

公孙宇
2023-03-14

这是一个非常简单的可缩放图像。

@Composable
fun ZoomableImage() {
    var scale by remember { mutableStateOf(1f) }
    var offset by remember { mutableStateOf(Offset.Zero) }

    Box(
        Modifier
            .size(600.dp)
    ) {
        Image(
            painter = rememberImagePainter(data = "https://picsum.photos/600/600"),
            contentDescription = "A Content description",
            modifier = Modifier
                .align(Alignment.Center)
                .graphicsLayer(
                    scaleX = scale,
                    scaleY = scale,
                    translationX = if (scale > 1f) offset.x else 0f,
                    translationY = if (scale > 1f) offset.y else 0f
                )
                .pointerInput(Unit) {
                    detectTransformGestures(
                        onGesture = { _, pan: Offset, zoom: Float, _ ->
                            offset += pan
                            scale = (scale * zoom).coerceIn(0.5f, 4f)
                        }
                    )
                }
        )
    }
}

仅支持缩放和平移。不支持旋转和双击。要获得稍微平滑的平移,您可以将小乘数应用于pan,例如:

offset += pan * 1.5f

我还添加了强制输入,以避免在边界看起来很奇怪之前放大/缩小。如果需要,可以随意删除强制输入。也可以删除包含的框对齐。仅当我们之前缩放过时,才会应用平移。这看起来更自然。

欢迎反馈和改进

许高峻
2023-03-14

我将此解决方案中的代码制作成一个库:de.mr-pine.utils:zoomables

您必须将指针输入范围与检测变形几何图形一起使用,并将此功能用作您的 onGesture:

fun onTransformGesture(
    centroid: Offset,
    pan: Offset,
    zoom: Float,
    transformRotation: Float
) {
    offset += pan
    scale *= zoom
    rotation += transformRotation

    val x0 = centroid.x - imageCenter.x
    val y0 = centroid.y - imageCenter.y

    val hyp0 = sqrt(x0 * x0 + y0 * y0)
    val hyp1 = zoom * hyp0 * (if (x0 > 0) {
        1f
    } else {
        -1f
    })

    val alpha0 = atan(y0 / x0)

    val alpha1 = alpha0 + (transformRotation * ((2 * PI) / 360))

    val x1 = cos(alpha1) * hyp1
    val y1 = sin(alpha1) * hyp1

    transformOffset =
        centroid - (imageCenter - offset) - Offset(x1.toFloat(), y1.toFloat())
    offset = transformOffset
}

以下是如何围绕触摸输入旋转/缩放的示例,该示例还支持滑动和双击以重置缩放:

val scope = rememberCoroutineScope()

var scale by remember { mutableStateOf(1f) }
var rotation by remember { mutableStateOf(0f) }
var offset by remember { mutableStateOf(Offset.Zero) }
val state = rememberTransformableState { zoomChange, offsetChange, rotationChange ->
    scale *= zoomChange
    rotation += rotationChange
    offset += offsetChange
}

var dragOffset by remember { mutableStateOf(Offset.Zero) }
var imageCenter by remember { mutableStateOf(Offset.Zero) }
var transformOffset by remember { mutableStateOf(Offset.Zero) }


Box(
    Modifier
        .pointerInput(Unit) {
            detectTapGestures(
                onDoubleTap = {
                    if (scale != 1f) {
                        scope.launch {
                            state.animateZoomBy(1 / scale)
                        }
                        offset = Offset.Zero
                        rotation = 0f
                    } else {
                        scope.launch {
                            state.animateZoomBy(2f)
                        }
                    }
                }
            )
        }
        .pointerInput(Unit) {
            val panZoomLock = true
            forEachGesture {
                awaitPointerEventScope {
                    var transformRotation = 0f
                    var zoom = 1f
                    var pan = Offset.Zero
                    var pastTouchSlop = false
                    val touchSlop = viewConfiguration.touchSlop
                    var lockedToPanZoom = false
                    var drag: PointerInputChange?
                    var overSlop = Offset.Zero

                    val down = awaitFirstDown(requireUnconsumed = false)


                    var transformEventCounter = 0
                    do {
                        val event = awaitPointerEvent()
                        val canceled = event.changes.fastAny { it.positionChangeConsumed() }
                        var relevant = true
                        if (event.changes.size > 1) {
                            if (!canceled) {
                                val zoomChange = event.calculateZoom()
                                val rotationChange = event.calculateRotation()
                                val panChange = event.calculatePan()

                                if (!pastTouchSlop) {
                                    zoom *= zoomChange
                                    transformRotation += rotationChange
                                    pan += panChange

                                    val centroidSize =
                                        event.calculateCentroidSize(useCurrent = false)
                                    val zoomMotion = abs(1 - zoom) * centroidSize
                                    val rotationMotion =
                                        abs(transformRotation * PI.toFloat() * centroidSize / 180f)
                                    val panMotion = pan.getDistance()

                                    if (zoomMotion > touchSlop ||
                                        rotationMotion > touchSlop ||
                                        panMotion > touchSlop
                                    ) {
                                        pastTouchSlop = true
                                        lockedToPanZoom =
                                            panZoomLock && rotationMotion < touchSlop
                                    }
                                }

                                if (pastTouchSlop) {
                                    val eventCentroid = event.calculateCentroid(useCurrent = false)
                                    val effectiveRotation =
                                        if (lockedToPanZoom) 0f else rotationChange
                                    if (effectiveRotation != 0f ||
                                        zoomChange != 1f ||
                                        panChange != Offset.Zero
                                    ) {
                                        onTransformGesture(
                                            eventCentroid,
                                            panChange,
                                            zoomChange,
                                            effectiveRotation
                                        )
                                    }
                                    event.changes.fastForEach {
                                        if (it.positionChanged()) {
                                            it.consumeAllChanges()
                                        }
                                    }
                                }
                            }
                        } else if (transformEventCounter > 3) relevant = false
                        transformEventCounter++
                    } while (!canceled && event.changes.fastAny { it.pressed } && relevant)

                    do {
                        val event = awaitPointerEvent()
                        drag = awaitTouchSlopOrCancellation(down.id) { change, over ->
                            change.consumePositionChange()
                            overSlop = over
                        }
                    } while (drag != null && !drag.positionChangeConsumed())
                    if (drag != null) {
                        dragOffset = Offset.Zero
                        if (scale !in 0.92f..1.08f) {
                            offset += overSlop
                        } else {
                            dragOffset += overSlop
                        }
                        if (drag(drag.id) {
                                if (scale !in 0.92f..1.08f) {
                                    offset += it.positionChange()
                                } else {
                                    dragOffset += it.positionChange()
                                }
                                it.consumePositionChange()
                            }
                        ) {
                            if (scale in 0.92f..1.08f) {
                                val offsetX = dragOffset.x
                                if (offsetX > 300) {
                                    onSwipeRight()

                                } else if (offsetX < -300) {
                                    onSwipeLeft()
                                }
                            }
                        }
                    }
                }
            }
        }
) {
    ZoomComposable(
        modifier = Modifier
            .offset { IntOffset(offset.x.roundToInt(), offset.y.roundToInt()) }
            .graphicsLayer(
                scaleX = scale - 0.02f,
                scaleY = scale - 0.02f,
                rotationZ = rotation
            )
            .onGloballyPositioned { coordinates ->
                val localOffset =
                    Offset(
                        coordinates.size.width.toFloat() / 2,
                        coordinates.size.height.toFloat() / 2
                    )
                val windowOffset = coordinates.localToWindow(localOffset)
                imageCenter = coordinates.parentLayoutCoordinates?.windowToLocal(windowOffset)
                    ?: Offset.Zero
            }
    )
}
 类似资料:
  • 问题内容: 考虑以下两行代码 和 在性能上,以上两个语句有什么区别吗?我见过很多人使用后者,当被问及他们说这是最佳实践时,没有充分的理由。 问题答案: 没有不同。 第二个原因仅仅是因为C / C ++程序员总是执行分配而不是比较。 例如 而java编译器会生成编译错误。 因此,由于可读性强,我个人更喜欢第一个,人们倾向于从左到右阅读,而不是。

  • 问题内容: String s = “”; for(i=0;i<....){ s = some Assignment; } 要么 我不需要在循环外再次使用“ s”。第一个选项可能更好,因为不会每次都初始化一个新的String。但是,第二个结果将导致变量的范围仅限于循环本身。 编辑:回应米尔豪斯的回答。在循环中将String分配给常量是没有意义的吗?不,这里的“某些分配”是指从要迭代的列表中获得的变化

  • 问题内容: 有时我们可以同时使用派生表和临时表编写查询。我的问题是哪个更好?为什么? 问题答案: 派生表是一种逻辑构造。 可以将其存储在中,在运行时通过在每次访问时重新评估基础语句来构建,甚至可以对其进行优化。 临时表是一种物理构造。它是在其中创建的表,并在其中填充了值。 哪种更好取决于查询所使用的查询,用于派生表的语句以及许多其他因素。 例如,每次使用时都可以(并且很可能会)重新评估in中的(公

  • 问题内容: 如何使用https://www.amcharts.com/demos/line-chart-with-scroll-and- zoom/ 这样的图表 我对这些功能特别感兴趣 为了能够使用这两个选择控件选择域轴窗口。 为了能够通过选择部分域轴进行缩放。 为了能够缩小并查看大图。 为了能够映射到范围轴(Y)并在任意点(无标记)像工具提示一样查看该气球上的值 我在这里尝试了可滚动的JFree

  • 如何拥有像https://www.amcharts.com/demos/line-chart-with-scroll-and-zoom/这样的图表 null 我在这里做了初步尝试,可滚动JFree域轴和自定义标记标签,在垃圾上帝的帮助下,我使域可滚动。 我仍然缺少这些功能,以便能够有效地使用graph。

  • 问题内容: 我目前正在使用JFreeChart显示框线图的基于Java的项目。 我的问题是如何显示包含带有大约20个类别和5个以上系列的CategoryDataset的箱形图的图表。 当前,如果未设置ChartPanel的首选大小,则图例,标签和注释可读取,但Boxplots太小。或设置ChartPanel的大小,以使Boxplots具有可接受的大小,然后将图例,标签和注释水平拉伸。 我的问题是,