Flutter 鸿蒙开发实践:利用三方库集成实现跨端金融汇率趋势看板

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

1. 前言

鸿蒙(HarmonyOS) 应用生态中,金融理财类应用占据了重要地位。相较于传统原生开发,Flutter 凭借“一次编写、多端运行”的特性,能大幅降低鸿蒙与其他平台的开发成本,同时保证高性能的 UI 渲染体验。本文将带你通过 Flutter 构建一个动态汇率趋势看板,学习如何集成强大的图表三方库 `fl_chart` 实现数据可视化,并在没有硬件依赖的情况下,在鸿蒙虚拟机上完成高交互 UI 的开发与调试。

2. 核心关键词

  • Flutter: 跨端高性能 UI 引擎,兼容 HarmonyOS Next 全场景部署。

  • 三方库: `fl_chart` (专业图表库,支持折线/柱状/饼图等多类型可视化) 与 `intl` (货币/日期标准化格式化)。

  • 鸿蒙: HarmonyOS Next 运行环境(虚拟机 100% 流程支持,无需真机即可完成全链路调试)。

  • 金融场景: 实时汇率趋势展示、多币种切换、数据动态刷新。

3. 项目案例:Harmony Finance Hub

3.1 产品定位

打造轻量化跨端金融面板,核心功能包括:

  • 实时展示美元/欧元/日元对人民币的汇率走势折线图;

  • 支持按“日/周/月”维度切换趋势数据;

  • 货币金额自动格式化,适配多语言区域显示;

  • 鸿蒙系统下的沉浸式 UI 适配(如状态栏、圆角、手势交互)。

3.2 最终效果预览

功能模块 效果描述
汇率折线图 平滑动画过渡,支持触摸高亮显示对应时间点汇率,鸿蒙虚拟机下 60fps 流畅渲染
维度切换按钮 底部 Tab 切换日/周/月数据,按钮选中态适配鸿蒙系统设计规范
货币格式化展示 自动补全小数点后两位,符合金融场景数字显示标准

4. 第一步:引入三方库

在 `pubspec.yaml` 中引入金融应用必不可少的图表和国际化工具库,同时确保 Flutter SDK 版本兼容 HarmonyOS Next:

name: harmony_finance_hub
description: A Flutter-based exchange rate dashboard for HarmonyOS
version: 1.0.0+1

environment:
  sdk: '>=3.0.0 <4.0.0' # 适配鸿蒙的Flutter SDK版本
  flutter: ">=3.10.0"

dependencies:
  flutter:
    sdk: flutter
  # 三方库:Flutter 最强大的图表库,支持丝滑的动画与鸿蒙适配
  fl_chart: ^0.66.0
  # 三方库:用于货币、数字、日期的标准化格式化
  intl: ^0.19.0
  # 鸿蒙系统适配库(可选,增强原生交互)
  harmony_os_api: ^1.0.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0

flutter:
  uses-material-design: true
  # 鸿蒙资源适配
  assets:
    - assets/images/

执行依赖安装命令:

flutter pub get

5. 第二步:核心代码实现

5.1 汇率数据模型

创建 `exchange_rate_model.dart`,定义标准化的数据结构,适配多维度数据切换:

import 'package:intl/intl.dart';

// 汇率数据模型
class ExchangeRate {
  final DateTime date; // 时间点
  final double value; // 汇率值
  final String currencyCode; // 货币编码(USD/EUR/JPY)

  ExchangeRate({
    required this.date,
    required this.value,
    required this.currencyCode,
  });

  // 格式化汇率显示(保留两位小数)
  String formattedValue() {
    final formatter = NumberFormat('#,##0.00', 'zh_CN');
    return formatter.format(value);
  }

  // 格式化日期(适配不同维度显示)
  String formattedDate(String dimension) {
    switch (dimension) {
      case 'day':
        return DateFormat('HH:mm').format(date);
      case 'week':
        return DateFormat('EEE').format(date);
      case 'month':
        return DateFormat('MM-dd').format(date);
      default:
        return DateFormat('yyyy-MM-dd').format(date);
    }
  }
}

// 模拟汇率数据生成器
class MockExchangeRateData {
  // 获取指定货币、指定维度的模拟数据
  static List<ExchangeRate> getMockData(String currencyCode, String dimension) {
    final List<ExchangeRate> data = [];
    final now = DateTime.now();
    int count = dimension == 'day' ? 24 : (dimension == 'week' ? 7 : 30);

    for (int i = 0; i < count; i++) {
      DateTime date;
      double baseValue = currencyCode == 'USD' ? 7.2 : (currencyCode == 'EUR' ? 7.8 : 0.05);
      // 模拟小幅波动
      double randomValue = baseValue + (Random().nextDouble() - 0.5) * 0.2;

      switch (dimension) {
        case 'day':
          date = now.subtract(Duration(hours: count - i));
          break;
        case 'week':
          date = now.subtract(Duration(days: count - i));
          break;
        case 'month':
          date = now.subtract(Duration(days: count - i));
          break;
        default:
          date = now.subtract(Duration(days: count - i));
      }

      data.add(ExchangeRate(
        date: date,
        value: randomValue,
        currencyCode: currencyCode,
      ));
    }
    return data;
  }
}

5.2 折线图组件实现

创建 `exchange_rate_chart.dart`,基于 `fl_chart` 封装鸿蒙适配的折线图组件:

import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:harmony_finance_hub/models/exchange_rate_model.dart';

class ExchangeRateLineChart extends StatefulWidget {
  final String currencyCode;
  final String dimension; // day/week/month

  const ExchangeRateLineChart({
    super.key,
    required this.currencyCode,
    required this.dimension,
  });

  
  State<ExchangeRateLineChart> createState() => _ExchangeRateLineChartState();
}

class _ExchangeRateLineChartState extends State<ExchangeRateLineChart> {
  late List<ExchangeRate> _rateData;

  
  void initState() {
    super.initState();
    _updateData();
  }

  
  void didUpdateWidget(covariant ExchangeRateLineChart oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.currencyCode != widget.currencyCode || oldWidget.dimension != widget.dimension) {
      _updateData();
    }
  }

  // 更新汇率数据
  void _updateData() {
    _rateData = MockExchangeRateData.getMockData(widget.currencyCode, widget.dimension);
  }

  // 构建折线图数据点
  List<FlSpot> _buildSpots() {
    return _rateData.asMap().entries.map((entry) {
      int index = entry.key;
      ExchangeRate rate = entry.value;
      return FlSpot(index.toDouble(), rate.value);
    }).toList();
  }

  
  Widget build(BuildContext context) {
    return Container(
      height: 300,
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
      child: LineChart(
        LineChartData(
          // 基础配置
          lineTouchData: LineTouchData(
            enabled: true,
            touchTooltipData: LineTouchTooltipData(
              tooltipBgColor: Colors.white.withOpacity(0.9),
              tooltipBorder: BorderSide(color: Colors.grey.shade200),
              // 鸿蒙风格的提示框文本
              getTooltipItems: (touchedSpots) {
                return touchedSpots.map((spot) {
                  final rate = _rateData[spot.x.toInt()];
                  return LineTooltipItem(
                    '${rate.currencyCode}: ${rate.formattedValue()}\n${rate.formattedDate(widget.dimension)}',
                    const TextStyle(color: Colors.black87, fontSize: 12),
                  );
                }).toList();
              },
            ),
          ),
          gridData: FlGridData(
            show: true,
            drawVerticalLine: false,
            horizontalInterval: 0.2,
            getDrawingHorizontalLine: (value) => FlLine(
              color: Colors.grey.shade100,
              strokeWidth: 1,
            ),
          ),
          titlesData: FlTitlesData(
            bottomTitles: AxisTitles(
              sideTitles: SideTitles(
                showTitles: true,
                interval: widget.dimension == 'day' ? 4 : 1, // 控制x轴标签密度
                getTitlesWidget: (value, meta) {
                  int index = value.toInt();
                  if (index >= 0 && index < _rateData.length) {
                    return Text(
                      _rateData[index].formattedDate(widget.dimension),
                      style: const TextStyle(fontSize: 10, color: Colors.grey),
                    );
                  }
                  return const Text('');
                },
              ),
            ),
            leftTitles: AxisTitles(
              sideTitles: SideTitles(
                showTitles: true,
                interval: 0.2,
                getTitlesWidget: (value, meta) {
                  return Text(
                    value.toStringAsFixed(2),
                    style: const TextStyle(fontSize: 10, color: Colors.grey),
                  );
                },
              ),
            ),
            topTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
            rightTitles: const AxisTitles(sideTitles: SideTitles(showTitles: false)),
          ),
          borderData: FlBorderData(
            show: true,
            border: Border.all(color: Colors.grey.shade200),
          ),
          // 折线配置
          lineBarsData: [
            LineChartBarData(
              spots: _buildSpots(),
              isCurved: true, // 平滑曲线
              color: _getCurrencyColor(),
              barWidth: 2,
              isStrokeCapRound: true,
              dotData: const FlDotData(show: true, dotSize: 4),
              belowBarData: BarAreaData(
                show: true,
                color: _getCurrencyColor().withOpacity(0.1),
              ),
            ),
          ],
        ),
        swapAnimationDuration: const Duration(milliseconds: 500), // 鸿蒙风格的过渡动画
        swapAnimationCurve: Curves.easeInOut,
      ),
    );
  }

  // 根据货币编码匹配颜色(鸿蒙系统色板适配)
  Color _getCurrencyColor() {
    switch (widget.currencyCode) {
      case 'USD':
        return Colors.blue.shade600;
      case 'EUR':
        return Colors.orange.shade600;
      case 'JPY':
        return Colors.green.shade600;
      default:
        return Colors.blue.shade600;
    }
  }
}

5.3 主页面整合

创建 `home_page.dart`,整合图表、切换按钮,适配鸿蒙 UI 规范:

import 'package:flutter/material.dart';
import 'package:harmony_finance_hub/components/exchange_rate_chart.dart';

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String _selectedCurrency = 'USD'; // 默认选中美元
  String _selectedDimension = 'day'; // 默认日维度

  // 货币切换按钮组
  final List<String> _currencyList = ['USD', 'EUR', 'JPY'];
  // 维度切换按钮组
  final List<String> _dimensionList = ['day', 'week', 'month'];

  
  Widget build(BuildContext context) {
    return Scaffold(
      // 鸿蒙沉浸式状态栏适配
      appBar: AppBar(
        title: const Text('汇率趋势看板'),
        elevation: 0,
        backgroundColor: Colors.white,
        foregroundColor: Colors.black87,
      ),
      body: SingleChildScrollView(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 货币切换区域
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
              child: Row(
                children: _currencyList.map((currency) {
                  return Expanded(
                    child: Padding(
                      padding: const EdgeInsets.symmetric(horizontal: 4),
                      child: ElevatedButton(
                        onPressed: () => setState(() => _selectedCurrency = currency),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: _selectedCurrency == currency 
                              ? Colors.blue.shade600 
                              : Colors.grey.shade100,
                          foregroundColor: _selectedCurrency == currency 
                              ? Colors.white 
                              : Colors.black87,
                          // 鸿蒙风格圆角
                          shape: RoundedRectangleBorder(
                            borderRadius: BorderRadius.circular(8),
                          ),
                        ),
                        child: Text(currency),
                      ),
                    ),
                  );
                }).toList(),
              ),
            ),

            // 汇率图表区域
            ExchangeRateLineChart(
              currencyCode: _selectedCurrency,
              dimension: _selectedDimension,
            ),

            // 维度切换区域
            Padding(
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: _dimensionList.map((dimension) {
                  String label = dimension == 'day' ? '日' : (dimension == 'week' ? '周' : '月');
                  return Padding(
                    padding: const EdgeInsets.symmetric(horizontal: 8),
                    child: GestureDetector(
                      onTap: () => setState(() => _selectedDimension = dimension),
                      child: Container(
                        padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                        decoration: BoxDecoration(
                          color: _selectedDimension == dimension 
                              ? Colors.blue.shade600 
                              : Colors.grey.shade100,
                          borderRadius: BorderRadius.circular(20), // 鸿蒙风格胶囊按钮
                        ),
                        child: Text(
                          label,
                          style: TextStyle(
                            color: _selectedDimension == dimension 
                                ? Colors.white 
                                : Colors.black87,
                            fontSize: 14,
                          ),
                        ),
                      ),
                    ),
                  );
                }).toList(),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

5.4 入口文件配置

修改 `main.dart`,适配鸿蒙应用启动:

import 'package:flutter/material.dart';
import 'package:harmony_finance_hub/pages/home_page.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Harmony Finance Hub',
      theme: ThemeData(
        // 鸿蒙系统主题适配
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
        // 鸿蒙圆角规范
        cardTheme: const CardTheme(
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.all(Radius.circular(12)),
          ),
        ),
      ),
      home: const HomePage(),
      debugShowCheckedModeBanner: false, // 隐藏调试横幅
    );
  }
}

6. 第三步:鸿蒙虚拟机运行与调试

6.1 鸿蒙虚拟机环境准备

  1. 下载并安装 HarmonyOS DevEco Studio

  2. 创建 HarmonyOS Next 虚拟机(推荐 API 9 及以上版本);

  3. 确保 Flutter 已配置鸿蒙编译环境:
    flutter config \-\-enable\-harmony

6.2 编译并运行

# 构建鸿蒙应用包
flutter build harmony --release

# 直接运行到鸿蒙虚拟机
flutter run -d harmony

6.3 调试注意事项

  1. 鸿蒙虚拟机中若出现图表渲染卡顿,可关闭 `fl_chart` 的抗锯齿优化:
    LineChart\( LineChartData\(/\* \.\.\. \*/\), clipData: FlClipData\.none\(\), antialias: false, \)

  2. 货币格式化需适配鸿蒙系统的区域设置,可通过 `intl` 库动态获取系统语言:
    final locale = Localizations\.localeOf\(context\); final formatter = NumberFormat\(\&\#39;\#,\#\#0\.00\&\#39;, locale\.languageCode\);

7. 进阶优化方向

7.1 性能优化

  • 对模拟数据进行缓存,避免频繁重建;

  • 使用 `RepaintBoundary` 包裹图表组件,减少不必要的重绘;

  • 鸿蒙系统下开启硬件加速,提升动画流畅度。

7.2 功能扩展

  • 接入真实汇率 API(如 Fixer、ExchangeRate-API),替换模拟数据;

  • 添加汇率预警功能,适配鸿蒙的通知栏推送;

  • 支持鸿蒙平板的分屏/悬浮窗模式适配。

7.3 鸿蒙原生交互增强

  • 通过 `harmony_os_api` 调用鸿蒙原生能力(如生物识别、支付接口);

  • 适配鸿蒙的深色模式,实现主题动态切换;

  • 遵循鸿蒙应用上架规范,优化权限申请、隐私政策等流程。

8. 总结

本文通过一个实战案例,完整讲解了如何基于 Flutter 集成 `fl_chart` 和 `intl` 三方库,开发适配 HarmonyOS Next 的金融汇率趋势看板。核心要点包括:

  1. 三方库的引入与版本适配,确保兼容鸿蒙环境;

  2. 金融场景下的数据模型设计与格式化处理;

  3. `fl_chart` 的定制化封装,适配鸿蒙的 UI 风格与交互规范;

  4. 鸿蒙虚拟机的部署与调试技巧。

通过 Flutter 跨端能力,开发者可快速实现鸿蒙金融应用的开发,同时复用已有代码到 iOS/Android 平台,大幅提升开发效率。后续可基于此案例,进一步扩展更多金融场景功能,如K线图、资产统计等,打造完整的鸿蒙金融应用生态。

运行截图:
在这里插入图片描述

Logo

一站式 AI 云服务平台

更多推荐