Flutter 框架跨平台鸿蒙开发 - 车辆油耗记录器应用开发教程

Flutter车辆油耗记录器应用开发教程

项目简介

车辆油耗记录器是一款帮助车主记录和分析车辆加油信息的Flutter应用。通过简洁实用的界面,用户可以轻松记录每次加油的详细信息,并通过统计分析功能了解车辆的油耗表现和费用支出。
运行效果图



核心功能

  • 加油记录管理:记录里程、油量、油价、费用等信息
  • 油耗计算:自动计算百公里油耗
  • 统计分析:总体统计、月度统计、油价分析
  • 数据管理:支持编辑、删除、备份、导出功能
  • 直观展示:卡片式布局,信息一目了然

技术特点

  • 单文件架构,代码简洁易懂
  • Material Design 3设计风格
  • 响应式布局设计
  • 完整的CRUD操作
  • 实时计算和统计功能

架构设计

整体架构

核心功能
记录管理
统计分析
油耗计算
数据模型
FuelRecord
FuelTrackerApp
FuelTrackerHomePage
记录页面
统计页面
设置页面
添加记录对话框
记录详情对话框
编辑记录对话框

页面结构

应用采用底部导航栏设计,包含三个主要页面:

  1. 记录页面:加油记录的查看和管理
  2. 统计页面:油耗和费用的统计分析
  3. 设置页面:应用配置和数据管理

数据模型设计

FuelRecord(加油记录)

dart 复制代码
class FuelRecord {
  final String id;              // 唯一标识
  final DateTime date;          // 加油日期
  final double odometer;        // 里程表读数(km)
  final double fuelAmount;      // 加油量(升)
  final double fuelPrice;       // 油价(元/升)
  final double totalCost;       // 总费用(元)
  final String gasStation;      // 加油站名称
  final String notes;           // 备注信息
}

数据模型特点

  • 完整性:包含加油记录的所有关键信息
  • 可扩展性:支持添加更多字段如燃油类型等
  • 计算能力:支持油耗和费用的自动计算
  • 易用性:提供便捷的数据访问方法

核心功能实现

1. 记录页面实现

页面头部设计
dart 复制代码
Widget _buildRecordsHeader() {
  final totalRecords = _fuelRecords.length;
  final totalCost = _fuelRecords.fold(0.0, (sum, record) => sum + record.totalCost);
  final totalFuel = _fuelRecords.fold(0.0, (sum, record) => sum + record.fuelAmount);

  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.local_gas_station, color: Colors.white, size: 32),
            const SizedBox(width: 12),
            const Expanded(
              child: Text(
                '车辆油耗记录器',
                style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold, color: Colors.white),
              ),
            ),
          ],
        ),
        const SizedBox(height: 20),
        // 统计卡片
        Row(
          children: [
            Expanded(child: _buildSummaryCard('总记录', '$totalRecords', '次', Icons.receipt)),
            const SizedBox(width: 12),
            Expanded(child: _buildSummaryCard('总费用', '${totalCost.toStringAsFixed(0)}', '元', Icons.attach_money)),
            const SizedBox(width: 12),
            Expanded(child: _buildSummaryCard('总油量', '${totalFuel.toStringAsFixed(0)}', '升', Icons.local_gas_station)),
          ],
        ),
      ],
    ),
  );
}
记录卡片设计
dart 复制代码
Widget _buildRecordCard(FuelRecord record, int index) {
  // 计算油耗(如果不是第一条记录)
  double? fuelConsumption;
  if (index < _fuelRecords.length - 1) {
    final sortedRecords = List<FuelRecord>.from(_fuelRecords)
      ..sort((a, b) => b.date.compareTo(a.date));
    final nextRecord = sortedRecords[index + 1];
    final distance = record.odometer - nextRecord.odometer;
    if (distance > 0) {
      fuelConsumption = (record.fuelAmount / distance) * 100;
    }
  }

  return Card(
    margin: const EdgeInsets.only(bottom: 16),
    child: InkWell(
      onTap: () => _showRecordDetails(record),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 头部信息
            Row(
              children: [
                Container(
                  width: 50,
                  height: 50,
                  decoration: BoxDecoration(
                    color: Colors.blue.withValues(alpha: 0.2),
                    borderRadius: BorderRadius.circular(25),
                  ),
                  child: const Icon(Icons.local_gas_station, color: Colors.blue, size: 24),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(record.dateString, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
                      Text('里程:${record.odometer.toStringAsFixed(0)}km'),
                      if (record.gasStation.isNotEmpty) Text(record.gasStation),
                    ],
                  ),
                ),
                Column(
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: [
                    Text('¥${record.totalCost.toStringAsFixed(1)}', 
                      style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.red)),
                    Text('${record.fuelAmount.toStringAsFixed(1)}L'),
                  ],
                ),
              ],
            ),
            const SizedBox(height: 12),
            // 详细信息标签
            Row(
              children: [
                _buildInfoChip('油价', '¥${record.fuelPrice.toStringAsFixed(1)}/L', Colors.orange),
                const SizedBox(width: 8),
                if (fuelConsumption != null)
                  _buildInfoChip('油耗', '${fuelConsumption.toStringAsFixed(1)}L/100km', Colors.green),
              ],
            ),
            if (record.notes.isNotEmpty) ...[
              const SizedBox(height: 8),
              Text(record.notes, style: TextStyle(fontSize: 12, color: Colors.grey.shade600)),
            ],
          ],
        ),
      ),
    ),
  );
}

2. 添加记录对话框

表单设计
dart 复制代码
Widget build(BuildContext context) {
  return AlertDialog(
    title: const Text('添加加油记录'),
    content: SizedBox(
      width: 400,
      height: 500,
      child: Form(
        key: _formKey,
        child: SingleChildScrollView(
          child: Column(
            children: [
              // 日期选择
              ListTile(
                leading: const Icon(Icons.calendar_today),
                title: const Text('日期'),
                subtitle: Text('${_selectedDate.year}年${_selectedDate.month}月${_selectedDate.day}日'),
                onTap: () async {
                  final date = await showDatePicker(
                    context: context,
                    initialDate: _selectedDate,
                    firstDate: DateTime(2020),
                    lastDate: DateTime.now(),
                  );
                  if (date != null) {
                    setState(() => _selectedDate = date);
                  }
                },
              ),
              // 里程表读数
              TextFormField(
                controller: _odometerController,
                decoration: const InputDecoration(
                  labelText: '里程表读数 (km)',
                  border: OutlineInputBorder(),
                  prefixIcon: Icon(Icons.speed),
                ),
                keyboardType: TextInputType.number,
                validator: (value) => value?.isEmpty == true ? '请输入里程表读数' : null,
              ),
              // 加油量
              TextFormField(
                controller: _fuelAmountController,
                decoration: const InputDecoration(
                  labelText: '加油量 (升)',
                  border: OutlineInputBorder(),
                  prefixIcon: Icon(Icons.local_gas_station),
                ),
                keyboardType: TextInputType.number,
                onChanged: (_) => _calculateTotalCost(),
                validator: (value) => value?.isEmpty == true ? '请输入加油量' : null,
              ),
              // 油价
              TextFormField(
                controller: _fuelPriceController,
                decoration: const InputDecoration(
                  labelText: '油价 (元/升)',
                  border: OutlineInputBorder(),
                  prefixIcon: Icon(Icons.attach_money),
                ),
                keyboardType: TextInputType.number,
                onChanged: (_) => _calculateTotalCost(),
                validator: (value) => value?.isEmpty == true ? '请输入油价' : null,
              ),
              // 总费用显示
              Container(
                width: double.infinity,
                padding: const EdgeInsets.all(16),
                decoration: BoxDecoration(
                  color: Colors.blue.withValues(alpha: 0.1),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Text(
                  '总费用: ¥${_totalCost.toStringAsFixed(2)}',
                  style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.blue),
                  textAlign: TextAlign.center,
                ),
              ),
            ],
          ),
        ),
      ),
    ),
  );
}
实时计算功能
dart 复制代码
void _calculateTotalCost() {
  final fuelAmount = double.tryParse(_fuelAmountController.text) ?? 0;
  final fuelPrice = double.tryParse(_fuelPriceController.text) ?? 0;
  setState(() {
    _totalCost = fuelAmount * fuelPrice;
  });
}

3. 统计分析功能

总体统计
dart 复制代码
Widget _buildOverallStats() {
  if (_fuelRecords.isEmpty) return const Card(child: Padding(padding: EdgeInsets.all(16), child: Text('暂无数据')));

  final totalCost = _fuelRecords.fold(0.0, (sum, record) => sum + record.totalCost);
  final totalFuel = _fuelRecords.fold(0.0, (sum, record) => sum + record.fuelAmount);
  final avgPrice = totalCost / totalFuel;
  
  // 计算平均油耗
  double avgConsumption = 0;
  int consumptionCount = 0;
  final sortedRecords = List<FuelRecord>.from(_fuelRecords)
    ..sort((a, b) => a.date.compareTo(b.date));
  
  for (int i = 1; i < sortedRecords.length; i++) {
    final distance = sortedRecords[i].odometer - sortedRecords[i-1].odometer;
    if (distance > 0) {
      avgConsumption += (sortedRecords[i].fuelAmount / distance) * 100;
      consumptionCount++;
    }
  }
  if (consumptionCount > 0) {
    avgConsumption /= consumptionCount;
  }

  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('总体统计', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
          const SizedBox(height: 16),
          Row(
            children: [
              Expanded(child: _buildStatItem('总费用', '¥${totalCost.toStringAsFixed(0)}', Colors.red)),
              Expanded(child: _buildStatItem('总油量', '${totalFuel.toStringAsFixed(0)}L', Colors.blue)),
            ],
          ),
          const SizedBox(height: 12),
          Row(
            children: [
              Expanded(child: _buildStatItem('平均油价', '¥${avgPrice.toStringAsFixed(1)}/L', Colors.orange)),
              Expanded(child: _buildStatItem('平均油耗', '${avgConsumption.toStringAsFixed(1)}L/100km', Colors.green)),
            ],
          ),
        ],
      ),
    ),
  );
}
月度统计
dart 复制代码
Widget _buildMonthlyStats() {
  final now = DateTime.now();
  final thisMonth = _fuelRecords.where((record) => 
    record.date.year == now.year && record.date.month == now.month).toList();
  
  final monthCost = thisMonth.fold(0.0, (sum, record) => sum + record.totalCost);
  final monthFuel = thisMonth.fold(0.0, (sum, record) => sum + record.fuelAmount);

  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('本月统计 (${now.month}月)', style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
          const SizedBox(height: 16),
          Row(
            children: [
              Expanded(child: _buildStatItem('本月费用', '¥${monthCost.toStringAsFixed(0)}', Colors.red)),
              Expanded(child: _buildStatItem('本月油量', '${monthFuel.toStringAsFixed(0)}L', Colors.blue)),
              Expanded(child: _buildStatItem('加油次数', '${thisMonth.length}次', Colors.purple)),
            ],
          ),
        ],
      ),
    ),
  );
}
油价统计
dart 复制代码
Widget _buildFuelPriceStats() {
  if (_fuelRecords.isEmpty) return const SizedBox();

  final prices = _fuelRecords.map((r) => r.fuelPrice).toList()..sort();
  final minPrice = prices.first;
  final maxPrice = prices.last;
  final avgPrice = prices.fold(0.0, (sum, price) => sum + price) / prices.length;

  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text('油价统计', style: TextStyle(fontSize: 18, fontWeight: FontWeight.w600)),
          const SizedBox(height: 16),
          Row(
            children: [
              Expanded(child: _buildStatItem('最低油价', '¥${minPrice.toStringAsFixed(1)}/L', Colors.green)),
              Expanded(child: _buildStatItem('最高油价', '¥${maxPrice.toStringAsFixed(1)}/L', Colors.red)),
              Expanded(child: _buildStatItem('平均油价', '¥${avgPrice.toStringAsFixed(1)}/L', Colors.orange)),
            ],
          ),
        ],
      ),
    ),
  );
}

UI组件设计

1. 统计卡片组件

dart 复制代码
Widget _buildSummaryCard(String title, String value, String unit, IconData icon) {
  return Container(
    padding: const EdgeInsets.all(12),
    decoration: BoxDecoration(
      color: Colors.white.withValues(alpha: 0.2),
      borderRadius: BorderRadius.circular(12),
    ),
    child: Column(
      children: [
        Icon(icon, color: Colors.white, size: 20),
        const SizedBox(height: 4),
        Text(value, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white)),
        Text(unit, style: const TextStyle(fontSize: 10, color: Colors.white70)),
        const SizedBox(height: 2),
        Text(title, style: const TextStyle(fontSize: 12, color: Colors.white70)),
      ],
    ),
  );
}

2. 信息标签组件

dart 复制代码
Widget _buildInfoChip(String label, String value, Color color) {
  return Container(
    padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
    decoration: BoxDecoration(
      color: color.withValues(alpha: 0.2),
      borderRadius: BorderRadius.circular(12),
    ),
    child: Text(
      '$label: $value',
      style: TextStyle(
        fontSize: 12,
        color: color,
        fontWeight: FontWeight.bold,
      ),
    ),
  );
}

3. 统计项组件

dart 复制代码
Widget _buildStatItem(String title, String value, Color color) {
  return Container(
    padding: const EdgeInsets.all(12),
    margin: const EdgeInsets.all(4),
    decoration: BoxDecoration(
      color: color.withValues(alpha: 0.1),
      borderRadius: BorderRadius.circular(8),
    ),
    child: Column(
      children: [
        Text(value, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: color)),
        const SizedBox(height: 4),
        Text(title, style: TextStyle(fontSize: 12, color: Colors.grey.shade600)),
      ],
    ),
  );
}

对话框组件实现

1. 记录详情对话框

dart 复制代码
class _RecordDetailsDialog extends StatelessWidget {
  final FuelRecord record;
  final Function(FuelRecord) onEdit;
  final VoidCallback onDelete;

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Row(
        children: [
          const Icon(Icons.local_gas_station, color: Colors.blue),
          const SizedBox(width: 12),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text('加油记录'),
                Text(record.dateString, style: TextStyle(fontSize: 14, color: Colors.grey.shade600)),
              ],
            ),
          ),
        ],
      ),
      content: SizedBox(
        width: 300,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            _buildDetailRow('里程表读数', '${record.odometer.toStringAsFixed(0)} km'),
            _buildDetailRow('加油量', '${record.fuelAmount.toStringAsFixed(1)} 升'),
            _buildDetailRow('油价', '¥${record.fuelPrice.toStringAsFixed(2)}/升'),
            _buildDetailRow('总费用', '¥${record.totalCost.toStringAsFixed(2)}', isHighlight: true),
            if (record.gasStation.isNotEmpty) _buildDetailRow('加油站', record.gasStation),
            if (record.notes.isNotEmpty) _buildDetailRow('备注', record.notes),
          ],
        ),
      ),
      actions: [
        TextButton(onPressed: () => Navigator.pop(context), child: const Text('关闭')),
        TextButton(onPressed: () => _showEditDialog(context), child: const Text('编辑')),
        TextButton(
          onPressed: () => _showDeleteConfirmation(context),
          style: TextButton.styleFrom(foregroundColor: Colors.red),
          child: const Text('删除'),
        ),
      ],
    );
  }

  Widget _buildDetailRow(String label, String value, {bool isHighlight = false}) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(
            width: 80,
            child: Text(label, style: TextStyle(fontSize: 14, color: Colors.grey.shade600)),
          ),
          Expanded(
            child: Text(
              value,
              style: TextStyle(
                fontSize: 14,
                fontWeight: isHighlight ? FontWeight.bold : FontWeight.normal,
                color: isHighlight ? Colors.red : Colors.black87,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

2. 编辑记录对话框

编辑对话框复用添加对话框的UI结构,但需要预填充现有数据:

dart 复制代码
class _EditRecordDialog extends StatefulWidget {
  final FuelRecord record;
  final Function(FuelRecord) onSave;

  @override
  State<_EditRecordDialog> createState() => _EditRecordDialogState();
}

class _EditRecordDialogState extends State<_EditRecordDialog> {
  @override
  void initState() {
    super.initState();
    // 预填充现有数据
    _odometerController = TextEditingController(text: widget.record.odometer.toString());
    _fuelAmountController = TextEditingController(text: widget.record.fuelAmount.toString());
    _fuelPriceController = TextEditingController(text: widget.record.fuelPrice.toString());
    _gasStationController = TextEditingController(text: widget.record.gasStation);
    _notesController = TextEditingController(text: widget.record.notes);
    _selectedDate = widget.record.date;
    _totalCost = widget.record.totalCost;
  }

  void _saveRecord() {
    if (_formKey.currentState!.validate()) {
      final editedRecord = widget.record.copyWith(
        date: _selectedDate,
        odometer: double.parse(_odometerController.text),
        fuelAmount: double.parse(_fuelAmountController.text),
        fuelPrice: double.parse(_fuelPriceController.text),
        totalCost: _totalCost,
        gasStation: _gasStationController.text.trim(),
        notes: _notesController.text.trim(),
      );

      widget.onSave(editedRecord);
      Navigator.pop(context);
    }
  }
}

核心算法实现

1. 油耗计算算法

dart 复制代码
double? calculateFuelConsumption(FuelRecord current, FuelRecord previous) {
  final distance = current.odometer - previous.odometer;
  if (distance > 0) {
    // 百公里油耗 = (加油量 / 行驶距离) * 100
    return (current.fuelAmount / distance) * 100;
  }
  return null;
}

2. 统计计算算法

dart 复制代码
class FuelStatistics {
  static Map<String, dynamic> calculateOverallStats(List<FuelRecord> records) {
    if (records.isEmpty) return {};
    
    final totalCost = records.fold(0.0, (sum, record) => sum + record.totalCost);
    final totalFuel = records.fold(0.0, (sum, record) => sum + record.fuelAmount);
    final avgPrice = totalCost / totalFuel;
    
    // 计算平均油耗
    double avgConsumption = 0;
    int consumptionCount = 0;
    final sortedRecords = List<FuelRecord>.from(records)
      ..sort((a, b) => a.date.compareTo(b.date));
    
    for (int i = 1; i < sortedRecords.length; i++) {
      final distance = sortedRecords[i].odometer - sortedRecords[i-1].odometer;
      if (distance > 0) {
        avgConsumption += (sortedRecords[i].fuelAmount / distance) * 100;
        consumptionCount++;
      }
    }
    if (consumptionCount > 0) {
      avgConsumption /= consumptionCount;
    }
    
    return {
      'totalCost': totalCost,
      'totalFuel': totalFuel,
      'avgPrice': avgPrice,
      'avgConsumption': avgConsumption,
      'recordCount': records.length,
    };
  }
  
  static Map<String, dynamic> calculateMonthlyStats(List<FuelRecord> records, DateTime month) {
    final monthRecords = records.where((record) => 
      record.date.year == month.year && record.date.month == month.month).toList();
    
    final monthCost = monthRecords.fold(0.0, (sum, record) => sum + record.totalCost);
    final monthFuel = monthRecords.fold(0.0, (sum, record) => sum + record.fuelAmount);
    
    return {
      'monthCost': monthCost,
      'monthFuel': monthFuel,
      'recordCount': monthRecords.length,
    };
  }
}

3. 数据验证算法

dart 复制代码
class DataValidator {
  static String? validateOdometer(String? value, double? lastOdometer) {
    if (value == null || value.isEmpty) {
      return '请输入里程表读数';
    }
    
    final odometer = double.tryParse(value);
    if (odometer == null) {
      return '请输入有效的数字';
    }
    
    if (odometer < 0) {
      return '里程表读数不能为负数';
    }
    
    if (lastOdometer != null && odometer < lastOdometer) {
      return '里程表读数不能小于上次记录';
    }
    
    return null;
  }
  
  static String? validateFuelAmount(String? value) {
    if (value == null || value.isEmpty) {
      return '请输入加油量';
    }
    
    final amount = double.tryParse(value);
    if (amount == null) {
      return '请输入有效的数字';
    }
    
    if (amount <= 0) {
      return '加油量必须大于0';
    }
    
    if (amount > 200) {
      return '加油量不能超过200升';
    }
    
    return null;
  }
  
  static String? validateFuelPrice(String? value) {
    if (value == null || value.isEmpty) {
      return '请输入油价';
    }
    
    final price = double.tryParse(value);
    if (price == null) {
      return '请输入有效的数字';
    }
    
    if (price <= 0) {
      return '油价必须大于0';
    }
    
    if (price > 20) {
      return '油价不能超过20元/升';
    }
    
    return null;
  }
}

功能扩展建议

1. 数据持久化

dart 复制代码
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:convert';

class FuelDataManager {
  static const String _recordsKey = 'fuel_records';
  
  static Future<void> saveRecords(List<FuelRecord> records) async {
    final prefs = await SharedPreferences.getInstance();
    final jsonList = records.map((record) => record.toJson()).toList();
    await prefs.setString(_recordsKey, jsonEncode(jsonList));
  }
  
  static Future<List<FuelRecord>> loadRecords() async {
    final prefs = await SharedPreferences.getInstance();
    final jsonString = prefs.getString(_recordsKey);
    if (jsonString != null) {
      final jsonList = jsonDecode(jsonString) as List;
      return jsonList.map((json) => FuelRecord.fromJson(json)).toList();
    }
    return [];
  }
  
  static Future<void> exportToCSV(List<FuelRecord> records) async {
    final csv = const ListToCsvConverter().convert([
      ['日期', '里程(km)', '加油量(L)', '油价(元/L)', '总费用(元)', '加油站', '备注'],
      ...records.map((record) => [
        record.dateString,
        record.odometer,
        record.fuelAmount,
        record.fuelPrice,
        record.totalCost,
        record.gasStation,
        record.notes,
      ]),
    ]);
    
    // 保存到文件
    final directory = await getApplicationDocumentsDirectory();
    final file = File('${directory.path}/fuel_records_export.csv');
    await file.writeAsString(csv);
  }
}

2. 图表可视化

dart 复制代码
import 'package:fl_chart/fl_chart.dart';

class FuelCharts {
  static Widget buildConsumptionChart(List<FuelRecord> records) {
    final consumptionData = <FlSpot>[];
    final sortedRecords = List<FuelRecord>.from(records)
      ..sort((a, b) => a.date.compareTo(b.date));
    
    for (int i = 1; i < sortedRecords.length; i++) {
      final distance = sortedRecords[i].odometer - sortedRecords[i-1].odometer;
      if (distance > 0) {
        final consumption = (sortedRecords[i].fuelAmount / distance) * 100;
        consumptionData.add(FlSpot(i.toDouble(), consumption));
      }
    }
    
    return LineChart(
      LineChartData(
        gridData: FlGridData(show: true),
        titlesData: FlTitlesData(show: true),
        borderData: FlBorderData(show: true),
        lineBarsData: [
          LineChartBarData(
            spots: consumptionData,
            isCurved: true,
            color: Colors.blue,
            barWidth: 3,
            dotData: FlDotData(show: true),
          ),
        ],
      ),
    );
  }
  
  static Widget buildCostChart(List<FuelRecord> records) {
    final costData = records.asMap().entries.map((entry) {
      return FlSpot(entry.key.toDouble(), entry.value.totalCost);
    }).toList();
    
    return LineChart(
      LineChartData(
        gridData: FlGridData(show: true),
        titlesData: FlTitlesData(show: true),
        borderData: FlBorderData(show: true),
        lineBarsData: [
          LineChartBarData(
            spots: costData,
            isCurved: true,
            color: Colors.red,
            barWidth: 3,
            dotData: FlDotData(show: true),
          ),
        ],
      ),
    );
  }
}

3. 多车辆支持

dart 复制代码
class Vehicle {
  final String id;
  final String name;
  final String brand;
  final String model;
  final int year;
  final String licensePlate;
  final double engineSize;
  final String fuelType;
  
  const Vehicle({
    required this.id,
    required this.name,
    required this.brand,
    required this.model,
    required this.year,
    required this.licensePlate,
    required this.engineSize,
    required this.fuelType,
  });
}

class MultiVehicleManager {
  static List<Vehicle> _vehicles = [];
  static String? _currentVehicleId;
  
  static void addVehicle(Vehicle vehicle) {
    _vehicles.add(vehicle);
    if (_currentVehicleId == null) {
      _currentVehicleId = vehicle.id;
    }
  }
  
  static void switchVehicle(String vehicleId) {
    _currentVehicleId = vehicleId;
  }
  
  static Vehicle? get currentVehicle {
    return _vehicles.firstWhere((v) => v.id == _currentVehicleId);
  }
  
  static List<FuelRecord> getRecordsForVehicle(String vehicleId, List<FuelRecord> allRecords) {
    return allRecords.where((record) => record.vehicleId == vehicleId).toList();
  }
}

4. 提醒功能

dart 复制代码
class FuelReminder {
  static Future<void> scheduleMaintenanceReminder(double currentOdometer) async {
    final nextMaintenance = ((currentOdometer / 5000).ceil() * 5000).toDouble();
    final remainingKm = nextMaintenance - currentOdometer;
    
    if (remainingKm <= 500) {
      await _showNotification(
        '保养提醒',
        '您的车辆还有${remainingKm.toInt()}公里需要保养',
      );
    }
  }
  
  static Future<void> checkFuelEfficiency(List<FuelRecord> records) async {
    if (records.length < 3) return;
    
    final recentRecords = records.take(3).toList();
    double totalConsumption = 0;
    int count = 0;
    
    for (int i = 1; i < recentRecords.length; i++) {
      final distance = recentRecords[i-1].odometer - recentRecords[i].odometer;
      if (distance > 0) {
        totalConsumption += (recentRecords[i-1].fuelAmount / distance) * 100;
        count++;
      }
    }
    
    if (count > 0) {
      final avgConsumption = totalConsumption / count;
      if (avgConsumption > 12) { // 假设正常油耗为12L/100km以下
        await _showNotification(
          '油耗异常',
          '最近油耗偏高(${avgConsumption.toStringAsFixed(1)}L/100km),建议检查车辆',
        );
      }
    }
  }
  
  static Future<void> _showNotification(String title, String body) async {
    // 使用flutter_local_notifications显示通知
  }
}

性能优化策略

1. 列表优化

dart 复制代码
// 使用ListView.builder进行懒加载
Widget _buildRecordsList() {
  final sortedRecords = List<FuelRecord>.from(_fuelRecords)
    ..sort((a, b) => b.date.compareTo(a.date));

  return ListView.builder(
    padding: const EdgeInsets.all(16),
    itemCount: sortedRecords.length,
    itemBuilder: (context, index) {
      final record = sortedRecords[index];
      return _buildRecordCard(record, index);
    },
  );
}

// 使用AutomaticKeepAliveClientMixin保持页面状态
class _FuelTrackerHomePageState extends State<FuelTrackerHomePage>
    with AutomaticKeepAliveClientMixin {
  
  @override
  bool get wantKeepAlive => true;
  
  @override
  Widget build(BuildContext context) {
    super.build(context); // 必须调用
    return Scaffold(/* ... */);
  }
}

2. 计算优化

dart 复制代码
class FuelCalculationCache {
  static final Map<String, double> _consumptionCache = {};
  static final Map<String, Map<String, dynamic>> _statsCache = {};
  
  static double? getCachedConsumption(String recordId) {
    return _consumptionCache[recordId];
  }
  
  static void setCachedConsumption(String recordId, double consumption) {
    _consumptionCache[recordId] = consumption;
  }
  
  static void clearCache() {
    _consumptionCache.clear();
    _statsCache.clear();
  }
  
  static Map<String, dynamic>? getCachedStats(String key) {
    return _statsCache[key];
  }
  
  static void setCachedStats(String key, Map<String, dynamic> stats) {
    _statsCache[key] = stats;
  }
}

3. 内存管理

dart 复制代码
class MemoryManager {
  static void optimizeImageMemory() {
    // 清理图片缓存
    PaintingBinding.instance.imageCache.clear();
    PaintingBinding.instance.imageCache.clearLiveImages();
  }
  
  static void limitImageCacheSize() {
    // 限制图片缓存大小
    PaintingBinding.instance.imageCache.maximumSize = 100;
    PaintingBinding.instance.imageCache.maximumSizeBytes = 50 << 20; // 50MB
  }
}

测试指南

1. 单元测试

dart 复制代码
// test/fuel_record_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:fuel_tracker/models/fuel_record.dart';

void main() {
  group('FuelRecord', () {
    test('should create fuel record with required fields', () {
      final record = FuelRecord(
        id: '1',
        date: DateTime.now(),
        odometer: 10000,
        fuelAmount: 50,
        fuelPrice: 7.5,
        totalCost: 375,
      );
      
      expect(record.id, '1');
      expect(record.odometer, 10000);
      expect(record.fuelAmount, 50);
      expect(record.totalCost, 375);
    });
    
    test('should calculate fuel consumption correctly', () {
      final record1 = FuelRecord(
        id: '1',
        date: DateTime.now().subtract(const Duration(days: 7)),
        odometer: 10000,
        fuelAmount: 50,
        fuelPrice: 7.5,
        totalCost: 375,
      );
      
      final record2 = FuelRecord(
        id: '2',
        date: DateTime.now(),
        odometer: 10500,
        fuelAmount: 45,
        fuelPrice: 7.8,
        totalCost: 351,
      );
      
      final distance = record2.odometer - record1.odometer;
      final consumption = (record2.fuelAmount / distance) * 100;
      
      expect(consumption, closeTo(9.0, 0.1));
    });
  });
}

2. Widget测试

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

void main() {
  group('FuelTrackerApp', () {
    testWidgets('should display navigation bar with 3 tabs', (WidgetTester tester) async {
      await tester.pumpWidget(const FuelTrackerApp());
      
      expect(find.byType(NavigationBar), findsOneWidget);
      expect(find.text('记录'), findsOneWidget);
      expect(find.text('统计'), findsOneWidget);
      expect(find.text('设置'), findsOneWidget);
    });
    
    testWidgets('should show add button on records page', (WidgetTester tester) async {
      await tester.pumpWidget(const FuelTrackerApp());
      
      expect(find.byType(FloatingActionButton), findsOneWidget);
    });
    
    testWidgets('should open add record dialog when FAB is tapped', (WidgetTester tester) async {
      await tester.pumpWidget(const FuelTrackerApp());
      
      await tester.tap(find.byType(FloatingActionButton));
      await tester.pumpAndSettle();
      
      expect(find.text('添加加油记录'), findsOneWidget);
    });
  });
}

部署指南

1. Android部署

yaml 复制代码
# android/app/build.gradle
android {
    compileSdkVersion 34
    
    defaultConfig {
        applicationId "com.example.fuel_tracker"
        minSdkVersion 21
        targetSdkVersion 34
        versionCode 1
        versionName "1.0.0"
    }
    
    buildTypes {
        release {
            signingConfig signingConfigs.release
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

2. iOS部署

xml 复制代码
<!-- ios/Runner/Info.plist -->
<key>CFBundleDisplayName</key>
<string>车辆油耗记录器</string>
<key>CFBundleIdentifier</key>
<string>com.example.fuelTracker</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>

3. 构建命令

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

# 构建Android App Bundle
flutter build appbundle --release

# 构建iOS
flutter build ios --release

项目总结

车辆油耗记录器应用通过Flutter框架实现了一个功能实用、界面简洁的油耗管理工具。应用具有以下特点:

技术亮点

  1. 简洁架构:单文件实现,代码结构清晰易懂
  2. 实用功能:涵盖加油记录、油耗计算、统计分析等核心需求
  3. 用户友好:直观的界面设计和便捷的操作流程
  4. 数据准确:实时计算和验证,确保数据的准确性
  5. 扩展性强:支持多种功能扩展和定制

功能完整性

  • ✅ 加油记录管理
  • ✅ 油耗自动计算
  • ✅ 统计分析功能
  • ✅ 数据编辑删除
  • ✅ 费用跟踪
  • ✅ 直观数据展示

应用价值

  1. 实用性强:解决车主日常油耗记录和分析需求
  2. 操作简单:界面直观,操作便捷
  3. 数据准确:自动计算,减少人工错误
  4. 分析全面:多维度统计,帮助优化用车成本

通过本教程的学习,开发者可以快速掌握Flutter应用开发的实用技能,并创建出满足实际需求的移动应用。

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

相关推荐
灰灰勇闯IT2 小时前
【Flutter for OpenHarmony--Dart 入门日记】第1篇:变量声明详解——从 `var` 开始认识 Dart 的类型世界
flutter·交互
前端世界2 小时前
鸿蒙系统中时间与日期的国际化实践:一次把不同文化显示问题讲清楚
android·华为·harmonyos
信创天地2 小时前
信创环境下数据库与中间件监控实战:指标采集、工具应用与告警体系构建
java·运维·数据库·安全·elk·华为·中间件
猛扇赵四那边好嘴.2 小时前
Flutter 框架跨平台鸿蒙开发 - 居家好物收纳应用开发教程
flutter·华为·harmonyos
[H*]2 小时前
Flutter框架跨平台鸿蒙开发——AnimatedIcon动画图标
运维·nginx·flutter
夜雨声烦丿2 小时前
Flutter 框架跨平台鸿蒙开发 - 育儿知识大全应用开发教程
flutter·华为·harmonyos
菜鸟小芯2 小时前
【开源鸿蒙跨平台开发先锋训练营】DAY7 第一阶段知识要点复盘
flutter·harmonyos
kirk_wang2 小时前
Flutter艺术探索-Flutter发布应用:Android与iOS打包流程
flutter·移动开发·flutter教程·移动开发教程
程序员老刘·3 小时前
跨平台开发地图:2025跨平台技术简单总结 | 2026年1月
flutter·跨平台开发·客户端开发