Flutter for OpenHarmony 量角器应用开发实战:8大功能打造专业角度测量工具

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

作者:maaath

一、引言

在日常工作和学习中,角度测量是一个常见需求——设计师需要测量图纸角度,建筑工人需要检查屋顶坡度,学生需要验证几何定理。然而,随身携带一个实体量角器并不方便。借助 Flutter for OpenHarmony 跨平台框架,我们可以在鸿蒙设备上开发一款功能完备的量角器应用,将手机或平板变成随身携带的精确测量工具。

本文将从零开始,详细介绍如何使用 Flutter 在 OpenHarmony 平台上构建一款量角器应用,涵盖角度精确测量、拍照测量、历史记录、屏幕校准、单位切换、多角度同时测量、数据导出和语音播报等 8 大核心功能。所有代码均已在 OpenHarmony 设备上验证通过,完整源码请访问 AtomGit(https://atomgit.com)获取。

二、应用架构设计

在开始编码之前,我们先设计应用的架构。量角器应用采用经典的单例服务模式 + 页面分离架构:

  • 数据模型层(Models):定义角度线、测量记录、校准数据、导出格式等核心数据结构
  • 业务服务层(Services):管理测量历史、校准参数、单位设置、语音开关等全局状态
  • UI 展示层(Pages):包含主测量页面、拍照测量页面、历史记录页面、校准页面、导出页面

这种分层架构的优势在于:数据与 UI 分离,服务层作为单例可以在不同页面间共享状态,代码结构清晰,便于后续功能扩展。

三、核心数据模型设计

首先,我们定义量角器的核心数据模型。角度测量涉及两个关键概念:角度线(AngleLine)测量记录(AngleMeasurement)

// 角度线:由起点和终点定义的一条射线
class AngleLine {
  final String id;
  double startX;
  double startY;
  double endX;
  double endY;
  int colorValue;

  AngleLine({
    required this.id,
    required this.startX,
    required this.startY,
    required this.endX,
    required this.endY,
    this.colorValue = 0xFF4A90D9,
  });

  // 计算该线与水平方向的夹角(度)
  double get angle {
    final dx = endX - startX;
    final dy = endY - startY;
    return atan2(dy, dx) * 180.0 / pi;
  }
}

// 测量记录:包含两条或多条角度线及其测量结果
class AngleMeasurement {
  final String id;
  final List<AngleLine> lines;
  final DateTime timestamp;
  final MeasurementMode mode;
  String? note;
  bool isFavorite;

  // 计算两条线之间的夹角
  double get angleDegrees {
    if (lines.length < 2) return 0;
    final angle1 = lines[0].angle;
    final angle2 = lines[1].angle;
    var diff = (angle2 - angle1).abs();
    if (diff > 180) diff = 360 - diff;
    return double.parse(diff.toStringAsFixed(2));
  }
}

AngleLine 通过 atan2 函数计算每条线与水平方向的夹角,AngleMeasurement 则通过两条线的角度差计算出最终夹角。代码中还对超过 180° 的情况做了处理,确保始终返回较小的夹角值。

四、量角器刻度盘绘制

量角器的核心视觉组件是刻度盘。我们使用 Flutter 的 CustomPainter 在 Canvas 上绘制一个 360° 的量角器刻度盘,包含主刻度(每 30°)、次刻度(每 10°)和细刻度(每 5°)。

class _ProtractorPainter extends CustomPainter {
  final double radius;
  final double centerX;
  final double centerY;

  void _drawProtractorScale(Canvas canvas) {
    final center = Offset(centerX, centerY);

    // 绘制外圈刻度
    for (int i = 0; i < 360; i += 5) {
      final rad = i * pi / 180.0;
      final isMajor = i % 30 == 0;    // 主刻度
      final isMedium = i % 10 == 0;   // 中刻度
      final innerR = isMajor ? radius - 15 : (isMedium ? radius - 10 : radius - 5);
      final outerR = radius + 5;

      final inner = Offset(centerX + innerR * cos(rad), centerY + innerR * sin(rad));
      final outer = Offset(centerX + outerR * cos(rad), centerY + outerR * sin(rad));

      tickPaint.strokeWidth = isMajor ? 1.5 : 0.5;
      canvas.drawLine(inner, outer, tickPaint);

      // 主刻度旁标注角度数值
      if (isMajor) {
        final labelPos = Offset(
          centerX + (radius - 25) * cos(rad),
          centerY + (radius - 25) * sin(rad),
        );
        // 绘制角度标签...
      }
    }
  }
}

通过三角函数 cossin 计算每个刻度线的起点和终点位置,配合 strokeWidth 区分不同层级的刻度,实现了与传统量角器一致的视觉体验。

五、交互式角度测量

用户可以通过拖拽测量线的端点来调整角度,实现交互式测量。每个端点都是一个可拖拽的圆形控件:

Widget _buildDraggablePoint(double x, double y, int lineIndex) {
  return Positioned(
    left: x - 16,
    top: y - 16,
    child: GestureDetector(
      onTap: () => setState(() => _selectedLineIndex = lineIndex),
      onPanUpdate: (details) {
        setState(() {
          final newX = (x + details.delta.dx).clamp(20.0, 340.0);
          final newY = (y + details.delta.dy).clamp(20.0, 420.0);
          _lines[lineIndex].endX = newX;
          _lines[lineIndex].endY = newY;
        });
      },
      child: Container(
        width: 32,
        height: 32,
        decoration: BoxDecoration(
          color: Color(_lines[lineIndex].colorValue),
          shape: BoxShape.circle,
          border: Border.all(color: Colors.white, width: 3),
        ),
      ),
    ),
  );
}

GestureDetectoronPanUpdate 回调实时更新端点坐标,setState 触发 UI 重绘,从而实现流畅的拖拽交互体验。端点坐标通过 clamp 限制在有效范围内,防止拖拽出界。

六、拍照测量模式

拍照测量模式允许用户在照片上标记测量点,自动计算角度。我们模拟了一个建筑屋顶场景,用户点击画面添加测量点,系统自动连线并计算角度:

void _addPoint(Offset point) {
  setState(() {
    _points.add(point);
    if (_points.length >= 2) {
      final lastTwo = _points.sublist(_points.length - 2);
      _lines.add(AngleLine(
        id: 'photo_line_${_lines.length}',
        startX: lastTwo[0].dx,
        startY: lastTwo[0].dy,
        endX: lastTwo[1].dx,
        endY: lastTwo[1].dy,
        colorValue: _lineColors[_lines.length % _lineColors.length],
      ));
    }
  });
}

每点击两个点形成一条测量线,至少需要四条点(两条线)才能计算出一个角度。这种设计模拟了真实场景中在照片上画辅助线测量的过程。

七、屏幕校准功能

由于不同设备的屏幕尺寸和触控精度存在差异,我们提供了屏幕校准功能。校准采用 5 点校准法——用户依次点击屏幕上显示的 5 个十字准星标记,系统自动计算偏移量和缩放因子:

void _calculateCalibration() {
  double sumDx = 0;
  double sumDy = 0;

  for (int i = 0; i < _calibrationPoints.length; i++) {
    sumDx += _targetPoints[i].dx - _calibrationPoints[i].dx;
    sumDy += _targetPoints[i].dy - _calibrationPoints[i].dy;
  }

  _offsetX = sumDx / _calibrationPoints.length;
  _offsetY = sumDy / _calibrationPoints.length;

  // 计算缩放因子
  final actualDist = (_calibrationPoints[1] - _calibrationPoints[0]).distance;
  final targetDist = (_targetPoints[1] - _targetPoints[0]).distance;
  _scaleFactor = targetDist / actualDist;

  _service.updateCalibration(_offsetX, _offsetY, _scaleFactor);
}

校准参数通过 ProtractorService 单例保存,后续所有测量都会自动应用校准补偿,确保在不同设备上都能获得准确的测量结果。

八、数据导出与语音播报

应用支持将测量历史导出为 CSV 或 JSON 格式,方便用户进行数据分析或存档:

String generateCsvExport() {
  final buffer = StringBuffer();
  buffer.writeln('ID,角度(度),角度(弧度),模式,时间,备注');
  for (final m in _history) {
    buffer.writeln(
      '${m.id},${m.angleDegrees},${m.getAngleInUnit(AngleUnit.radian)},'
      '${m.mode.label},${m.timestamp.toIso8601String()},${m.note ?? ""}'
    );
  }
  return buffer.toString();
}

语音播报功能则在测量完成后通过对话框播报测量结果,提升用户体验。用户可以在设置中随时开关语音功能。

九、运行效果截图

以下是在 OpenHarmony 设备上运行量角器应用的截图:

功能模块 截图

主测量页面 | 量角器刻度盘清晰显示,两条测量线分别用蓝色和红色标识,夹角区域高亮显示,顶部实时显示角度数值 45.00° |
在这里插入图片描述

| 拍照测量 | 深色主题的拍照测量界面,网格背景上已标记测量点,系统自动连线并计算角度 |
在这里插入图片描述

| 历史记录 | 列表展示所有测量记录,包含角度值、测量模式标签、时间戳,支持收藏和语音回放 |
在这里插入图片描述

| 屏幕校准 | 5 点校准界面,绿色标记已校准点,橙色标记当前待校准点,校准完成后显示偏移量和缩放因子 |
在这里插入图片描述
在这里插入图片描述

| 数据导出 | CSV/JSON 格式选择,全部记录/仅收藏范围选择,导出结果预览和复制功能 |
在这里插入图片描述

注:由于截图文件较大,完整截图请查看项目仓库中的 screenshots/ 目录。

十、总结与展望

本文详细介绍了如何使用 Flutter for OpenHarmony 跨平台框架开发一款功能完备的量角器应用。通过 CustomPainter 绘制刻度盘、GestureDetector 实现交互拖拽、单例服务模式管理全局状态,我们成功在鸿蒙设备上实现了 8 大核心功能。

Flutter for OpenHarmony 为开发者提供了强大的跨平台开发能力,一套代码即可同时在 Android、iOS 和 OpenHarmony 设备上运行。通过本文的实践,读者可以掌握以下技能:

  1. 使用 CustomPainter 进行自定义绘制
  2. 实现拖拽交互和实时角度计算
  3. 单例服务模式在 Flutter 中的应用
  4. 数据导出(CSV/JSON)的实现
  5. 屏幕校准算法的设计与实现

未来可以进一步扩展的功能包括:AR 增强现实测量、云端数据同步、角度测量精度优化、更多导出格式支持等。欢迎访问 AtomGit(https://atomgit.com)获取完整源码,加入开源鸿蒙跨平台社区(https://openharmonycrossplatform.csdn.net)交流讨论,共同推动 Flutter for OpenHarmony 生态发展。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐