compose-multiplatform 跨平台图表开发指南

Viewed 0

告别跨平台图表适配烦恼:compose-multiplatform可视化开发指南

本文介绍如何使用 compose-multiplatform 构建跨平台数据可视化界面,实现一次编码多端运行,解决多平台图表开发难题。你将学习基础图表绘制、交互实现和跨平台适配技巧。

开发环境搭建

首先确保开发环境已正确配置。compose-multiplatform 支持 Android Studio 作为主要开发工具,需要安装 Kotlin 插件和 compose-multiplatform 插件。环境配置可参考官方文档,确保 Gradle 版本与项目兼容。

项目结构与依赖配置

以 graphics-2d 示例项目为基础进行扩展,该项目已实现基本图形绘制功能,支持 Android、iOS、桌面和浏览器多平台运行。项目结构如下:

examples/graphics-2d/
├── androidApp/        # Android平台代码
├── iosApp/            # iOS平台代码
├── desktopApp/        # 桌面平台代码
├── jsApp/             # Web平台代码
└── shared/            # 共享代码模块
    └── src/commonMain/kotlin/
        ├── fallingballs/   # 下落球动画示例
        ├── bouncingballs/  # 弹跳球动画示例
        └── visualeffects/  # 视觉效果组件

在共享模块的 build.gradle.kts 中添加必要依赖:

commonMain {
    dependencies {
        implementation("org.jetbrains.compose.ui:ui")
        implementation("org.jetbrains.compose.foundation:foundation")
        implementation("org.jetbrains.compose.material:material")
    }
}

基础图表组件开发

折线图实现

在 shared 模块中创建图表基础组件,首先实现简单折线图。创建 commonMain/kotlin/charts/LineChart.kt 文件:

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke

data class ChartData(val x: Float, val y: Float)

@Composable
fun LineChart(
    data: List<ChartData>,
    modifier: Modifier = Modifier,
    lineColor: Color = Color.Blue,
    lineWidth: Float = 2f
) {
    Canvas(modifier = modifier.fillMaxSize()) {
        val canvasWidth = size.width
        val canvasHeight = size.height
        val minX = data.minOfOrNull { it.x } ?: 0f
        val maxX = data.maxOfOrNull { it.x } ?: 1f
        val minY = data.minOfOrNull { it.y } ?: 0f
        val maxY = data.maxOfOrNull { it.y } ?: 1f

        fun transformX(x: Float) =
            ((x - minX) / (maxX - minX)) * canvasWidth
        fun transformY(y: Float) =
            canvasHeight - ((y - minY) / (maxY - minY)) * canvasHeight

        drawLine(
            color = Color.Black,
            start = Offset(0f, canvasHeight),
            end = Offset(canvasWidth, canvasHeight),
            strokeWidth = 2f
        )
        drawLine(
            color = Color.Black,
            start = Offset(0f, 0f),
            end = Offset(0f, canvasHeight),
            strokeWidth = 2f
        )

        if (data.size >= 2) {
            for (i in 0 until data.size - 1) {
                val start = Offset(transformX(data[i].x), transformY(data[i].y))
                val end = Offset(transformX(data[i+1].x), transformY(data[i+1].y))
                drawLine(
                    color = lineColor,
                    start = start,
                    end = end,
                    strokeWidth = lineWidth,
                    cap = StrokeCap.Round
                )
            }
        }
    }
}

柱状图实现

创建 commonMain/kotlin/charts/BarChart.kt 文件,实现柱状图组件:

import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Fill

@Composable
fun BarChart(
    data: List<Float>,
    modifier: Modifier = Modifier,
    barColor: Color = Color.Green,
    barSpacing: Float = 10f
) {
    Canvas(modifier = modifier.fillMaxSize()) {
        val canvasWidth = size.width
        val canvasHeight = size.height
        val barWidth = (canvasWidth - barSpacing * (data.size + 1)) / data.size

        drawLine(
            color = Color.Black,
            start = Offset(0f, canvasHeight),
            end = Offset(canvasWidth, canvasHeight),
            strokeWidth = 2f
        )
        drawLine(
            color = Color.Black,
            start = Offset(0f, 0f),
            end = Offset(0f, canvasHeight),
            strokeWidth = 2f
        )

        val maxValue = data.maxOrNull() ?: 1f
        data.forEachIndexed { index, value ->
            val barHeight = (value / maxValue) * canvasHeight * 0.8f
            val x = barSpacing * (index + 1) + barWidth * index
            val y = canvasHeight - barHeight
            drawRect(
                color = barColor,
                topLeft = Offset(x, y),
                size = Size(barWidth, barHeight),
                style = Fill
            )
        }
    }
}

图表组合与交互实现

在主界面中集成图表组件,创建 commonMain/kotlin/ChartDemo.kt 文件:

import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import charts.BarChart
import charts.LineChart
import charts.ChartData

@Composable
fun ChartDemo() {
    val currentChart = remember { mutableStateOf("line") }
    val lineData = listOf(
        ChartData(0f, 3f),
        ChartData(1f, 5f),
        ChartData(2f, 2f),
        ChartData(3f, 7f),
        ChartData(4f, 4f),
        ChartData(5f, 8f)
    )
    val barData = listOf(4f, 6f, 3f, 8f, 5f, 7f)

    Column(modifier = Modifier.fillMaxSize().padding(16.dp)) {
        Button(onClick = { currentChart.value = "line" }) {
            Text("折线图")
        }
        Button(onClick = { currentChart.value = "bar" }) {
            Text("柱状图")
        }

        when (currentChart.value) {
            "line" -> LineChart(
                data = lineData,
                modifier = Modifier.weight(1f)
            )
            "bar" -> BarChart(
                data = barData,
                modifier = Modifier.weight(1f)
            )
        }
    }
}

多平台运行配置

桌面平台运行

桌面应用入口在 desktopApp/src/jvmMain/kotlin/Main.kt,修改为:

import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application

fun main() = application {
    Window(onCloseRequest = ::exitApplication, title = "图表演示") {
        ChartDemo()
    }
}

运行桌面应用:

./gradlew desktopApp:run

Android平台运行

Android应用入口在 androidApp/src/main/kotlin/MainActivity.kt,修改为:

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            ChartDemo()
        }
    }
}

iOS平台运行

iOS应用入口在 iosApp/iosApp/iOSApp.swift,修改为:

import SwiftUI
import ComposeApp

@main
struct iOSApp: App {
    var body: some Scene {
        WindowGroup {
            ComposeView().edgesIgnoringSafeArea(.all)
        }
    }
}

struct ComposeView: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIViewController {
        MainViewControllerKt.MainViewController()
    }

    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {}
}

性能优化与最佳实践

  1. 数据缓存:对于大量数据,使用 rememberSaveable 缓存计算结果。
  2. 按需渲染:使用 LaunchedEffect 处理数据加载和更新。
  3. 避免过度绘制:合理设置 Canvas 大小,避免全屏绘制。
  4. 平台适配:针对不同平台调整图表交互方式。

总结与扩展

通过本文学习,你已掌握使用 compose-multiplatform 开发跨平台图表的基本方法。可以进一步扩展实现饼图、散点图等更多图表类型,或集成数据解析功能实现从 CSV、JSON 文件加载数据。参考 graphics-2d 示例和官方文档以探索更多高级功能。

0 Answers