欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net

引言

在移动应用开发中,数据可视化已经成为不可或缺的功能。无论是金融分析、健康追踪还是业务报表,图表都能直观地展示数据趋势和关系。Flutter 作为跨平台开发框架,为开发者提供了一套统一的代码库,可在 iOS、Android、Web 等多个平台运行。随着 OpenHarmony 生态的快速发展,如何将现有的 Flutter 图表库适配到 OpenHarmony 平台成为了一个新的挑战。

Syncfusion Flutter Charts 是 Flutter 生态中一款功能强大的图表库,以其高度自定义能力、丰富的图表类型和专业的视觉效果著称。它支持多种图表类型,包括线图、柱状图、饼图、甜甜圈图等,并且提供了丰富的配置选项,让开发者可以根据需求创建各种复杂的图表。

混合工程结构深度解析

项目目录架构

当Flutter项目集成鸿蒙支持后,典型的项目结构会发生显著变化。以下是经过ohos_flutter插件初始化后的项目结构:

fluuter_openHarmony/
├── lib/                          # Flutter业务代码(基本不变)
│   ├── main.dart                 # 应用入口
├── pubspec.yaml                  # Flutter依赖配置
├── ohos/                         # 鸿蒙原生层(核心适配区)
│   ├── entry/                    # 主模块
│   │   └── src/main/
│   │       ├── ets/              # ArkTS代码
│   │       │   ├── entryability/
│   │       │   │   └── EntryAbility.ets       # 主Ability
│   │       │   └── pages/
│   │       │       └── Index.ets           # 主页面
│   │       ├── resources/        # 鸿蒙资源文件
│   │       │   ├── base/
│   │       │   │   ├── element/  # 字符串等
│   │       │   │   ├── media/    # 图片资源
│   │       │   │   └── profile/  # 配置文件
│   │       └── module.json5      # 应用核心配置
│   ├── AppScope/                 # 应用全局配置
│   │   └── resources/
│   │       └── base/
│   │           ├── element/
│   │           └── media/
│   ├── build-profile.json5      # 构建配置
│   └── oh-package.json5         # 鸿蒙依赖管理
└── README.md

展示效果图片

flutter 实时预览 效果展示
在这里插入图片描述

运行到鸿蒙虚拟设备中效果展示
在这里插入图片描述

依赖配置

具体配置方法

  1. 添加依赖:在 pubspec.yaml 文件中添加 Syncfusion Flutter Charts 依赖

    dependencies:
      flutter:
        sdk: flutter
      cupertino_icons: ^1.0.8
      syncfusion_flutter_charts: ^26.1.39
    
  2. 安装依赖:执行 flutter pub get 命令安装依赖

    flutter pub get
    
  3. 导入包:在需要使用图表的文件中导入包

    import 'package:syncfusion_flutter_charts/charts.dart';
    

配置中遇到的问题和解决方案

  1. 依赖版本冲突

    • 问题:当项目中存在其他依赖与 Syncfusion Flutter Charts 版本不兼容时
    • 解决方案:执行 flutter pub upgrade 命令更新所有依赖到兼容版本,或在 pubspec.yaml 中指定兼容的版本范围
  2. 构建失败

    • 问题:在 OpenHarmony 平台上构建时出现依赖解析失败
    • 解决方案:确保 Flutter SDK 版本 >= 3.18.0,并且 ohos_flutter 插件版本与当前 Flutter 版本兼容

甜甜圈图实现

实现方法

  1. 数据模型设计

    class PieData {
      final String category;
      final double value;
      final Color color;
    
      PieData(this.category, this.value, this.color);
    }
    
  2. 数据准备

    List<PieData> getPieData() {
      return [
        PieData('苹果', 35, Colors.red),
        PieData('香蕉', 25, Colors.yellow),
        PieData('橙子', 20, Colors.orange),
        PieData('葡萄', 15, Colors.purple),
        PieData('其他', 5, Colors.grey),
      ];
    }
    
  3. 图表实现

    SizedBox(
      height: 400,
      child: SfCircularChart(
        title: ChartTitle(text: '水果销售分布'),
        tooltipBehavior: _tooltipBehavior,
        series: <CircularSeries>[// 甜甜圈图
          DoughnutSeries<PieData, String>(
            dataSource: _chartData,
            xValueMapper: (PieData data, _) => data.category,
            yValueMapper: (PieData data, _) => data.value,
            pointColorMapper: (PieData data, _) => data.color,
            dataLabelSettings: const DataLabelSettings(
              isVisible: true,
              labelPosition: ChartDataLabelPosition.inside,
            ),
            // 甜甜圈图特有属性
            innerRadius: '60%',
            radius: '80%',
          ),
        ],
        legend: const Legend(
          isVisible: true,
          position: LegendPosition.bottom,
        ),
      ),
    );
    
  4. 完整代码实现

    import 'package:flutter/material.dart';
    import 'package:syncfusion_flutter_charts/charts.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter 甜甜圈图演示',
          theme: ThemeData(
            colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
            useMaterial3: true,
          ),
          debugShowCheckedModeBanner: false,
          home: const MyHomePage(title: 'Flutter 甜甜圈图演示'),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({super.key, required this.title});
    
      final String title;
    
      
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class PieData {
      final String category;
      final double value;
      final Color color;
    
      PieData(this.category, this.value, this.color);
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      late List<PieData> _chartData;
      late TooltipBehavior _tooltipBehavior;
    
      
      void initState() {
        _chartData = getPieData();
        _tooltipBehavior = TooltipBehavior(enable: true);
        super.initState();
      }
    
      List<PieData> getPieData() {
        return [
          PieData('苹果', 35, Colors.red),
          PieData('香蕉', 25, Colors.yellow),
          PieData('橙子', 20, Colors.orange),
          PieData('葡萄', 15, Colors.purple),
          PieData('其他', 5, Colors.grey),
        ];
      }
    
      
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Center(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: [
                  const Text(
                    '水果销售占比',
                    style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
                    textAlign: TextAlign.center,
                  ),
                  const SizedBox(height: 20),
                  SizedBox(
                    height: 400,
                    child: SfCircularChart(
                      title: ChartTitle(text: '水果销售分布'),
                      tooltipBehavior: _tooltipBehavior,
                      series: <CircularSeries>[// 甜甜圈图
                        DoughnutSeries<PieData, String>(
                          dataSource: _chartData,
                          xValueMapper: (PieData data, _) => data.category,
                          yValueMapper: (PieData data, _) => data.value,
                          pointColorMapper: (PieData data, _) => data.color,
                          dataLabelSettings: const DataLabelSettings(
                            isVisible: true,
                            labelPosition: ChartDataLabelPosition.inside,
                          ),
                          // 甜甜圈图特有属性
                          innerRadius: '60%',
                          radius: '80%',
                        ),
                      ],
                      legend: const Legend(
                        isVisible: true,
                        position: LegendPosition.bottom,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    }
    

实现中遇到的问题和解决方案

  1. 图表显示不完整

    • 问题:在部分设备上,图表可能显示不完整或被截断
    • 解决方案:使用 SizedBox 为图表指定固定的高度和宽度,确保图表有足够的空间显示
  2. 颜色配置

    • 问题:默认颜色可能不符合设计需求
    • 解决方案:通过 pointColorMapper 属性自定义每个数据点的颜色,实现个性化的视觉效果
  3. 数据标签显示

    • 问题:数据标签可能重叠或显示位置不当
    • 解决方案:调整 dataLabelSettings 中的 labelPosition 属性,选择合适的标签位置

高度自定义配置详解

图表标题配置

title: ChartTitle(
  text: '水果销售分布',
  textStyle: const TextStyle(
    fontSize: 16,
    fontWeight: FontWeight.bold,
  ),
  alignment: ChartAlignment.near,
),

图例配置

legend: Legend(
  isVisible: true,
  position: LegendPosition.bottom,
  orientation: LegendItemOrientation.horizontal,
  legendItemBuilder: (String name, dynamic series, dynamic point, int index) {
    return Container(
      padding: const EdgeInsets.all(5),
      child: Row(
        children: [
          Container(
            width: 10,
            height: 10,
            color: _chartData[index].color,
          ),
          const SizedBox(width: 5),
          Text(name),
        ],
      ),
    );
  },
),

工具提示配置

tooltipBehavior: TooltipBehavior(
  enable: true,
  format: 'point.x: point.y%',
  tooltipPosition: TooltipPosition.pointer,
  canShowMarker: true,
  borderWidth: 1,
  borderColor: Colors.grey,
),

甜甜圈图特有配置

DoughnutSeries<PieData, String>(
  // 其他配置...
  innerRadius: '60%', // 内圆半径
  radius: '80%', // 外圆半径
  startAngle: 90, // 起始角度
  endAngle: 360, // 结束角度
  explode: true, // 是否突出显示点击的扇区
  explodeIndex: 0, // 默认突出显示的扇区索引
  explodeOffset: '10%', // 突出显示的偏移量
),

OpenHarmony 适配优化实践

性能优化

  1. 减少重绘:使用 const 构造函数和 const 变量,减少不必要的重绘
  2. 懒加载:对于复杂图表,考虑使用 FutureBuilderStreamBuilder 进行懒加载
  3. 数据处理:在数据量较大时,考虑在后台线程处理数据,避免阻塞 UI 线程

布局适配

  1. 响应式布局:使用 MediaQuery 获取屏幕尺寸,动态调整图表大小

    final screenWidth = MediaQuery.of(context).size.width;
    final chartHeight = screenWidth * 0.8;
    
    SizedBox(
      height: chartHeight,
      child: SfCircularChart(
        // 配置...
      ),
    );
    
  2. 安全区域适配:使用 SafeArea 确保图表在不同设备上都能正常显示

    SafeArea(
      child: Scaffold(
        // 内容...
      ),
    );
    

平台特定代码

  1. 条件导入:根据平台导入不同的代码

    import 'dart:io';
    
    if (Platform.isAndroid) {
      // Android 特定代码
    } else if (Platform.isIOS) {
      // iOS 特定代码
    } else if (Platform.isWindows) {
      // Windows 特定代码
    }
    
  2. 平台检测:使用 Platform 类检测当前平台

    if (Platform.isHarmony) {
      // OpenHarmony 特定代码
    }
    

构建和部署

  1. 构建命令

    # 构建 OpenHarmony 版本
    flutter build ohos
    
    # 构建 Web 版本
    flutter build web
    
  2. 部署到设备

    # 运行到 OpenHarmony 设备
    flutter run -d ohos
    

展示效果

Flutter 实时预览效果

在 Flutter 开发环境中,通过热重载功能可以实时预览甜甜圈图的效果。图表会根据数据变化实时更新,便于开发者快速调整和优化。

OpenHarmony 设备运行效果

在 OpenHarmony 设备上运行时,甜甜圈图能够保持与 Flutter 预览一致的视觉效果,包括颜色、动画和交互体验。图表会根据设备屏幕大小自动适配,确保在不同尺寸的设备上都能正常显示。

总结与展望

通过本次开发,我们成功实现了在 Flutter 项目中使用 Syncfusion Flutter Charts 库创建甜甜圈图,并将其适配到 OpenHarmony 平台。Syncfusion Flutter Charts 提供了丰富的配置选项和良好的跨平台兼容性,使得开发者可以轻松创建专业、美观的图表。

未来,我们可以进一步探索 Syncfusion Flutter Charts 的其他功能,如:

  1. 交互式图表:添加手势操作,如缩放、平移等
  2. 动态数据更新:实现实时数据更新和动画效果
  3. 复合图表:将多种图表类型组合使用,如饼图与线图结合
  4. 主题定制:根据应用主题动态调整图表样式

通过不断优化和扩展,我们可以为用户提供更加丰富、直观的数据可视化体验,满足不同场景下的需求。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐