Flutter 鸿蒙开发实践:利用三方库实现跨端动态数学函数曲线拟合器

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

1. 前言

对于具备数学背景的**鸿蒙(HarmonyOS)**开发者来说,将抽象公式转化为可视化图形是极具成就感的挑战。传统的鸿蒙原生开发实现数学函数可视化需要编写大量底层绘图代码,且跨端适配成本高;而 Flutter 凭借跨端一致性、高性能渲染引擎的优势,能快速实现跨鸿蒙、Android、iOS 等多端的数学函数可视化能力。

本文将带你通过 Flutter 构建一个动态函数图像生成器,学习如何集成三方库 expressions 动态解析用户输入的数学公式,结合 Flutter 绘图组件在鸿蒙虚拟机(HarmonyOS Next)上实时渲染曲线,同时实现坐标系平移、缩放等交互功能。

2. 核心关键词

  • Flutter: 高性能跨端渲染引擎,一套代码适配鸿蒙/Android/iOS 等平台。

  • 三方库: expressions (表达式动态求值插件),支持将字符串格式的数学公式解析为可执行逻辑。

  • 鸿蒙: HarmonyOS Next 运行环境(基于 OpenHarmony 内核,Flutter 应用可通过方舟编译器无缝运行)。

  • 函数拟合: 通过离散点采样、插值计算,将数学表达式转化为连续的可视化曲线。

3. 项目案例:Harmony MathGraph

3.1 功能说明

用户在输入框中输入任意合法的数学表达式(如 sin\(x\) \* xx^2 \- 2x \+ 1cos\(2x\) \+ sin\(x\)),应用实时解析表达式并在笛卡尔坐标系中绘制对应的函数曲线,支持以下交互:

  • 手指拖拽:坐标系平移,查看不同区间的函数图像;

  • 双指缩放:调整坐标系刻度,聚焦函数细节;

  • 表达式校验:实时提示非法表达式(如语法错误、未定义变量)。

3.2 效果预览

功能场景 效果描述
基础函数绘制 输入 y = x^2,快速渲染抛物线,坐标轴自动适配函数值域
三角函数绘制 输入 y = sin\(x\) \+ cos\(x\),渲染周期曲线,支持自定义x轴取值范围
交互操作 拖拽平移至函数极值点,缩放查看极值点坐标细节

4. 开发步骤

4.1 配置依赖

pubspec\.yaml 中引入核心依赖库,除表达式解析库外,补充绘图和交互相关基础依赖:

dependencies:
  flutter:
    sdk: flutter
  # 核心:动态解析并执行复杂的数学表达式
  expressions: ^0.2.5
  # 辅助:简化数值计算和精度处理
  decimal: ^2.3.3
  # 辅助:手势交互封装(可选,也可原生实现)
  flutter_gesture_detector: ^0.2.0

dev_dependencies:
  flutter_test:
    sdk: flutter
  # 鸿蒙适配辅助工具(可选)
  ohos_flutter: ^1.0.0

配置完成后执行 flutter pub get 拉取依赖。

4.2 核心代码实现

4.2.1 表达式解析工具类

创建math\_expression\_parser\.dart,封装表达式解析和求值逻辑:

import 'package:expressions/expressions.dart';
import 'package:decimal/decimal.dart';

/// 数学表达式解析工具类
class MathExpressionParser {
  /// 解析并计算数学表达式
  /// [expressionStr] 数学表达式字符串(如 "sin(x) * x")
  /// [xValue] x变量的取值
  /// 返回计算结果,解析失败返回null
  static double? calculateExpression(String expressionStr, double xValue) {
    try {
      // 替换表达式中的特殊符号(如^为**,适配expressions库语法)
      String processedStr = expressionStr
          .replaceAll('^', '**')
          .replaceAll('π', '3.141592653589793')
          .replaceAll('e', '2.718281828459045');

      // 定义表达式上下文(绑定x变量)
      final context = {"x": xValue};

      // 构建表达式解析器
      final parser = ExpressionParser();
      final expression = parser.parse(processedStr);

      // 执行表达式计算
      final evaluator = const ExpressionEvaluator();
      final result = evaluator.eval(expression, context);

      // 处理数值精度,转换为double
      if (result is num) {
        return Decimal.parse(result.toString()).toDouble();
      }
      return null;
    } catch (e) {
      print("表达式解析失败:$e");
      return null;
    }
  }

  /// 校验表达式是否合法
  static bool validateExpression(String expressionStr) {
    try {
      String processedStr = expressionStr
          .replaceAll('^', '**')
          .replaceAll('π', '3.141592653589793')
          .replaceAll('e', '2.718281828459045');
      final parser = ExpressionParser();
      parser.parse(processedStr);
      return true;
    } catch (e) {
      return false;
    }
  }
}
4.2.2 函数曲线绘制组件

创建 function\_painter\.dart,继承 CustomPainter 实现坐标系和函数曲线绘制:

import 'package:flutter/material.dart';
import 'package:decimal/decimal.dart';
import 'math_expression_parser.dart';

/// 函数曲线绘制器
class FunctionPainter extends CustomPainter {
  final String expression; // 数学表达式
  final double scale; // 缩放比例
  final Offset offset; // 平移偏移量
  final double xMin; // x轴最小值
  final double xMax; // x轴最大值
  final int sampleCount; // 采样点数(越多曲线越平滑)

  FunctionPainter({
    required this.expression,
    this.scale = 1.0,
    this.offset = Offset.zero,
    this.xMin = -10.0,
    this.xMax = 10.0,
    this.sampleCount = 500,
  });

  // 绘制坐标系
  void _drawCoordinateSystem(Canvas canvas, Size size, Paint paint) {
    // 画布中心
    final centerX = size.width / 2 + offset.dx;
    final centerY = size.height / 2 + offset.dy;

    // 绘制x轴
    paint
      ..color = Colors.grey[700]!
      ..strokeWidth = 1.0;
    canvas.drawLine(
      Offset(0, centerY),
      Offset(size.width, centerY),
      paint,
    );

    // 绘制y轴
    canvas.drawLine(
      Offset(centerX, 0),
      Offset(centerX, size.height),
      paint,
    );

    // 绘制刻度(简化版,仅示例)
    const tickStep = 1.0;
    for (double x = xMin; x <= xMax; x += tickStep) {
      final screenX = centerX + x * scale;
      if (screenX >= 0 && screenX <= size.width) {
        canvas.drawLine(
          Offset(screenX, centerY - 5),
          Offset(screenX, centerY + 5),
          paint,
        );
      }
    }
    for (double y = -10; y <= 10; y += tickStep) {
      final screenY = centerY - y * scale;
      if (screenY >= 0 && screenY <= size.height) {
        canvas.drawLine(
          Offset(centerX - 5, screenY),
          Offset(centerX + 5, screenY),
          paint,
        );
      }
    }
  }

  // 绘制函数曲线
  void _drawFunctionCurve(Canvas canvas, Size size, Paint paint) {
    if (!MathExpressionParser.validateExpression(expression)) return;

    final centerX = size.width / 2 + offset.dx;
    final centerY = size.height / 2 + offset.dy;
    final step = (xMax - xMin) / sampleCount; // 采样步长

    Path path = Path();
    bool isFirstPoint = true;

    for (int i = 0; i <= sampleCount; i++) {
      final x = xMin + i * step;
      final y = MathExpressionParser.calculateExpression(expression, x);

      if (y == null || y.isInfinite || y.isNaN) {
        isFirstPoint = true;
        continue;
      }

      // 转换为屏幕坐标(y轴反向,因为Flutter画布y轴向下)
      final screenX = centerX + x * scale;
      final screenY = centerY - y * scale;

      if (isFirstPoint) {
        path.moveTo(screenX, screenY);
        isFirstPoint = false;
      } else {
        path.lineTo(screenX, screenY);
      }
    }

    // 绘制曲线
    paint
      ..color = Colors.blueAccent
      ..strokeWidth = 2.0
      ..style = PaintingStyle.stroke;
    canvas.drawPath(path, paint);
  }

  
  void paint(Canvas canvas, Size size) {
    final paint = Paint();

    // 1. 绘制坐标系
    _drawCoordinateSystem(canvas, size, paint);

    // 2. 绘制函数曲线
    _drawFunctionCurve(canvas, size, paint);
  }

  
  bool shouldRepaint(covariant FunctionPainter oldDelegate) {
    // 当表达式、缩放、偏移等参数变化时重绘
    return oldDelegate.expression != expression ||
        oldDelegate.scale != scale ||
        oldDelegate.offset != offset ||
        oldDelegate.xMin != xMin ||
        oldDelegate.xMax != xMax;
  }
}
4.2.3 主页面(交互逻辑)

创建 math\_graph\_page\.dart,实现输入、交互和渲染整合:

import 'package:flutter/material.dart';
import 'function_painter.dart';

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

  
  State<MathGraphPage> createState() => _MathGraphPageState();
}

class _MathGraphPageState extends State<MathGraphPage> {
  final TextEditingController _expressionController =
      TextEditingController(text: "sin(x) * x");
  double _scale = 1.0;
  Offset _offset = Offset.zero;
  Offset? _lastOffset; // 上一次平移偏移量

  // 处理缩放手势
  void _onScaleUpdate(ScaleUpdateDetails details) {
    setState(() {
      // 限制缩放范围,避免过度缩放
      _scale = (_scale * details.scale).clamp(0.5, 5.0);
      if (_lastOffset != null) {
        _offset = Offset(
          _offset.dx + (details.focalPoint.dx - _lastOffset!.dx),
          _offset.dy + (details.focalPoint.dy - _lastOffset!.dy),
        );
      }
      _lastOffset = details.focalPoint;
    });
  }

  // 处理缩放结束
  void _onScaleEnd(ScaleEndDetails details) {
    _lastOffset = null;
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("Harmony MathGraph"),
        backgroundColor: Colors.blueAccent,
      ),
      body: Column(
        children: [
          // 表达式输入框
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: TextField(
              controller: _expressionController,
              decoration: InputDecoration(
                labelText: "输入数学表达式(如 sin(x)*x、x^2)",
                border: const OutlineInputBorder(),
                errorText: MathExpressionParser.validateExpression(
                        _expressionController.text)
                    ? null
                    : "表达式格式错误",
                suffixIcon: IconButton(
                  icon: const Icon(Icons.refresh),
                  onPressed: () {
                    setState(() {
                      _scale = 1.0;
                      _offset = Offset.zero;
                    });
                  },
                ),
              ),
              onChanged: (value) {
                setState(() {}); // 输入变化时重绘
              },
            ),
          ),

          // 绘图区域(占满剩余空间)
          Expanded(
            child: GestureDetector(
              onScaleUpdate: _onScaleUpdate,
              onScaleEnd: _onScaleEnd,
              child: CustomPaint(
                painter: FunctionPainter(
                  expression: _expressionController.text,
                  scale: _scale,
                  offset: _offset,
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }

  
  void dispose() {
    _expressionController.dispose();
    super.dispose();
  }
}
4.2.4 入口函数(鸿蒙适配)

修改 main\.dart,配置鸿蒙应用入口:

import 'package:flutter/material.dart';
import 'math_graph_page.dart';

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Harmony MathGraph',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        // 鸿蒙适配:跟随系统主题
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.blueAccent,
          brightness: Brightness.light,
        ),
      ),
      home: const MathGraphPage(),
      // 鸿蒙适配:隐藏debug标签
      debugShowCheckedModeBanner: false,
    );
  }
}

4.3 鸿蒙环境适配

4.3.1 编译配置

在项目根目录的 ohos 文件夹中,修改 build\-profile\.json5,确保 Flutter 鸿蒙编译配置正确:

{
  "apiType": "stageMode",
  "buildMode": "debug",
  "product": "default",
  "compileSdkVersion": 10,
  "compilerVersion": "10",
  "targetSdkVersion": 10,
  "minSdkVersion": 9,
  "flutter": {
    "root": "../flutter",
    "mode": "debug",
    "dartDefines": [],
    "entrypoint": "lib/main.dart"
  }
}
4.3.2 运行调试
  1. 安装鸿蒙开发工具 DevEco Studio,并配置 Flutter 鸿蒙插件;

  2. 连接鸿蒙虚拟机(或真机);

  3. 执行 flutter run \-d ohos 启动应用;

  4. 在鸿蒙设备上测试表达式输入、曲线绘制、手势交互等功能。

5. 功能扩展建议

5.1 进阶功能

  1. 多函数对比:支持同时输入多个表达式,用不同颜色绘制曲线,方便对比;

  2. 自定义值域:添加x/y轴取值范围设置,精准聚焦函数特定区间;

  3. 极值点标注:自动计算并标注函数的极值点、零点、拐点等关键坐标;

  4. 公式保存/导出:支持将绘制的函数图像导出为图片,或保存表达式到本地。

5.2 性能优化

  1. 采样优化:根据缩放比例动态调整采样点数(缩放越大,采样点越多);

  2. 离屏渲染:使用 RepaintBoundary 隔离绘图区域,避免整页重绘;

  3. 表达式缓存:缓存解析后的表达式,避免重复解析相同字符串;

  4. 鸿蒙专项优化:针对鸿蒙方舟编译器进行代码优化,提升渲染帧率。

6. 常见问题与解决方案

问题现象 原因分析 解决方案
表达式解析报错 语法不兼容(如使用^而非**) 在解析工具类中自动替换特殊符号,或提示用户使用标准语法
曲线绘制卡顿 采样点数过多/缩放逻辑未优化 动态调整采样点数,使用RepaintBoundary隔离重绘区域
鸿蒙设备运行闪退 编译版本不兼容/权限缺失 升级Flutter鸿蒙插件,确保compileSdkVersion与设备系统版本匹配
手势交互不灵敏 手势识别区域冲突/缩放逻辑漏洞 优化GestureDetector的手势优先级,增加缩放/平移的边界校验

7. 总结

本文通过 Flutter + 鸿蒙的技术组合,基于 expressions 三方库实现了跨端动态数学函数曲线拟合器,核心价值在于:

  1. 跨端高效开发:Flutter 一套代码适配鸿蒙/Android/iOS,降低多端开发成本;

  2. 动态表达式解析:借助 expressions 库快速实现字符串公式到可执行逻辑的转化,避免手写大量解析规则;

  3. 鸿蒙生态适配:通过简单的配置和优化,Flutter 应用可无缝运行在 HarmonyOS Next 环境中。

该方案不仅适用于数学函数可视化场景,还可扩展到教育类APP、工程计算工具等领域,为鸿蒙生态下的跨端数学可视化开发提供了轻量化、易落地的参考方案。

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

Logo

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

更多推荐