Android Jetpack Composable 绘制大型坐标列表

Android Jetpack Composable Draw Large List of coordinates

我有一个包含 36000 个坐标的列表,我需要在 canvas 上绘制为点。坐标不断变化,所以我将它们保存在地图中作为我的视图模型的一部分

mutableStateOf(HashMap<Int, AtomicInteger>()) 并且 AtomicIntegers 正在不断更新。

地图包含角度和距离,我在 canvas 上将其转换为坐标。这可行,但我的问题是这是否是绘制它的最有效方法 - 连续遍历地图似乎效率低下

@Composable
fun lidarComposable(vm: MainViewModel) {
    MaterialTheme {
        Column {
            Canvas(modifier = Modifier.size(500.dp, 500.dp)) {
                drawRect(Color.LightGray, topLeft = Offset(0f, 0f), size = Size(this.size.width, this.size.height))
                drawCircle(Color.Blue, 20F, this.center)
                vm.lidardata.data.forEach { (a, d) ->
                        val angle: Double = a.toDouble() / Lidar.relevence
                        val distance: Double = (d.get() / Lidar.relevence).toDouble()
                        val fixed = angle - vm.compass
                        val rad = fixed * PI / 180
                        val xx = this.center.x + (distance * cos(rad))
                        val yy = this.center.y + (distance * sin(rad))
                        drawCircle(Color.Red, 5F, Offset(xx.toFloat(), yy.toFloat()))
                }
            }
        }
    }
}

结果是一个灰色 canvas,中间有一个蓝色圆圈和我想要的圆点,看来必须有更好的方法。

从旋转的 LIDAR 收集数据一段时间后,我得到了一张非常好的房间照片,但我是新手,想知道是否有更好的方法将更改推送到 canvas.

我不认为 Compose 的当前状态 Canvas class 旨在处理这种情况。您必须在 canvas 组成之前绘制所有内容,如果有任何变化,您需要重新绘制所有内容。这对于轻量级绘图是可以的,但对于显示 36,000 点之类的东西就不行了。

一种解决方案是使用旧的基于视图的 canvas 并缓存绘制的图像,并在使用新数据更新图像时重用该缓存图像。您可以使用 Flow 将点按顺序发送到绘图 canvas,而不是遍历 36,000 个项目。当新点到达时,您将它们输入流中,当它们被收集时,它们被绘制到 canvas 上。但是在绘制新的角度之前,您还需要删除给定角度的 canvas 上的最后一个点。要删除前一个,您需要从地图中读取它的最后一个值。通过使用流程,您只需在新数据点到达时更新 canvas。

即使可组合项(托管您的 canvas)重新组合,您也应该能够检索缓存的 canvas 图像并使用它,而不必通过迭代重建整个图像地图上的所有点。您只需要确保在视图模型中放置对 canvas 的引用,这样它就不会在重组可组合项时被销毁。

我通过预加载点列表来实现此功能,该列表可以过滤掉过时值、0 和无用的大数字等噪音。这段代码满足了我的需要,我可以看到物理对象随着激光雷达移动通过 space 并带有罗盘航向而发生变化。甚至在路上显示点为红色,蓝色线显示磁北,在屏幕截图中我什至可以看到我的肩膀和弯曲的显示器。


@Composable
fun lidarComposable(vm: MainViewModel) {

    val maxRelevantAge = 5000

    MaterialTheme {
        Column {
            Canvas(modifier = Modifier.fillMaxSize(), onDraw = {
                val padding = 50
                val canvasWidth = size.width - padding
                val canvasHeight = size.height - padding

                drawRect(Color.LightGray, topLeft = Offset(0f, 0f), size = Size(this.size.width, this.size.height))
                drawCircle(Color.Blue, 20F, this.center)

                val north = 360 - vm.compass
                rotate(north) {
                    drawLine(
                        Color.Blue,
                        this.center,
                        Offset(this.center.x, (canvasHeight / 2 - canvasWidth / 2) + padding / 2)
                    )
                    drawCircle(Color.Blue, canvasWidth / 2, this.center, style = Stroke(5f))
                }

                rotate(vm.compass) {
                    drawLine(
                        Color.Red,
                        this.center,
                        Offset(this.center.x, (canvasHeight / 2 - canvasWidth / 2) + padding / 2)
                    )
                }

                    val obstructions: MutableList<Offset> = ArrayList()
                    val surroundings: MutableList<Offset> = ArrayList()
                    vm.lidardata.data
                    vm.lidardata.data.forEach { (a, d) ->

                        if (System.currentTimeMillis() - d.timestamp.get() < maxRelevantAge) {
                            val angle: Double = a.toDouble() / Lidar.relevance
                            val distance: Double = ((d.distance.get() / Lidar.relevance).toDouble() / 2)
                            val fixed = angle - vm.compass
                            val rad = fixed * PI / 180
                            val xx = this.center.x + (distance * cos(rad))
                            val yy = this.center.y + (distance * sin(rad))

                            if (distance > 0 && ((angle < 20) or (angle > 340))) {
                                obstructions.add(Offset(xx.toFloat(), yy.toFloat()))
                            } else if (distance < canvasWidth / 2) {
                                surroundings.add(Offset(xx.toFloat(), yy.toFloat()))
                            }

                        }
                    }


                    drawPoints(obstructions, PointMode.Points, Color.Red, strokeWidth = 5f)
                    drawPoints(surroundings, PointMode.Points, Color.Black, strokeWidth = 5f)



            })

        }
    }
}