Flutter手账胶带收藏本应用开发教程

项目概述

本教程将带你开发一个功能完整的Flutter手账胶带收藏本应用。这款应用专为手账爱好者设计,提供胶带收藏管理、使用记录追踪、愿望清单管理和统计分析等功能,帮助用户更好地管理和享受手账创作生活。
运行效果图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

应用特色

  • 胶带收藏管理:记录胶带的详细信息,包括品牌、系列、图案、规格等
  • 库存状态监控:实时显示剩余量,智能提醒库存不足和即将用完
  • 使用记录追踪:记录每次使用的长度、项目、用途和满意度
  • 愿望清单管理:管理想要购买的胶带,设置优先级和预算
  • 智能筛选搜索:支持按图案、宽度、品牌、收藏状态等多维度筛选
  • 统计分析功能:提供收藏统计、图案分布、品牌分析等数据洞察
  • 个性化标签:自定义标签系统,便于分类管理

技术栈

  • 框架:Flutter 3.x
  • 语言:Dart
  • UI组件:Material Design 3
  • 状态管理:StatefulWidget
  • 动画:AnimationController + FadeTransition
  • 数据存储:内存存储(可扩展为本地数据库)
  • 导航:NavigationBar

项目结构设计

核心数据模型

1. 胶带信息模型(TapeItem)
class TapeItem {
  final String id;              // 唯一标识
  final String name;            // 胶带名称
  final String brand;           // 品牌
  final String series;          // 系列
  final String pattern;         // 图案类型
  final double width;           // 宽度(mm)
  final double length;          // 长度(m)
  final double remainingLength; // 剩余长度
  final String color;           // 主色调
  final DateTime purchaseDate;  // 购买日期
  final double price;           // 价格
  final String purchasePlace;   // 购买地点
  final String notes;           // 备注
  final List<String> tags;      // 标签
  final String imageUrl;        // 图片URL
  bool isFavorite;             // 是否收藏
  int usageCount;              // 使用次数
  DateTime? lastUsedDate;      // 最后使用日期
  String condition;            // 状态:全新、轻微使用、半用、快用完
}
2. 使用记录模型(UsageRecord)
class UsageRecord {
  final String id;              // 唯一标识
  final String tapeId;          // 关联的胶带ID
  final DateTime usageDate;     // 使用日期
  final double lengthUsed;      // 使用长度
  final String project;         // 使用项目
  final String purpose;         // 用途
  final String notes;           // 备注
  final int satisfaction;       // 满意度 1-5
}
3. 愿望清单模型(WishlistItem)
class WishlistItem {
  final String id;              // 唯一标识
  final String name;            // 胶带名称
  final String brand;           // 品牌
  final String series;          // 系列
  final double estimatedPrice;  // 预估价格
  final String reason;          // 想要的原因
  final int priority;           // 优先级 1-5
  final DateTime addedDate;     // 添加日期
  final String imageUrl;        // 图片URL
  bool isPurchased;            // 是否已购买
}

枚举定义

胶带图案类型枚举
enum PatternType {
  solid,      // 纯色
  floral,     // 花卉
  geometric,  // 几何
  character,  // 卡通人物
  text,       // 文字
  vintage,    // 复古
  seasonal,   // 季节性
  food,       // 食物
  animal,     // 动物
  landscape,  // 风景
}
胶带宽度枚举
enum TapeWidth {
  narrow,     // 窄胶带 (5-10mm)
  medium,     // 中等 (15-20mm)
  wide,       // 宽胶带 (25-30mm)
  extraWide,  // 超宽 (40mm+)
}

页面架构

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

  1. 收藏页面:展示所有胶带收藏,支持搜索和筛选
  2. 使用记录页面:记录和查看胶带使用历史
  3. 愿望清单页面:管理想要购买的胶带
  4. 统计页面:展示收藏统计和数据分析

详细实现步骤

第一步:项目初始化

创建新的Flutter项目:

flutter create tape_collection_app
cd tape_collection_app

第二步:主应用结构

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '手账胶带收藏本',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.pink),
        useMaterial3: true,
      ),
      home: const TapeCollectionHomePage(),
    );
  }
}

第三步:数据初始化

创建示例胶带数据:

void _initializeData() {
  _tapeItems = [
    TapeItem(
      id: '1',
      name: '樱花飞舞',
      brand: 'mt',
      series: '春日物语',
      pattern: _getPatternTypeName(PatternType.floral),
      width: 15.0,
      length: 10.0,
      remainingLength: 8.5,
      color: '粉色',
      purchaseDate: DateTime.now().subtract(const Duration(days: 30)),
      price: 25.0,
      purchasePlace: '淘宝',
      notes: '春天限定款,图案很美',
      tags: ['春天', '樱花', '限定', '粉色'],
      imageUrl: 'sakura_tape.jpg',
      isFavorite: true,
      usageCount: 3,
      lastUsedDate: DateTime.now().subtract(const Duration(days: 5)),
      condition: '轻微使用',
    ),
    // 更多胶带数据...
  ];
}

第四步:胶带列表页面

胶带卡片组件
Widget _buildTapeCard(TapeItem item) {
  return Card(
    elevation: 4,
    margin: const EdgeInsets.only(bottom: 16),
    child: InkWell(
      onTap: () => _showTapeDetail(item),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 标题行
            Row(
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        item.name,
                        style: const TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      Text(
                        '${item.brand}${item.series}',
                        style: TextStyle(
                          color: Colors.grey.shade600,
                          fontSize: 14,
                        ),
                      ),
                    ],
                  ),
                ),
                IconButton(
                  icon: Icon(
                    item.isFavorite ? Icons.favorite : Icons.favorite_border,
                    color: item.isFavorite ? Colors.red : Colors.grey,
                  ),
                  onPressed: () => _toggleFavorite(item),
                ),
              ],
            ),

            // 图案和规格信息
            Row(
              children: [
                Container(
                  padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                  decoration: BoxDecoration(
                    color: _getPatternColor(item.pattern),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Text(
                    item.pattern,
                    style: const TextStyle(
                      color: Colors.white,
                      fontSize: 12,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
                // 更多信息显示...
              ],
            ),

            // 剩余量进度条
            LinearProgressIndicator(
              value: item.remainingPercentage,
              backgroundColor: Colors.grey.shade300,
              valueColor: AlwaysStoppedAnimation<Color>(
                item.isAlmostEmpty
                    ? Colors.red
                    : item.isLowStock
                        ? Colors.orange
                        : Colors.green,
              ),
            ),
          ],
        ),
      ),
    ),
  );
}
智能状态检测
// 获取剩余百分比
double get remainingPercentage => remainingLength / length;

// 是否库存不足(少于20%)
bool get isLowStock => remainingPercentage < 0.2;

// 是否即将用完(少于10%)
bool get isAlmostEmpty => remainingPercentage < 0.1;

第五步:搜索和筛选功能

多维度筛选
List<TapeItem> _getFilteredTapeItems() {
  return _tapeItems.where((item) {
    // 搜索过滤
    if (_searchQuery.isNotEmpty) {
      final query = _searchQuery.toLowerCase();
      if (!item.name.toLowerCase().contains(query) &&
          !item.brand.toLowerCase().contains(query) &&
          !item.series.toLowerCase().contains(query) &&
          !item.pattern.toLowerCase().contains(query) &&
          !item.tags.any((tag) => tag.toLowerCase().contains(query))) {
        return false;
      }
    }

    // 图案过滤
    if (_selectedPattern != null &&
        item.pattern != _getPatternTypeName(_selectedPattern!)) {
      return false;
    }

    // 宽度过滤
    if (_selectedWidth != null) {
      final widthCategory = _getWidthCategory(item.width);
      if (widthCategory != _selectedWidth) {
        return false;
      }
    }

    // 品牌过滤
    if (_selectedBrand != null && item.brand != _selectedBrand) {
      return false;
    }

    // 收藏过滤
    if (_showFavoritesOnly && !item.isFavorite) {
      return false;
    }

    // 库存不足过滤
    if (_showLowStockOnly && !item.isLowStock) {
      return false;
    }

    return true;
  }).toList();
}
宽度分类算法
TapeWidth _getWidthCategory(double width) {
  if (width <= 10) return TapeWidth.narrow;
  if (width <= 20) return TapeWidth.medium;
  if (width <= 30) return TapeWidth.wide;
  return TapeWidth.extraWide;
}

第六步:使用记录功能

使用记录卡片
Widget _buildUsageRecordCard(UsageRecord record, TapeItem tape) {
  return Card(
    elevation: 2,
    margin: const EdgeInsets.only(bottom: 12),
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 标题行
          Row(
            children: [
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      tape.name,
                      style: const TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    Text(
                      _formatDateTime(record.usageDate),
                      style: TextStyle(
                        color: Colors.grey.shade600,
                        fontSize: 12,
                      ),
                    ),
                  ],
                ),
              ),
              // 满意度星星
              Row(
                children: List.generate(5, (index) {
                  return Icon(
                    index < record.satisfaction ? Icons.star : Icons.star_border,
                    color: Colors.amber,
                    size: 16,
                  );
                }),
              ),
            ],
          ),

          // 使用信息
          Row(
            children: [
              _buildInfoItem(
                Icons.straighten,
                '用量',
                '${record.lengthUsed}m',
                Colors.blue,
              ),
              _buildInfoItem(
                Icons.work,
                '项目',
                record.project,
                Colors.green,
              ),
              _buildInfoItem(
                Icons.label,
                '用途',
                record.purpose,
                Colors.orange,
              ),
            ],
          ),
        ],
      ),
    ),
  );
}
信息展示组件
Widget _buildInfoItem(IconData icon, String label, String value, Color color) {
  return Column(
    children: [
      Icon(icon, size: 20, color: color),
      const SizedBox(height: 4),
      Text(
        label,
        style: TextStyle(
          fontSize: 10,
          color: Colors.grey.shade600,
        ),
      ),
      Text(
        value,
        style: TextStyle(
          fontSize: 12,
          fontWeight: FontWeight.bold,
          color: color,
        ),
      ),
    ],
  );
}

第七步:愿望清单功能

愿望清单卡片
Widget _buildWishlistCard(WishlistItem item) {
  return Card(
    elevation: 2,
    margin: const EdgeInsets.only(bottom: 12),
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 标题行
          Row(
            children: [
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      item.name,
                      style: TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                        decoration: item.isPurchased
                            ? TextDecoration.lineThrough
                            : null,
                        color: item.isPurchased
                            ? Colors.grey.shade600
                            : Colors.black,
                      ),
                    ),
                    Text(
                      '${item.brand}${item.series}',
                      style: TextStyle(
                        color: Colors.grey.shade600,
                        fontSize: 12,
                      ),
                    ),
                  ],
                ),
              ),
              // 优先级星星
              Row(
                children: List.generate(5, (index) {
                  return Icon(
                    index < item.priority ? Icons.star : Icons.star_border,
                    color: Colors.amber,
                    size: 16,
                  );
                }),
              ),
            ],
          ),

          // 价格和状态
          Row(
            children: [
              Text(
                ${item.estimatedPrice.toStringAsFixed(0)}',
                style: TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                  color: item.isPurchased ? Colors.grey : Colors.pink,
                ),
              ),
              const Spacer(),
              if (item.isPurchased)
                Container(
                  padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                  decoration: BoxDecoration(
                    color: Colors.green,
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: const Text(
                    '已购买',
                    style: TextStyle(
                      color: Colors.white,
                      fontSize: 12,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
            ],
          ),

          // 想要的原因
          Text(
            item.reason,
            style: TextStyle(
              color: Colors.grey.shade700,
              fontSize: 14,
            ),
          ),
        ],
      ),
    ),
  );
}
愿望清单管理功能
void _markAsPurchased(WishlistItem item) {
  setState(() {
    item.isPurchased = true;
  });

  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('${item.name} 已标记为已购买')),
  );
}

void _removeFromWishlist(WishlistItem item) {
  setState(() {
    _wishlistItems.remove(item);
  });

  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('${item.name} 已从愿望清单中移除')),
  );
}

第八步:统计分析功能

统计卡片组件
Widget _buildStatCard(String title, String value, IconData icon, Color color) {
  return Container(
    padding: const EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: color.withValues(alpha: 0.1),
      borderRadius: BorderRadius.circular(12),
      border: Border.all(color: color.withValues(alpha: 0.3)),
    ),
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: color, size: 32),
        const SizedBox(height: 8),
        Text(
          value,
          style: TextStyle(
            fontSize: 18,
            fontWeight: FontWeight.bold,
            color: color,
          ),
        ),
        Text(
          title,
          style: TextStyle(
            fontSize: 12,
            color: color,
          ),
          textAlign: TextAlign.center,
        ),
      ],
    ),
  );
}
数据统计计算
// 获取图案分布
Map<String, int> _getPatternDistribution() {
  final distribution = <String, int>{};
  for (final item in _tapeItems) {
    distribution[item.pattern] = (distribution[item.pattern] ?? 0) + 1;
  }
  return Map.fromEntries(
      distribution.entries.toList()..sort((a, b) => b.value.compareTo(a.value)));
}

// 获取品牌分布
Map<String, int> _getBrandDistribution() {
  final distribution = <String, int>{};
  for (final item in _tapeItems) {
    distribution[item.brand] = (distribution[item.brand] ?? 0) + 1;
  }
  return Map.fromEntries(
      distribution.entries.toList()..sort((a, b) => b.value.compareTo(a.value)));
}

// 获取唯一品牌列表
List<String> _getUniqueBrands() {
  return _tapeItems.map((item) => item.brand).toSet().toList()..sort();
}
分布图表展示
// 图案分布图表
..._getPatternDistribution().entries.map((entry) {
  final percentage = entry.value / _tapeItems.length;
  return Padding(
    padding: const EdgeInsets.only(bottom: 12),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Row(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            Text(entry.key),
            Text('${entry.value}卷 (${(percentage * 100).toStringAsFixed(1)}%)'),
          ],
        ),
        const SizedBox(height: 4),
        LinearProgressIndicator(
          value: percentage,
          backgroundColor: Colors.grey.shade300,
          valueColor: AlwaysStoppedAnimation<Color>(
            _getPatternColor(entry.key),
          ),
        ),
      ],
    ),
  );
}).take(5).toList(),

第九步:动画效果实现

淡入动画
void _setupAnimations() {
  _fadeAnimationController = AnimationController(
    duration: const Duration(milliseconds: 800),
    vsync: this,
  );

  _fadeAnimation = Tween<double>(
    begin: 0.0,
    end: 1.0,
  ).animate(CurvedAnimation(
    parent: _fadeAnimationController,
    curve: Curves.easeInOut,
  ));

  _fadeAnimationController.forward();
}

// 在build方法中使用
body: FadeTransition(
  opacity: _fadeAnimation,
  child: IndexedStack(
    index: _selectedIndex,
    children: [
      _buildTapeListPage(),
      _buildUsageRecordsPage(),
      _buildWishlistPage(),
      _buildStatisticsPage(),
    ],
  ),
),

核心功能详解

1. 智能状态监控

应用提供了多种智能状态检测功能:

// 剩余量百分比计算
double get remainingPercentage => remainingLength / length;

// 库存不足检测(低于20%)
bool get isLowStock => remainingPercentage < 0.2;

// 即将用完检测(低于10%)
bool get isAlmostEmpty => remainingPercentage < 0.1;

2. 颜色主题系统

根据图案类型和颜色名称动态设置颜色:

Color _getPatternColor(String pattern) {
  switch (pattern) {
    case '纯色': return Colors.grey;
    case '花卉': return Colors.pink;
    case '几何': return Colors.blue;
    case '卡通人物': return Colors.orange;
    case '文字': return Colors.purple;
    case '复古': return Colors.brown;
    case '季节性': return Colors.green;
    case '食物': return Colors.amber;
    case '动物': return Colors.teal;
    case '风景': return Colors.indigo;
    default: return Colors.grey;
  }
}

Color _getColorFromName(String colorName) {
  switch (colorName) {
    case '红色': return Colors.red;
    case '粉色': return Colors.pink;
    case '橙色': return Colors.orange;
    case '黄色': return Colors.yellow;
    case '绿色': return Colors.green;
    case '蓝色': return Colors.blue;
    case '紫色': return Colors.purple;
    case '棕色': return Colors.brown;
    case '黑色': return Colors.black;
    case '白色': return Colors.grey;
    case '薄荷绿': return Colors.teal;
    default: return Colors.grey;
  }
}

3. 状态颜色映射

Color _getConditionColor(String condition) {
  switch (condition) {
    case '全新': return Colors.green;
    case '轻微使用': return Colors.blue;
    case '半用': return Colors.orange;
    case '快用完': return Colors.red;
    default: return Colors.grey;
  }
}

4. 搜索算法优化

bool _matchesSearchQuery(TapeItem item, String query) {
  final lowerQuery = query.toLowerCase();
  
  // 名称匹配
  if (item.name.toLowerCase().contains(lowerQuery)) return true;
  
  // 品牌匹配
  if (item.brand.toLowerCase().contains(lowerQuery)) return true;
  
  // 系列匹配
  if (item.series.toLowerCase().contains(lowerQuery)) return true;
  
  // 图案匹配
  if (item.pattern.toLowerCase().contains(lowerQuery)) return true;
  
  // 标签匹配
  if (item.tags.any((tag) => tag.toLowerCase().contains(lowerQuery))) return true;
  
  return false;
}

性能优化

1. 列表优化

使用ListView.builder实现虚拟滚动:

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

2. 状态管理优化

合理使用setState,避免不必要的重建:

void _toggleFavorite(TapeItem item) {
  setState(() {
    item.isFavorite = !item.isFavorite;
  });
  
  // 只更新必要的UI部分
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(
      content: Text(item.isFavorite ? '已添加到收藏' : '已从收藏中移除'),
      duration: const Duration(seconds: 1),
    ),
  );
}

3. 内存管理

及时释放动画控制器:


void dispose() {
  _fadeAnimationController.dispose();
  super.dispose();
}

4. 数据缓存策略

// 缓存筛选结果
List<TapeItem>? _cachedFilteredItems;
String? _lastSearchQuery;

List<TapeItem> _getFilteredTapeItems() {
  // 如果搜索条件没有变化,返回缓存结果
  if (_cachedFilteredItems != null && _lastSearchQuery == _searchQuery) {
    return _cachedFilteredItems!;
  }
  
  // 重新计算筛选结果
  _cachedFilteredItems = _tapeItems.where((item) {
    // 筛选逻辑...
  }).toList();
  
  _lastSearchQuery = _searchQuery;
  return _cachedFilteredItems!;
}

扩展功能

1. 数据持久化

使用shared_preferences保存数据:

dependencies:
  shared_preferences: ^2.2.0

// 保存胶带数据
Future<void> _saveTapeData() async {
  final prefs = await SharedPreferences.getInstance();
  final tapeJson = _tapeItems.map((item) => item.toJson()).toList();
  await prefs.setString('tape_items', jsonEncode(tapeJson));
}

// 加载胶带数据
Future<void> _loadTapeData() async {
  final prefs = await SharedPreferences.getInstance();
  final tapeJsonString = prefs.getString('tape_items');
  if (tapeJsonString != null) {
    final tapeJsonList = jsonDecode(tapeJsonString) as List;
    _tapeItems = tapeJsonList
        .map((json) => TapeItem.fromJson(json))
        .toList();
  }
}

2. 图片上传功能

集成image_picker插件:

dependencies:
  image_picker: ^1.0.4

Future<void> _pickImage() async {
  final picker = ImagePicker();
  final pickedFile = await picker.pickImage(source: ImageSource.gallery);
  
  if (pickedFile != null) {
    setState(() {
      // 更新胶带图片
    });
  }
}

3. 二维码扫描功能

使用mobile_scanner扫描胶带条码:

dependencies:
  mobile_scanner: ^3.5.2

Future<void> _scanBarcode() async {
  // 实现条码扫描功能
  // 自动识别胶带信息
}

4. 数据导出功能

导出收藏清单为Excel或PDF:

dependencies:
  excel: ^2.1.0
  pdf: ^3.10.4

Future<void> _exportToExcel() async {
  var excel = Excel.createExcel();
  Sheet sheetObject = excel['胶带收藏清单'];
  
  // 添加表头
  sheetObject.cell(CellIndex.indexByString("A1")).value = "名称";
  sheetObject.cell(CellIndex.indexByString("B1")).value = "品牌";
  sheetObject.cell(CellIndex.indexByString("C1")).value = "价格";
  
  // 添加数据
  for (int i = 0; i < _tapeItems.length; i++) {
    final item = _tapeItems[i];
    sheetObject.cell(CellIndex.indexByString("A${i + 2}")).value = item.name;
    sheetObject.cell(CellIndex.indexByString("B${i + 2}")).value = item.brand;
    sheetObject.cell(CellIndex.indexByString("C${i + 2}")).value = item.price;
  }
  
  // 保存文件
  var fileBytes = excel.save();
  // 保存到本地文件系统
}

5. 社交分享功能

分享收藏和使用心得:

dependencies:
  share_plus: ^7.2.1

Future<void> _shareCollection() async {
  final text = '我的手账胶带收藏:共${_tapeItems.length}卷,总价值¥${_tapeItems.fold(0.0, (sum, item) => sum + item.price).toStringAsFixed(0)}';
  await Share.share(text);
}

6. 提醒通知功能

库存不足和购买提醒:

dependencies:
  flutter_local_notifications: ^16.1.0

// 设置库存不足提醒
Future<void> _scheduleLowStockReminder() async {
  final lowStockItems = _tapeItems.where((item) => item.isLowStock).toList();
  
  if (lowStockItems.isNotEmpty) {
    // 发送本地通知
    await flutterLocalNotificationsPlugin.show(
      0,
      '库存提醒',
      '您有${lowStockItems.length}卷胶带库存不足',
      const NotificationDetails(),
    );
  }
}

测试策略

1. 单元测试

测试核心业务逻辑:

// test/tape_item_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:tape_collection_app/models/tape_item.dart';

void main() {
  group('TapeItem Tests', () {
    test('should calculate remaining percentage correctly', () {
      final tape = TapeItem(
        // 初始化参数...
        length: 10.0,
        remainingLength: 5.0,
      );
      
      expect(tape.remainingPercentage, equals(0.5));
    });
    
    test('should detect low stock correctly', () {
      final tape = TapeItem(
        // 初始化参数...
        length: 10.0,
        remainingLength: 1.0, // 10%
      );
      
      expect(tape.isLowStock, isTrue);
      expect(tape.isAlmostEmpty, isTrue);
    });
  });
}

2. Widget测试

测试UI组件:

// test/widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:tape_collection_app/main.dart';

void main() {
  testWidgets('App should display navigation bar', (WidgetTester tester) async {
    await tester.pumpWidget(const MyApp());
    
    expect(find.byType(NavigationBar), findsOneWidget);
    expect(find.text('收藏'), findsOneWidget);
    expect(find.text('使用记录'), findsOneWidget);
    expect(find.text('愿望清单'), findsOneWidget);
    expect(find.text('统计'), findsOneWidget);
  });
  
  testWidgets('Should toggle favorite status', (WidgetTester tester) async {
    await tester.pumpWidget(const MyApp());
    
    // 查找收藏按钮并点击
    final favoriteButton = find.byIcon(Icons.favorite_border).first;
    await tester.tap(favoriteButton);
    await tester.pump();
    
    // 验证状态变化
    expect(find.byIcon(Icons.favorite), findsWidgets);
  });
}

3. 集成测试

测试完整用户流程:

// 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:tape_collection_app/main.dart' as app;

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  
  group('App Integration Tests', () {
    testWidgets('Complete user flow test', (WidgetTester tester) async {
      app.main();
      await tester.pumpAndSettle();
      
      // 测试搜索功能
      await tester.tap(find.byIcon(Icons.search));
      await tester.pumpAndSettle();
      
      await tester.enterText(find.byType(TextField), '樱花');
      await tester.tap(find.text('搜索'));
      await tester.pumpAndSettle();
      
      // 验证搜索结果
      expect(find.text('樱花飞舞'), findsOneWidget);
      
      // 测试导航
      await tester.tap(find.text('愿望清单'));
      await tester.pumpAndSettle();
      
      expect(find.text('愿望清单'), findsWidgets);
    });
  });
}

部署和发布

1. Android发布

# 生成签名密钥
keytool -genkey -v -keystore ~/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key

# 配置android/app/build.gradle
android {
    signingConfigs {
        release {
            keyAlias keystoreProperties['keyAlias']
            keyPassword keystoreProperties['keyPassword']
            storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
            storePassword keystoreProperties['storePassword']
        }
    }
    buildTypes {
        release {
            signingConfig signingConfigs.release
        }
    }
}

# 构建APK
flutter build apk --release

2. iOS发布

# 构建iOS应用
flutter build ios --release

# 使用Xcode进行代码签名和发布
open ios/Runner.xcworkspace

3. Web发布

# 构建Web应用
flutter build web --release

# 部署到服务器
# 将build/web目录内容上传到Web服务器

总结

本教程详细介绍了如何使用Flutter开发一个功能完整的手账胶带收藏本应用。应用包含了胶带收藏管理、使用记录追踪、愿望清单管理、统计分析等核心功能,采用了Material Design 3设计规范,提供了良好的用户体验。

通过本项目的学习,你将掌握:

  • Flutter应用架构设计
  • 复杂数据模型的设计和管理
  • 多页面导航和状态管理
  • 搜索和筛选功能的实现
  • 数据统计和可视化
  • 动画效果的应用
  • 性能优化技巧
  • 测试策略和部署流程

这个应用不仅适合手账爱好者使用,也为其他收藏类应用的开发提供了很好的参考模板。你可以根据自己的需求进行功能扩展和定制,比如添加数据同步、社交分享、AI推荐等高级功能。

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

Logo

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

更多推荐