Flutter 框架跨平台鸿蒙开发 - 屏幕尺子工具应用开发教程

Flutter屏幕尺子工具应用开发教程

项目简介

这是一款功能完整的屏幕尺子工具应用,为用户提供精确的屏幕测量功能。应用采用Material Design 3设计风格,支持多种测量单位、智能校准、测量历史记录等功能,界面简洁专业,操作直观便捷。
运行效果图



核心特性

  • 精确测量:支持厘米、毫米、英寸、像素、点等多种单位
  • 智能校准:基于设备信息自动校准,支持手动精确校准
  • 实时显示:实时显示距离、角度、像素等测量信息
  • 测量历史:保存测量记录,支持查看、复制、删除操作
  • 网格背景:可选显示测量网格和刻度数字
  • 个性化设置:自定义尺子颜色、粗细、反馈方式
  • 触觉反馈:支持震动和声音反馈增强体验
  • 设备信息:显示屏幕尺寸、像素密度等设备参数
  • 专业界面:渐变设计和动画效果提升用户体验

技术栈

  • Flutter 3.x
  • Material Design 3
  • CustomPainter 自定义绘制
  • 手势识别与处理
  • 动画控制器
  • 设备信息获取

项目架构

RulerHomePage
RulerPage
HistoryPage
CalibrationPage
SettingsPage
MeasurementArea
GridBackground
MeasurementInfo
GestureDetector
MeasurementPainter
PointIndicator
HistoryList
HistoryItem
AutoCalibration
ManualCalibration
DeviceInfo
UnitSettings
AppearanceSettings
FeedbackSettings
MeasurementUnit
RulerSettings
MeasurementResult
GridPainter
MeasurementPainter

数据模型设计

MeasurementUnit(测量单位模型)

dart 复制代码
class MeasurementUnit {
  final String name;                 // 单位名称(中文)
  final String symbol;               // 单位符号
  final double pixelsPerUnit;        // 每单位像素数
  final int precision;               // 显示精度(小数位数)
}

支持的测量单位

  • 厘米 (cm):37.8 像素/厘米,精度1位小数
  • 毫米 (mm):3.78 像素/毫米,精度0位小数
  • 英寸 (in):96.0 像素/英寸,精度2位小数
  • 像素 (px):1.0 像素/像素,精度0位小数
  • 点 (pt):1.33 像素/点,精度1位小数

RulerSettings(尺子设置模型)

dart 复制代码
class RulerSettings {
  final MeasurementUnit unit;        // 当前测量单位
  final bool showGrid;               // 是否显示网格
  final bool showNumbers;            // 是否显示刻度数字
  final Color rulerColor;            // 尺子颜色
  final double rulerWidth;           // 尺子线条粗细
  final bool vibrationEnabled;       // 是否启用震动反馈
  final bool soundEnabled;           // 是否启用声音反馈
}

MeasurementResult(测量结果模型)

dart 复制代码
class MeasurementResult {
  final double distance;             // 测量距离
  final MeasurementUnit unit;        // 测量单位
  final Offset startPoint;           // 起始点坐标
  final Offset endPoint;             // 结束点坐标
  final DateTime timestamp;          // 测量时间
  
  String get formattedDistance;      // 格式化距离显示
  double get angle;                  // 测量角度
}

核心功能实现

1. 手势识别与测量

实现触摸拖拽测量功能,支持实时显示测量结果。

dart 复制代码
Widget _buildRulerPage() {
  return Column(
    children: [
      _buildRulerHeader(),
      Expanded(
        child: Stack(
          children: [
            // 背景网格
            if (_settings.showGrid) _buildGridBackground(),
            
            // 测量区域
            GestureDetector(
              onPanStart: _onPanStart,
              onPanUpdate: _onPanUpdate,
              onPanEnd: _onPanEnd,
              child: Container(
                width: double.infinity,
                height: double.infinity,
                color: Colors.transparent,
              ),
            ),
            
            // 测量线和标注
            if (_startPoint != null && _endPoint != null)
              CustomPaint(
                painter: MeasurementPainter(
                  startPoint: _startPoint!,
                  endPoint: _endPoint!,
                  settings: _settings,
                  calibrationFactor: _calibrationFactor,
                ),
                size: Size.infinite,
              ),
            
            // 起始点指示器
            if (_startPoint != null) _buildStartPointIndicator(),
          ],
        ),
      ),
      _buildMeasurementInfo(),
    ],
  );
}

手势处理逻辑

dart 复制代码
void _onPanStart(DragStartDetails details) {
  setState(() {
    _startPoint = details.localPosition;
    _endPoint = details.localPosition;
    _isMeasuring = true;
  });
  
  if (_settings.vibrationEnabled) {
    HapticFeedback.lightImpact(); // 轻触觉反馈
  }
}

void _onPanUpdate(DragUpdateDetails details) {
  setState(() {
    _endPoint = details.localPosition;
  });
}

void _onPanEnd(DragEndDetails details) {
  setState(() {
    _isMeasuring = false;
  });
  
  if (_settings.vibrationEnabled) {
    HapticFeedback.mediumImpact(); // 中等触觉反馈
  }
}

2. 自定义绘制器

使用CustomPainter实现测量线、标注和网格的绘制。

dart 复制代码
class MeasurementPainter extends CustomPainter {
  final Offset startPoint;
  final Offset endPoint;
  final RulerSettings settings;
  final double calibrationFactor;

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = settings.rulerColor
      ..strokeWidth = settings.rulerWidth
      ..style = PaintingStyle.stroke;

    // 绘制测量线
    canvas.drawLine(startPoint, endPoint, paint);

    // 绘制端点圆圈
    final pointPaint = Paint()
      ..color = settings.rulerColor
      ..style = PaintingStyle.fill;

    canvas.drawCircle(startPoint, 6, pointPaint);
    canvas.drawCircle(endPoint, 6, pointPaint);

    // 绘制距离标签
    final dx = endPoint.dx - startPoint.dx;
    final dy = endPoint.dy - startPoint.dy;
    final distance = sqrt(dx * dx + dy * dy);
    final actualDistance = distance / (settings.unit.pixelsPerUnit * calibrationFactor);
    
    final midPoint = Offset(
      (startPoint.dx + endPoint.dx) / 2,
      (startPoint.dy + endPoint.dy) / 2,
    );

    // 绘制距离文本
    final textPainter = TextPainter(
      text: TextSpan(
        text: '${actualDistance.toStringAsFixed(settings.unit.precision)} ${settings.unit.symbol}',
        style: TextStyle(
          color: settings.rulerColor,
          fontSize: 16,
          fontWeight: FontWeight.bold,
          backgroundColor: Colors.white.withValues(alpha: 0.8),
        ),
      ),
      textDirection: TextDirection.ltr,
    );

    textPainter.layout();
    textPainter.paint(canvas, Offset(
      midPoint.dx - textPainter.width / 2,
      midPoint.dy - textPainter.height / 2 - 20,
    ));

    // 绘制角度标识
    if (distance > 50) {
      final angle = atan2(dy, dx);
      final angleText = TextPainter(
        text: TextSpan(
          text: '${(angle * 180 / pi).toStringAsFixed(1)}°',
          style: TextStyle(
            color: settings.rulerColor.withValues(alpha: 0.7),
            fontSize: 12,
            backgroundColor: Colors.white.withValues(alpha: 0.8),
          ),
        ),
        textDirection: TextDirection.ltr,
      );

      angleText.layout();
      angleText.paint(canvas, Offset(
        midPoint.dx - angleText.width / 2,
        midPoint.dy + 25,
      ));
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

3. 网格背景绘制

绘制测量网格和刻度标识,提供视觉参考。

dart 复制代码
class GridPainter extends CustomPainter {
  final MeasurementUnit unit;
  final double calibrationFactor;
  final bool showNumbers;

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.grey.withValues(alpha: 0.3)
      ..strokeWidth = 0.5;

    final unitSize = unit.pixelsPerUnit * calibrationFactor;
    
    // 绘制垂直线
    for (double x = 0; x <= size.width; x += unitSize) {
      canvas.drawLine(Offset(x, 0), Offset(x, size.height), paint);

      // 绘制数字标签
      if (showNumbers && x > 0) {
        final value = x / unitSize;
        final textPainter = TextPainter(
          text: TextSpan(
            text: value.toStringAsFixed(unit.precision == 0 ? 0 : 1),
            style: TextStyle(color: Colors.grey.shade600, fontSize: 10),
          ),
          textDirection: TextDirection.ltr,
        );
        textPainter.layout();
        textPainter.paint(canvas, Offset(x + 2, 2));
      }
    }

    // 绘制水平线
    for (double y = 0; y <= size.height; y += unitSize) {
      canvas.drawLine(Offset(0, y), Offset(size.width, y), paint);

      // 绘制数字标签
      if (showNumbers && y > 0) {
        final value = y / unitSize;
        final textPainter = TextPainter(
          text: TextSpan(
            text: value.toStringAsFixed(unit.precision == 0 ? 0 : 1),
            style: TextStyle(color: Colors.grey.shade600, fontSize: 10),
          ),
          textDirection: TextDirection.ltr,
        );
        textPainter.layout();
        textPainter.paint(canvas, Offset(2, y + 2));
      }
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

4. 智能校准系统

基于设备信息自动计算校准系数,提高测量精度。

dart 复制代码
void _autoCalibrate() {
  final mediaQuery = MediaQuery.of(context);
  final devicePixelRatio = mediaQuery.devicePixelRatio;
  
  // 基于设备像素密度进行校准
  final dpi = 160 * devicePixelRatio;
  final actualPixelsPerCm = dpi / 2.54;
  
  setState(() {
    _calibrationFactor = actualPixelsPerCm / _settings.unit.pixelsPerUnit;
  });
}

校准原理

  • 获取设备像素密度比例
  • 计算实际DPI(每英寸点数)
  • 转换为每厘米像素数
  • 与理论值比较得出校准系数

5. 测量结果计算

实时计算距离、角度等测量参数。

dart 复制代码
MeasurementResult? _getCurrentMeasurement() {
  if (_startPoint == null || _endPoint == null) return null;
  
  final pixelDistance = _getPixelDistance();
  final distance = pixelDistance / (_settings.unit.pixelsPerUnit * _calibrationFactor);
  
  return MeasurementResult(
    distance: distance,
    unit: _settings.unit,
    startPoint: _startPoint!,
    endPoint: _endPoint!,
    timestamp: DateTime.now(),
  );
}

double _getPixelDistance() {
  if (_startPoint == null || _endPoint == null) return 0.0;
  
  final dx = _endPoint!.dx - _startPoint!.dx;
  final dy = _endPoint!.dy - _startPoint!.dy;
  return sqrt(dx * dx + dy * dy);
}

6. 动画效果

为起始点添加脉冲动画效果,增强视觉反馈。

dart 复制代码
@override
void initState() {
  super.initState();
  _pulseController = AnimationController(
    duration: const Duration(milliseconds: 1000),
    vsync: this,
  );
  _pulseAnimation = Tween<double>(begin: 0.8, end: 1.2).animate(
    CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut),
  );
  _pulseController.repeat(reverse: true);
}

Widget _buildStartPointIndicator() {
  return Positioned(
    left: _startPoint!.dx - 15,
    top: _startPoint!.dy - 15,
    child: AnimatedBuilder(
      animation: _pulseAnimation,
      builder: (context, child) {
        return Transform.scale(
          scale: _isMeasuring ? _pulseAnimation.value : 1.0,
          child: Container(
            width: 30, height: 30,
            decoration: BoxDecoration(
              color: _settings.rulerColor.withValues(alpha: 0.3),
              shape: BoxShape.circle,
              border: Border.all(color: _settings.rulerColor, width: 2),
            ),
            child: Icon(Icons.my_location, color: _settings.rulerColor, size: 16),
          ),
        );
      },
    ),
  );
}

7. 测量历史管理

保存和管理测量记录,支持查看、复制、删除操作。

dart 复制代码
Widget _buildHistoryPage() {
  return Column(
    children: [
      _buildHistoryHeader(),
      Expanded(
        child: _measurementHistory.isEmpty
            ? _buildEmptyHistoryState()
            : ListView.builder(
                padding: const EdgeInsets.all(16),
                itemCount: _measurementHistory.length,
                itemBuilder: (context, index) {
                  final measurement = _measurementHistory[_measurementHistory.length - 1 - index];
                  return _buildHistoryItem(measurement, index);
                },
              ),
      ),
    ],
  );
}

Widget _buildHistoryItem(MeasurementResult measurement, int index) {
  return Card(
    margin: const EdgeInsets.only(bottom: 12),
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Container(
                width: 40, height: 40,
                decoration: BoxDecoration(
                  color: Colors.blue.shade100,
                  borderRadius: BorderRadius.circular(20),
                ),
                child: Center(
                  child: Text('${index + 1}', 
                             style: TextStyle(fontWeight: FontWeight.bold, color: Colors.blue.shade700)),
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(measurement.formattedDistance, 
                         style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                    Text(_formatDateTime(measurement.timestamp), 
                         style: TextStyle(fontSize: 12, color: Colors.grey.shade600)),
                  ],
                ),
              ),
              PopupMenuButton<String>(
                onSelected: (value) {
                  if (value == 'delete') {
                    _deleteMeasurement(measurement);
                  } else if (value == 'copy') {
                    _copyMeasurement(measurement);
                  }
                },
                itemBuilder: (context) => [
                  const PopupMenuItem(value: 'copy', child: Row(children: [Icon(Icons.copy, size: 20), SizedBox(width: 8), Text('复制')])),
                  const PopupMenuItem(value: 'delete', child: Row(children: [Icon(Icons.delete, size: 20, color: Colors.red), SizedBox(width: 8), Text('删除', style: TextStyle(color: Colors.red))])),
                ],
              ),
            ],
          ),
          const SizedBox(height: 12),
          Row(
            children: [
              _buildMeasurementTag('角度', '${measurement.angle.toStringAsFixed(1)}°', Icons.rotate_right, Colors.green),
              const SizedBox(width: 8),
              _buildMeasurementTag('单位', measurement.unit.symbol, Icons.straighten, Colors.blue),
              const SizedBox(width: 8),
              _buildMeasurementTag('像素', '${_calculatePixelDistance(measurement).toStringAsFixed(0)}px', Icons.grid_on, Colors.orange),
            ],
          ),
        ],
      ),
    ),
  );
}

历史记录功能

  • 自动保存测量结果
  • 按时间倒序显示
  • 支持复制到剪贴板
  • 支持单个删除和批量清除
  • 显示详细测量信息

8. 校准页面实现

提供自动校准和手动校准功能,显示设备信息。

dart 复制代码
Widget _buildCalibrationPage() {
  return Column(
    children: [
      _buildCalibrationHeader(),
      Expanded(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // 自动校准卡片
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          Icon(Icons.auto_fix_high, color: Colors.orange.shade600),
                          const SizedBox(width: 8),
                          const Text('自动校准', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                        ],
                      ),
                      const SizedBox(height: 12),
                      const Text('基于设备屏幕信息自动计算校准系数,适用于大多数设备。', style: TextStyle(color: Colors.grey)),
                      const SizedBox(height: 16),
                      SizedBox(
                        width: double.infinity,
                        child: ElevatedButton.icon(
                          onPressed: _autoCalibrate,
                          icon: const Icon(Icons.refresh),
                          label: const Text('重新自动校准'),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
              
              const SizedBox(height: 16),
              
              // 手动校准卡片
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          Icon(Icons.straighten, color: Colors.blue.shade600),
                          const SizedBox(width: 8),
                          const Text('手动校准', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                        ],
                      ),
                      const SizedBox(height: 12),
                      const Text('使用已知长度的物体进行精确校准,获得更高的测量精度。', style: TextStyle(color: Colors.grey)),
                      const SizedBox(height: 16),
                      SizedBox(
                        width: double.infinity,
                        child: OutlinedButton.icon(
                          onPressed: _showManualCalibrationDialog,
                          icon: const Icon(Icons.rule),
                          label: const Text('开始手动校准'),
                        ),
                      ),
                    ],
                  ),
                ),
              ),
              
              const SizedBox(height: 16),
              
              // 校准系数调整
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          Icon(Icons.settings, color: Colors.green.shade600),
                          const SizedBox(width: 8),
                          const Text('校准系数调整', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                        ],
                      ),
                      const SizedBox(height: 12),
                      Text('当前校准系数: ${_calibrationFactor.toStringAsFixed(3)}', 
                           style: const TextStyle(fontWeight: FontWeight.bold)),
                      const SizedBox(height: 8),
                      Slider(
                        value: _calibrationFactor,
                        min: 0.5, max: 2.0, divisions: 150,
                        label: _calibrationFactor.toStringAsFixed(3),
                        onChanged: (value) { setState(() { _calibrationFactor = value; }); },
                      ),
                      Row(
                        mainAxisAlignment: MainAxisAlignment.spaceBetween,
                        children: [
                          TextButton(onPressed: () { setState(() { _calibrationFactor = 0.5; }); }, child: const Text('最小')),
                          TextButton(onPressed: () { setState(() { _calibrationFactor = 1.0; }); }, child: const Text('重置')),
                          TextButton(onPressed: () { setState(() { _calibrationFactor = 2.0; }); }, child: const Text('最大')),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
              
              const SizedBox(height: 16),
              
              // 设备信息
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Row(
                        children: [
                          Icon(Icons.info_outline, color: Colors.blue.shade600),
                          const SizedBox(width: 8),
                          const Text('设备信息', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                        ],
                      ),
                      const SizedBox(height: 12),
                      _buildDeviceInfoRow('屏幕宽度', '${MediaQuery.of(context).size.width.toStringAsFixed(1)} px'),
                      _buildDeviceInfoRow('屏幕高度', '${MediaQuery.of(context).size.height.toStringAsFixed(1)} px'),
                      _buildDeviceInfoRow('像素密度', '${MediaQuery.of(context).devicePixelRatio.toStringAsFixed(2)}'),
                      _buildDeviceInfoRow('DPI', '${(160 * MediaQuery.of(context).devicePixelRatio).toStringAsFixed(0)}'),
                    ],
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    ],
  );
}

9. 设置页面实现

提供个性化设置选项,包括单位、外观、反馈等。

dart 复制代码
Widget _buildSettingsPage() {
  return Column(
    children: [
      _buildSettingsHeader(),
      Expanded(
        child: ListView(
          padding: const EdgeInsets.all(16),
          children: [
            // 测量单位设置
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        Icon(Icons.straighten, color: Colors.blue.shade600),
                        const SizedBox(width: 8),
                        const Text('测量单位', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                      ],
                    ),
                    const SizedBox(height: 16),
                    Wrap(
                      spacing: 8, runSpacing: 8,
                      children: _units.map((unit) {
                        final isSelected = unit.symbol == _settings.unit.symbol;
                        return FilterChip(
                          label: Text('${unit.name} (${unit.symbol})'),
                          selected: isSelected,
                          onSelected: (selected) {
                            if (selected) {
                              setState(() { _settings = _settings.copyWith(unit: unit); });
                              _autoCalibrate();
                            }
                          },
                        );
                      }).toList(),
                    ),
                  ],
                ),
              ),
            ),
            
            const SizedBox(height: 16),
            
            // 外观设置
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        Icon(Icons.palette, color: Colors.green.shade600),
                        const SizedBox(width: 8),
                        const Text('外观设置', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                      ],
                    ),
                    const SizedBox(height: 16),
                    SwitchListTile(
                      title: const Text('显示网格'),
                      subtitle: const Text('在背景显示测量网格'),
                      value: _settings.showGrid,
                      onChanged: (value) { setState(() { _settings = _settings.copyWith(showGrid: value); }); },
                    ),
                    SwitchListTile(
                      title: const Text('显示数字'),
                      subtitle: const Text('在网格上显示刻度数字'),
                      value: _settings.showNumbers,
                      onChanged: (value) { setState(() { _settings = _settings.copyWith(showNumbers: value); }); },
                    ),
                    const SizedBox(height: 16),
                    const Text('尺子颜色', style: TextStyle(fontWeight: FontWeight.bold)),
                    const SizedBox(height: 8),
                    Wrap(
                      spacing: 8,
                      children: [Colors.blue, Colors.red, Colors.green, Colors.orange, Colors.purple, Colors.teal].map((color) {
                        final isSelected = color.value == _settings.rulerColor.value;
                        return GestureDetector(
                          onTap: () { setState(() { _settings = _settings.copyWith(rulerColor: color); }); },
                          child: Container(
                            width: 40, height: 40,
                            decoration: BoxDecoration(
                              color: color, shape: BoxShape.circle,
                              border: isSelected ? Border.all(color: Colors.black, width: 3) : null,
                            ),
                            child: isSelected ? const Icon(Icons.check, color: Colors.white) : null,
                          ),
                        );
                      }).toList(),
                    ),
                    const SizedBox(height: 16),
                    Text('尺子粗细: ${_settings.rulerWidth.toStringAsFixed(1)}px'),
                    Slider(
                      value: _settings.rulerWidth, min: 1.0, max: 5.0, divisions: 8,
                      label: _settings.rulerWidth.toStringAsFixed(1),
                      onChanged: (value) { setState(() { _settings = _settings.copyWith(rulerWidth: value); }); },
                    ),
                  ],
                ),
              ),
            ),
            
            const SizedBox(height: 16),
            
            // 反馈设置
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        Icon(Icons.feedback, color: Colors.orange.shade600),
                        const SizedBox(width: 8),
                        const Text('反馈设置', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
                      ],
                    ),
                    const SizedBox(height: 16),
                    SwitchListTile(
                      title: const Text('震动反馈'),
                      subtitle: const Text('测量时提供触觉反馈'),
                      value: _settings.vibrationEnabled,
                      onChanged: (value) { setState(() { _settings = _settings.copyWith(vibrationEnabled: value); }); },
                    ),
                    SwitchListTile(
                      title: const Text('声音反馈'),
                      subtitle: const Text('测量时播放提示音'),
                      value: _settings.soundEnabled,
                      onChanged: (value) { setState(() { _settings = _settings.copyWith(soundEnabled: value); }); },
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    ],
  );
}

10. 工具方法实现

实现复制、删除、格式化等辅助功能。

dart 复制代码
// 复制测量结果到剪贴板
void _copyMeasurement(MeasurementResult measurement) {
  final text = '距离: ${measurement.formattedDistance}\n'
      '角度: ${measurement.angle.toStringAsFixed(1)}°\n'
      '时间: ${_formatDateTime(measurement.timestamp)}';
  
  Clipboard.setData(ClipboardData(text: text));
  
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('测量结果已复制到剪贴板'), backgroundColor: Colors.blue),
  );
}

// 删除单个测量记录
void _deleteMeasurement(MeasurementResult measurement) {
  setState(() {
    _measurementHistory.remove(measurement);
  });
  
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('测量记录已删除'), backgroundColor: Colors.orange),
  );
}

// 清除所有历史记录
void _clearHistory() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('清除历史记录'),
      content: const Text('确定要清除所有测量历史记录吗?此操作不可撤销。'),
      actions: [
        TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消')),
        ElevatedButton(
          onPressed: () {
            setState(() { _measurementHistory.clear(); });
            Navigator.pop(context);
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('历史记录已清除'), backgroundColor: Colors.orange),
            );
          },
          style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
          child: const Text('确定', style: TextStyle(color: Colors.white)),
        ),
      ],
    ),
  );
}

// 格式化日期时间
String _formatDateTime(DateTime dateTime) {
  return '${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')} '
      '${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
}

// 显示手动校准对话框
void _showManualCalibrationDialog() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('手动校准'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('请按照以下步骤进行手动校准:'),
          const SizedBox(height: 12),
          const Text('1. 准备一个已知长度的物体(如尺子、硬币等)'),
          const Text('2. 将物体放在屏幕上'),
          const Text('3. 测量物体的长度'),
          const Text('4. 输入物体的实际长度进行校准'),
          const SizedBox(height: 16),
          const Text('建议使用标准尺子或硬币进行校准以获得最佳精度。', 
                     style: TextStyle(fontSize: 12, color: Colors.grey, fontStyle: FontStyle.italic)),
        ],
      ),
      actions: [
        TextButton(onPressed: () => Navigator.pop(context), child: const Text('取消')),
        ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
            setState(() { _selectedIndex = 0; }); // 切换到尺子页面
            ScaffoldMessenger.of(context).showSnackBar(
              const SnackBar(content: Text('请在尺子页面测量已知长度的物体'), backgroundColor: Colors.blue),
            );
          },
          child: const Text('开始校准'),
        ),
      ],
    ),
  );
}

UI组件设计

1. 渐变头部组件

dart 复制代码
Widget _buildRulerHeader() {
  return Container(
    padding: const EdgeInsets.fromLTRB(16, 48, 16, 16),
    decoration: BoxDecoration(
      gradient: LinearGradient(
        colors: [Colors.blue.shade600, Colors.blue.shade400],
        begin: Alignment.topLeft,
        end: Alignment.bottomRight,
      ),
    ),
    child: Column(
      children: [
        Row(
          children: [
            const Icon(Icons.straighten, color: Colors.white, size: 32),
            const SizedBox(width: 12),
            const Expanded(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('屏幕尺子工具', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white)),
                  Text('精确测量屏幕上的距离', style: TextStyle(fontSize: 14, color: Colors.white70)),
                ],
              ),
            ),
            Container(
              padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
              decoration: BoxDecoration(color: Colors.white.withValues(alpha: 0.2), borderRadius: BorderRadius.circular(16)),
              child: Text(_settings.unit.symbol, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
            ),
          ],
        ),
        const SizedBox(height: 16),
        Row(
          children: [
            Expanded(child: _buildHeaderCard('当前单位', _settings.unit.name, Icons.straighten)),
            const SizedBox(width: 12),
            Expanded(child: _buildHeaderCard('测量次数', '${_measurementHistory.length}', Icons.analytics)),
          ],
        ),
      ],
    ),
  );
}

2. 信息展示卡片

dart 复制代码
Widget _buildInfoItem(String label, String value, IconData icon, Color color) {
  return Column(
    children: [
      Container(
        width: 50, height: 50,
        decoration: BoxDecoration(color: color.withValues(alpha: 0.1), shape: BoxShape.circle),
        child: Icon(icon, color: color, size: 24),
      ),
      const SizedBox(height: 8),
      Text(value, style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: color)),
      Text(label, style: TextStyle(fontSize: 12, color: Colors.grey.shade600)),
    ],
  );
}

3. 测量标签组件

dart 复制代码
Widget _buildMeasurementTag(String label, String value, IconData icon, Color color) {
  return Container(
    padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
    decoration: BoxDecoration(color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(12)),
    child: Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        Icon(icon, size: 12, color: color),
        const SizedBox(width: 4),
        Text('$label: $value', style: TextStyle(fontSize: 11, color: color, fontWeight: FontWeight.w500)),
      ],
    ),
  );
}
dart 复制代码
NavigationBar(
  selectedIndex: _selectedIndex,
  onDestinationSelected: (index) { setState(() { _selectedIndex = index; }); },
  destinations: const [
    NavigationDestination(icon: Icon(Icons.straighten_outlined), selectedIcon: Icon(Icons.straighten), label: '尺子'),
    NavigationDestination(icon: Icon(Icons.history_outlined), selectedIcon: Icon(Icons.history), label: '历史'),
    NavigationDestination(icon: Icon(Icons.tune_outlined), selectedIcon: Icon(Icons.tune), label: '校准'),
    NavigationDestination(icon: Icon(Icons.settings_outlined), selectedIcon: Icon(Icons.settings), label: '设置'),
  ],
)

功能扩展建议

1. 高级测量功能

dart 复制代码
class AdvancedMeasurementTools {
  // 面积测量
  double calculateArea(List<Offset> points) {
    if (points.length < 3) return 0.0;
    
    double area = 0.0;
    for (int i = 0; i < points.length; i++) {
      final j = (i + 1) % points.length;
      area += points[i].dx * points[j].dy;
      area -= points[j].dx * points[i].dy;
    }
    return area.abs() / 2.0;
  }
  
  // 角度测量
  double calculateAngle(Offset center, Offset point1, Offset point2) {
    final vector1 = Offset(point1.dx - center.dx, point1.dy - center.dy);
    final vector2 = Offset(point2.dx - center.dx, point2.dy - center.dy);
    
    final dot = vector1.dx * vector2.dx + vector1.dy * vector2.dy;
    final det = vector1.dx * vector2.dy - vector1.dy * vector2.dx;
    
    return atan2(det, dot) * 180 / pi;
  }
  
  // 圆形测量
  Widget buildCircleMeasurementTool() {
    return GestureDetector(
      onPanStart: (details) {
        // 开始圆形测量
      },
      onPanUpdate: (details) {
        // 更新圆形半径
      },
      child: CustomPaint(
        painter: CircleMeasurementPainter(),
        size: Size.infinite,
      ),
    );
  }
}

2. 测量数据导出

dart 复制代码
class MeasurementExporter {
  // 导出为CSV格式
  String exportToCSV(List<MeasurementResult> measurements) {
    final buffer = StringBuffer();
    buffer.writeln('序号,距离,单位,角度,像素,时间');
    
    for (int i = 0; i < measurements.length; i++) {
      final measurement = measurements[i];
      buffer.writeln('${i + 1},${measurement.distance.toStringAsFixed(measurement.unit.precision)},'
          '${measurement.unit.symbol},${measurement.angle.toStringAsFixed(1)},'
          '${_calculatePixelDistance(measurement).toStringAsFixed(0)},'
          '${_formatDateTime(measurement.timestamp)}');
    }
    
    return buffer.toString();
  }
  
  // 导出为JSON格式
  String exportToJSON(List<MeasurementResult> measurements) {
    final data = measurements.map((measurement) => {
      'distance': measurement.distance,
      'unit': measurement.unit.symbol,
      'angle': measurement.angle,
      'pixelDistance': _calculatePixelDistance(measurement),
      'timestamp': measurement.timestamp.toIso8601String(),
      'startPoint': {'x': measurement.startPoint.dx, 'y': measurement.startPoint.dy},
      'endPoint': {'x': measurement.endPoint.dx, 'y': measurement.endPoint.dy},
    }).toList();
    
    return jsonEncode({'measurements': data, 'exportTime': DateTime.now().toIso8601String()});
  }
  
  // 分享测量结果
  void shareMeasurements(List<MeasurementResult> measurements) {
    final text = measurements.map((m) => 
        '距离: ${m.formattedDistance}, 角度: ${m.angle.toStringAsFixed(1)}°'
    ).join('\n');
    
    Share.share(text, subject: '测量结果分享');
  }
}

3. 智能识别功能

dart 复制代码
class SmartRecognition {
  // 物体边缘检测
  List<Offset> detectEdges(ui.Image image) {
    // 使用图像处理算法检测物体边缘
    // 返回边缘点坐标列表
    return [];
  }
  
  // 自动测量建议
  Widget buildMeasurementSuggestions() {
    return Card(
      child: Column(
        children: [
          const Text('智能测量建议', style: TextStyle(fontWeight: FontWeight.bold)),
          ListTile(
            leading: const Icon(Icons.smartphone),
            title: const Text('手机屏幕'),
            subtitle: const Text('建议使用厘米或英寸单位'),
            onTap: () => _applySuggestion('phone'),
          ),
          ListTile(
            leading: const Icon(Icons.credit_card),
            title: const Text('信用卡'),
            subtitle: const Text('标准尺寸: 8.56cm × 5.4cm'),
            onTap: () => _applySuggestion('card'),
          ),
          ListTile(
            leading: const Icon(Icons.monetization_on),
            title: const Text('硬币'),
            subtitle: const Text('1元硬币直径: 2.5cm'),
            onTap: () => _applySuggestion('coin'),
          ),
        ],
      ),
    );
  }
  
  void _applySuggestion(String type) {
    switch (type) {
      case 'phone':
        // 设置为厘米单位,调整校准
        break;
      case 'card':
        // 提供信用卡标准尺寸参考
        break;
      case 'coin':
        // 提供硬币尺寸参考
        break;
    }
  }
}

4. 增强现实(AR)测量

dart 复制代码
class ARMeasurement {
  // AR相机预览
  Widget buildARMeasurementView() {
    return Stack(
      children: [
        CameraPreview(_cameraController),
        CustomPaint(
          painter: ARMeasurementPainter(),
          size: Size.infinite,
        ),
        Positioned(
          bottom: 20,
          left: 20,
          right: 20,
          child: _buildARControls(),
        ),
      ],
    );
  }
  
  // AR测量控制
  Widget _buildARControls() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      children: [
        FloatingActionButton(
          onPressed: _captureARMeasurement,
          child: const Icon(Icons.camera),
        ),
        FloatingActionButton(
          onPressed: _resetARMeasurement,
          child: const Icon(Icons.refresh),
        ),
        FloatingActionButton(
          onPressed: _saveARMeasurement,
          child: const Icon(Icons.save),
        ),
      ],
    );
  }
  
  // 3D空间测量
  void _captureARMeasurement() {
    // 使用ARCore/ARKit进行3D空间测量
    // 计算真实世界坐标
    // 显示3D测量结果
  }
}

5. 云端同步功能

dart 复制代码
class CloudSync {
  // 上传测量数据
  Future<void> uploadMeasurements(List<MeasurementResult> measurements) async {
    try {
      final data = {
        'measurements': measurements.map((m) => m.toJson()).toList(),
        'deviceInfo': await _getDeviceInfo(),
        'timestamp': DateTime.now().toIso8601String(),
      };
      
      final response = await http.post(
        Uri.parse('https://api.ruler-app.com/measurements'),
        headers: {'Content-Type': 'application/json'},
        body: jsonEncode(data),
      );
      
      if (response.statusCode == 200) {
        _showSuccessMessage('测量数据已同步到云端');
      }
    } catch (e) {
      _showErrorMessage('同步失败: $e');
    }
  }
  
  // 下载测量数据
  Future<List<MeasurementResult>> downloadMeasurements() async {
    try {
      final response = await http.get(
        Uri.parse('https://api.ruler-app.com/measurements'),
        headers: {'Authorization': 'Bearer ${await _getAuthToken()}'},
      );
      
      if (response.statusCode == 200) {
        final data = jsonDecode(response.body);
        return (data['measurements'] as List)
            .map((json) => MeasurementResult.fromJson(json))
            .toList();
      }
    } catch (e) {
      _showErrorMessage('下载失败: $e');
    }
    
    return [];
  }
  
  // 设备间同步
  Widget buildSyncSettings() {
    return Card(
      child: Column(
        children: [
          SwitchListTile(
            title: const Text('自动同步'),
            subtitle: const Text('自动将测量数据同步到云端'),
            value: _autoSyncEnabled,
            onChanged: (value) {
              setState(() { _autoSyncEnabled = value; });
            },
          ),
          ListTile(
            leading: const Icon(Icons.cloud_upload),
            title: const Text('手动同步'),
            subtitle: const Text('立即同步所有测量数据'),
            onTap: () => uploadMeasurements(_measurementHistory),
          ),
          ListTile(
            leading: const Icon(Icons.cloud_download),
            title: const Text('恢复数据'),
            subtitle: const Text('从云端恢复测量数据'),
            onTap: _restoreFromCloud,
          ),
        ],
      ),
    );
  }
}

6. 专业工具集成

dart 复制代码
class ProfessionalTools {
  // CAD导入功能
  Widget buildCADImporter() {
    return Card(
      child: Column(
        children: [
          const Text('CAD图纸导入', style: TextStyle(fontWeight: FontWeight.bold)),
          const SizedBox(height: 12),
          ElevatedButton.icon(
            onPressed: _importCADFile,
            icon: const Icon(Icons.upload_file),
            label: const Text('导入DWG/DXF文件'),
          ),
          const SizedBox(height: 8),
          const Text('支持AutoCAD、SolidWorks等格式', style: TextStyle(fontSize: 12, color: Colors.grey)),
        ],
      ),
    );
  }
  
  // 工程计算器
  Widget buildEngineeringCalculator() {
    return Card(
      child: Column(
        children: [
          const Text('工程计算器', style: TextStyle(fontWeight: FontWeight.bold)),
          const SizedBox(height: 12),
          Row(
            children: [
              Expanded(
                child: ElevatedButton(
                  onPressed: () => _showCalculator('area'),
                  child: const Text('面积计算'),
                ),
              ),
              const SizedBox(width: 8),
              Expanded(
                child: ElevatedButton(
                  onPressed: () => _showCalculator('volume'),
                  child: const Text('体积计算'),
                ),
              ),
            ],
          ),
          const SizedBox(height: 8),
          Row(
            children: [
              Expanded(
                child: ElevatedButton(
                  onPressed: () => _showCalculator('angle'),
                  child: const Text('角度计算'),
                ),
              ),
              const SizedBox(width: 8),
              Expanded(
                child: ElevatedButton(
                  onPressed: () => _showCalculator('scale'),
                  child: const Text('比例换算'),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
  
  // 测量报告生成
  Future<void> generateMeasurementReport(List<MeasurementResult> measurements) async {
    final pdf = pw.Document();
    
    pdf.addPage(
      pw.Page(
        build: (pw.Context context) {
          return pw.Column(
            crossAxisAlignment: pw.CrossAxisAlignment.start,
            children: [
              pw.Text('测量报告', style: pw.TextStyle(fontSize: 24, fontWeight: pw.FontWeight.bold)),
              pw.SizedBox(height: 20),
              pw.Text('生成时间: ${DateTime.now().toString()}'),
              pw.SizedBox(height: 20),
              pw.Table(
                border: pw.TableBorder.all(),
                children: [
                  pw.TableRow(
                    children: [
                      pw.Text('序号', style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
                      pw.Text('距离', style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
                      pw.Text('角度', style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
                      pw.Text('时间', style: pw.TextStyle(fontWeight: pw.FontWeight.bold)),
                    ],
                  ),
                  ...measurements.asMap().entries.map((entry) {
                    final index = entry.key;
                    final measurement = entry.value;
                    return pw.TableRow(
                      children: [
                        pw.Text('${index + 1}'),
                        pw.Text(measurement.formattedDistance),
                        pw.Text('${measurement.angle.toStringAsFixed(1)}°'),
                        pw.Text(_formatDateTime(measurement.timestamp)),
                      ],
                    );
                  }),
                ],
              ),
            ],
          );
        },
      ),
    );
    
    final bytes = await pdf.save();
    await _savePDFFile(bytes, 'measurement_report.pdf');
  }
}

7. 多语言支持

dart 复制代码
class LocalizationManager {
  static const Map<String, Map<String, String>> _localizedValues = {
    'en': {
      'app_title': 'Screen Ruler Tool',
      'ruler': 'Ruler',
      'history': 'History',
      'calibration': 'Calibration',
      'settings': 'Settings',
      'distance': 'Distance',
      'angle': 'Angle',
      'pixels': 'Pixels',
      'save_measurement': 'Save Measurement',
      'clear': 'Clear',
      'auto_calibration': 'Auto Calibration',
      'manual_calibration': 'Manual Calibration',
      'measurement_units': 'Measurement Units',
      'appearance_settings': 'Appearance Settings',
      'feedback_settings': 'Feedback Settings',
    },
    'zh': {
      'app_title': '屏幕尺子工具',
      'ruler': '尺子',
      'history': '历史',
      'calibration': '校准',
      'settings': '设置',
      'distance': '距离',
      'angle': '角度',
      'pixels': '像素',
      'save_measurement': '保存测量',
      'clear': '清除',
      'auto_calibration': '自动校准',
      'manual_calibration': '手动校准',
      'measurement_units': '测量单位',
      'appearance_settings': '外观设置',
      'feedback_settings': '反馈设置',
    },
  };
  
  static String translate(String key, String locale) {
    return _localizedValues[locale]?[key] ?? key;
  }
  
  Widget buildLanguageSelector() {
    return Card(
      child: Column(
        children: [
          const Text('语言设置', style: TextStyle(fontWeight: FontWeight.bold)),
          RadioListTile<String>(
            title: const Text('中文'),
            value: 'zh',
            groupValue: _currentLocale,
            onChanged: (value) => _changeLocale(value!),
          ),
          RadioListTile<String>(
            title: const Text('English'),
            value: 'en',
            groupValue: _currentLocale,
            onChanged: (value) => _changeLocale(value!),
          ),
        ],
      ),
    );
  }
}

8. 无障碍功能

dart 复制代码
class AccessibilityFeatures {
  // 语音播报
  Widget buildVoiceAnnouncement() {
    return Card(
      child: Column(
        children: [
          SwitchListTile(
            title: const Text('语音播报'),
            subtitle: const Text('测量时语音播报结果'),
            value: _voiceEnabled,
            onChanged: (value) {
              setState(() { _voiceEnabled = value; });
            },
          ),
          SwitchListTile(
            title: const Text('高对比度'),
            subtitle: const Text('提高界面对比度'),
            value: _highContrastEnabled,
            onChanged: (value) {
              setState(() { _highContrastEnabled = value; });
            },
          ),
          SwitchListTile(
            title: const Text('大字体'),
            subtitle: const Text('使用更大的字体显示'),
            value: _largeTextEnabled,
            onChanged: (value) {
              setState(() { _largeTextEnabled = value; });
            },
          ),
        ],
      ),
    );
  }
  
  // 语音播报测量结果
  void _announceMeasurement(MeasurementResult measurement) {
    if (_voiceEnabled) {
      final text = '测量距离 ${measurement.formattedDistance},角度 ${measurement.angle.toStringAsFixed(1)} 度';
      _textToSpeech.speak(text);
    }
  }
  
  // 触觉导航
  void _provideTactileGuidance(Offset position) {
    if (_tactileGuidanceEnabled) {
      // 根据位置提供不同强度的震动反馈
      final intensity = _calculateVibrationIntensity(position);
      HapticFeedback.vibrate();
    }
  }
}

性能优化建议

1. 绘制性能优化

dart 复制代码
class OptimizedPainter extends CustomPainter {
  final Path _cachedPath = Path();
  bool _pathCached = false;
  
  @override
  void paint(Canvas canvas, Size size) {
    // 使用缓存路径减少计算
    if (!_pathCached) {
      _buildPath();
      _pathCached = true;
    }
    
    // 使用图层减少重绘
    canvas.saveLayer(Rect.fromLTWH(0, 0, size.width, size.height), Paint());
    
    // 批量绘制操作
    _batchDrawOperations(canvas);
    
    canvas.restore();
  }
  
  void _buildPath() {
    _cachedPath.reset();
    // 构建复杂路径
  }
  
  void _batchDrawOperations(Canvas canvas) {
    // 批量执行绘制操作以提高性能
  }
  
  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    // 精确控制重绘条件
    return oldDelegate != this;
  }
}

2. 内存管理优化

dart 复制代码
class MemoryOptimizedRuler {
  final List<MeasurementResult> _measurements = [];
  static const int _maxHistorySize = 1000;
  
  void addMeasurement(MeasurementResult measurement) {
    _measurements.add(measurement);
    
    // 限制历史记录数量
    if (_measurements.length > _maxHistorySize) {
      _measurements.removeAt(0);
    }
  }
  
  @override
  void dispose() {
    // 清理资源
    _pulseController.dispose();
    _measurements.clear();
    super.dispose();
  }
}

3. 响应性能优化

dart 复制代码
class ResponsiveRuler {
  Timer? _debounceTimer;
  
  void _onPanUpdateDebounced(DragUpdateDetails details) {
    _debounceTimer?.cancel();
    _debounceTimer = Timer(const Duration(milliseconds: 16), () {
      setState(() {
        _endPoint = details.localPosition;
      });
    });
  }
  
  @override
  void dispose() {
    _debounceTimer?.cancel();
    super.dispose();
  }
}

测试建议

1. 单元测试

dart 复制代码
// test/measurement_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:ruler_app/models/measurement_result.dart';

void main() {
  group('MeasurementResult Tests', () {
    test('should calculate distance correctly', () {
      final result = MeasurementResult(
        distance: 5.0,
        unit: MeasurementUnit(name: '厘米', symbol: 'cm', pixelsPerUnit: 37.8, precision: 1),
        startPoint: const Offset(0, 0),
        endPoint: const Offset(100, 0),
        timestamp: DateTime.now(),
      );
      
      expect(result.formattedDistance, equals('5.0 cm'));
    });
    
    test('should calculate angle correctly', () {
      final result = MeasurementResult(
        distance: 5.0,
        unit: MeasurementUnit(name: '厘米', symbol: 'cm', pixelsPerUnit: 37.8, precision: 1),
        startPoint: const Offset(0, 0),
        endPoint: const Offset(100, 100),
        timestamp: DateTime.now(),
      );
      
      expect(result.angle, closeTo(45.0, 0.1));
    });
  });
}

2. Widget测试

dart 复制代码
// test/widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:ruler_app/main.dart';

void main() {
  group('Ruler App Widget Tests', () {
    testWidgets('should display navigation bar with 4 tabs', (WidgetTester tester) async {
      await tester.pumpWidget(const RulerApp());
      
      expect(find.byType(NavigationBar), findsOneWidget);
      expect(find.text('尺子'), findsOneWidget);
      expect(find.text('历史'), findsOneWidget);
      expect(find.text('校准'), findsOneWidget);
      expect(find.text('设置'), findsOneWidget);
    });
    
    testWidgets('should navigate between tabs correctly', (WidgetTester tester) async {
      await tester.pumpWidget(const RulerApp());
      
      // 点击历史标签
      await tester.tap(find.text('历史'));
      await tester.pumpAndSettle();
      
      expect(find.text('测量历史'), findsOneWidget);
      
      // 点击设置标签
      await tester.tap(find.text('设置'));
      await tester.pumpAndSettle();
      
      expect(find.text('测量单位'), findsOneWidget);
    });
  });
}

3. 集成测试

dart 复制代码
// integration_test/app_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:ruler_app/main.dart' as app;

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  
  group('Ruler App Integration Tests', () {
    testWidgets('complete measurement flow test', (WidgetTester tester) async {
      app.main();
      await tester.pumpAndSettle();
      
      // 1. 验证应用启动
      expect(find.text('屏幕尺子工具'), findsOneWidget);
      
      // 2. 执行测量操作
      final measurementArea = find.byType(GestureDetector).first;
      await tester.dragFrom(const Offset(100, 100), const Offset(200, 200));
      await tester.pumpAndSettle();
      
      // 3. 验证测量结果显示
      expect(find.text('距离'), findsOneWidget);
      expect(find.text('角度'), findsOneWidget);
      
      // 4. 保存测量结果
      await tester.tap(find.text('保存测量'));
      await tester.pumpAndSettle();
      
      // 5. 切换到历史页面
      await tester.tap(find.text('历史'));
      await tester.pumpAndSettle();
      
      // 6. 验证历史记录
      expect(find.byType(Card), findsAtLeastNWidgets(1));
      
      // 7. 测试设置功能
      await tester.tap(find.text('设置'));
      await tester.pumpAndSettle();
      
      // 8. 切换测量单位
      await tester.tap(find.text('毫米 (mm)'));
      await tester.pumpAndSettle();
      
      // 9. 验证单位切换成功
      await tester.tap(find.text('尺子'));
      await tester.pumpAndSettle();
      expect(find.text('mm'), findsOneWidget);
    });
  });
}

部署指南

1. Android部署

bash 复制代码
# 构建APK
flutter build apk --release

# 构建App Bundle(推荐用于Google Play)
flutter build appbundle --release

# 安装到设备
flutter install

2. iOS部署

bash 复制代码
# 构建iOS应用
flutter build ios --release

# 使用Xcode打开项目进行签名和发布
open ios/Runner.xcworkspace

3. 应用图标和启动页

yaml 复制代码
# pubspec.yaml
dev_dependencies:
  flutter_launcher_icons: ^0.13.1
  flutter_native_splash: ^2.3.2

flutter_icons:
  android: true
  ios: true
  image_path: "assets/icon/ruler_icon.png"
  adaptive_icon_background: "#2196F3"
  adaptive_icon_foreground: "assets/icon/ruler_foreground.png"

flutter_native_splash:
  color: "#2196F3"
  image: "assets/splash/ruler_splash.png"
  android_12:
    image: "assets/splash/ruler_splash_android12.png"
    color: "#2196F3"

项目总结

这个屏幕尺子工具应用展示了Flutter在工具类应用开发中的强大能力。通过精确的测量算法、智能的校准系统和专业的UI设计,为用户提供了完整的屏幕测量解决方案。

技术亮点

  1. 精确测量算法:基于像素计算和设备校准的高精度测量
  2. 自定义绘制:使用CustomPainter实现专业的测量界面
  3. 智能校准系统:自动和手动校准相结合的精度保证
  4. 丰富的交互体验:手势识别、动画效果、触觉反馈
  5. 完整的功能闭环:测量、保存、历史、设置的完整流程

学习价值

  • CustomPainter的高级应用
  • 手势识别和处理技巧
  • 设备信息获取和利用
  • 动画控制器的使用
  • 数据持久化和管理
  • 专业工具应用的设计模式

这个项目为Flutter开发者提供了一个完整的工具类应用开发案例,涵盖了从基础UI到高级功能的各个方面,是学习Flutter应用开发的优秀参考。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
一只大侠的侠2 小时前
从环境搭建到工程运行:OpenHarmony版Flutter全流程实战
flutter
猛扇赵四那边好嘴.2 小时前
Flutter 框架跨平台鸿蒙开发 - 每日心情日记应用开发教程
flutter·华为·harmonyos
不会写代码0002 小时前
Flutter 框架跨平台鸿蒙开发 - 学习计划制定器开发教程
学习·flutter·华为·harmonyos
2501_944526422 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 收藏功能实现
android·java·开发语言·javascript·python·flutter·游戏
2501_944526422 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 个人中心实现
android·java·javascript·python·flutter·游戏
恋猫de小郭2 小时前
Meta ShapeR :基于随机拍摄视频的 3D 物体生成,未来的 XR 和机器人基建支持
android·flutter·3d·ai·音视频·xr
前端世界2 小时前
鸿蒙 UI 为什么会卡?GPU 渲染性能实战分析与优化
ui·华为·harmonyos
大雷神2 小时前
HarmonyOS智慧农业管理应用开发教程--高高种地---第5篇:地图首页与核心交互
华为·交互·harmonyos
不会写代码0002 小时前
Flutter 框架跨平台鸿蒙开发 - 照片水印添加器开发教程
flutter·华为·harmonyos