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)
})
}
}
}
我有一个包含 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)
})
}
}
}