基于 Flutter + Dart + Material Design 3 的仿元初到家电商移动端APP项目 01
项目视频
jvideo
抖音 www.douyin.com/video/75135…
项目截图
首页01
首页02
首页03
首页04
首页05
个人中心
📋 项目概述
1.1 项目背景
随着移动电商的快速发展,用户对购物APP的体验要求越来越高。本项目基于Flutter框架开发一款高性能、跨平台的电商移动应用,旨在提供流畅的购物体验和完整的电商功能。
1.2 项目目标
- 用户体验:打造流畅、直观的购物界面
- 性能优化:实现快速加载和流畅动画
- 跨平台兼容:支持iOS、Android等多平台
- 组件化开发:构建可复用的UI组件库
- 可扩展性:支持快速功能迭代和业务扩展
1.3 应用场景
- 移动端购物应用
- 多平台电商解决方案
- UI组件库开发参考
- Flutter企业级应用实践
🛠️ 技术选型
2.1 核心技术栈
| 技术领域 | 选择方案 | 版本要求 | 选择理由 |
|---|---|---|---|
| 开发框架 | Flutter | ^3.8.0 | 跨平台、性能优秀、生态丰富 |
| 编程语言 | Dart | ^3.0.0 | 类型安全、异步支持、现代语法 |
| UI设计 | Material Design 3 | 最新版 | 现代化设计规范、组件丰富 |
| 图片缓存 | cached_network_image | ^3.3.1 | 性能优化、内存管理 |
| 文件系统 | path_provider | ^2.1.4 | 跨平台文件路径管理 |
2.2 开发工具链
开发环境:
- Flutter SDK: ^3.8.0
- Dart SDK: ^3.0.0
- IDE: VS Code / Android Studio
- 版本控制: Git
构建工具:
- Flutter CLI
- 自动化构建脚本 (ARM64优化)
- 多平台构建支持
代码质量:
- flutter_lints: ^5.0.0
- 静态代码分析
- 0警告编译标准
🏗️ 系统架构
3.1 整体架构设计
┌─────────────────────────────────────────┐
│ Presentation Layer │
├─────────────────────────────────────────┤
│ Pages/ │ Components/ │ Gallery/ │
│ • HomePage │ • UI (7个) │ • 组件展示 │
│ • CartPage │ • Home (6个) │ • 实时预览 │
│ • Profile │ • Product(2) │ • 交互演示 │
│ • Member │ • Profile(9) │ │
│ • Category │ • Common │ │
├─────────────────────────────────────────┤
│ Business Logic Layer │
├─────────────────────────────────────────┤
│ Services/ │ Models/ │ Utils/ │
│ • 网络请求 │ • 数据模型 │ • 工具类 │
│ • 状态管理 │ • 业务实体 │ • 常量定义 │
│ • 缓存管理 │ │ • 扩展方法 │
├─────────────────────────────────────────┤
│ Platform Layer │
├─────────────────────────────────────────┤
│ iOS │ Android │ Web │ Desktop │
│ Native │ Native │ Chrome │ Win/Mac/Lin│
└─────────────────────────────────────────┘
3.2 目录结构
lib/
├── main.dart # 应用入口点
├── gallery_page.dart # 组件画廊展示
├── components/ # 组件库目录
│ ├── ui/ # 通用UI组件(7个)
│ │ ├── location_tag.dart
│ │ ├── promotion_banner.dart
│ │ ├── coupon_card.dart
│ │ ├── delivery_tag.dart
│ │ ├── store_header.dart
│ │ ├── cart_icon_with_badge.dart
│ │ └── delivery_mode_switcher.dart
│ ├── home/ # 首页组件(6个)
│ │ ├── custom_search_bar.dart
│ │ ├── category_grid.dart
│ │ ├── horizontal_category_list.dart
│ │ ├── featured_section.dart
│ │ ├── carousel_banner.dart
│ │ └── tab_selector.dart
│ ├── product/ # 商品组件(2个)
│ │ ├── product_card.dart
│ │ └── product_card_large.dart
│ ├── profile/ # 个人中心组件(9个)
│ │ ├── profile_avatar.dart
│ │ ├── vip_badge.dart
│ │ ├── user_info_section.dart
│ │ ├── points_card.dart
│ │ ├── services_card.dart
│ │ ├── service_item.dart
│ │ ├── order_item.dart
│ │ ├── orders_section.dart
│ │ └── member_services_section.dart
│ └── common/ # 公共组件
├── pages/ # 页面文件
│ ├── home_page.dart
│ ├── category_page.dart
│ ├── member_page.dart
│ ├── cart_page.dart
│ └── profile_page.dart
├── models/ # 数据模型
├── services/ # 业务服务
└── utils/ # 工具类
🚀 核心功能模块
4.1 主要功能清单
📱 用户界面模块
- 底部导航栏:5个主要页面切换
- 首页展示:商品轮播、分类导航、特色推荐
- 商品浏览:商品列表、详情展示、规格选择
- 购物车管理:商品添加、数量调节、结算功能
- 用户中心:个人信息、VIP系统、积分管理
🎨 组件库系统
- UI界面组件:7个通用界面组件
- 首页组件:6个首页专用组件
- 商品组件:2个商品展示组件
- 个人中心组件:9个用户相关组件
- 组件画廊:24个组件的可视化展示系统
🛒 电商业务功能
- 商品管理:商品展示、分类筛选、搜索功能
- 购物车:添加商品、数量管理、价格计算
- 会员系统:VIP等级、积分累积、专属优惠
- 优惠券:券码管理、使用规则、优惠计算
- 订单管理:订单创建、状态跟踪、历史记录
4.2 技术功能特性
🎯 性能优化
- ARM64专用构建:APK体积减少65%(22MB→8MB)
- 图片缓存机制:网络图片本地缓存,提升加载速度
- 懒加载策略:组件按需加载,减少内存占用
- 动画优化:流畅的过渡效果和交互反馈
🌍 跨平台支持
- 移动端:iOS、Android原生性能
- 统一代码库:一套代码,多平台部署
💻 技术实现方案
5.1 应用入口设计
// main.dart - 应用程序入口
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SunGiven App',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
useMaterial3: true, // 启用 Material Design 3
),
home: const MainPage(),
);
}
}
// 主页面架构 - 底部导航
class MainPage extends StatefulWidget {
@override
State<MainPage> createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
int _currentIndex = 0;
final List<Widget> _pages = [
HomePage(), // 首页
CategoryPage(), // 分类
MemberPage(), // 会员
CartPage(), // 购物车
ProfilePage(), // 个人中心
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: _currentIndex,
onTap: (index) => setState(() => _currentIndex = index),
selectedItemColor: Colors.green,
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
BottomNavigationBarItem(icon: Icon(Icons.category), label: '分类'),
BottomNavigationBarItem(icon: Icon(Icons.card_membership), label: '会员'),
BottomNavigationBarItem(icon: CartIconWithBadge(), label: '购物车'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: '个人中心'),
],
),
);
}
}
5.2 组件化开发模式
可复用组件设计原则
// 示例:优惠券卡片组件
class CouponCard extends StatelessWidget {
final String amount; // 必需参数
final String condition; // 必需参数
final VoidCallback? onTap; // 可选回调
final Color? backgroundColor; // 可选样式
final Color? textColor; // 可选样式
const CouponCard({
Key? key,
required this.amount, // 明确必需参数
required this.condition,
this.onTap, // 提供回调扩展
this.backgroundColor, // 支持样式定制
this.textColor,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: backgroundColor ?? Colors.red.shade50,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.red.shade200),
// 添加券样式的切口效果
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 4,
offset: Offset(0, 2),
),
],
),
child: Row(
children: [
// 优惠金额
Text('¥$amount', style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
color: textColor ?? Colors.red,
)),
Spacer(),
// 使用条件
Text(condition, style: TextStyle(
color: Colors.grey.shade600,
fontSize: 14,
)),
SizedBox(width: 8),
Icon(Icons.arrow_forward_ios,
size: 16,
color: Colors.grey.shade400),
],
),
),
);
}
}
商品卡片组件实现
// 商品卡片 - 支持数量选择和购物车动画
class ProductCard extends StatefulWidget {
final String imageUrl;
final String title;
final String currentPrice;
final String? originalPrice;
final Function(int)? onQuantityChanged;
const ProductCard({
Key? key,
required this.imageUrl,
required this.title,
required this.currentPrice,
this.originalPrice,
this.onQuantityChanged,
}) : super(key: key);
@override
State<ProductCard> createState() => _ProductCardState();
}
class _ProductCardState extends State<ProductCard>
with SingleTickerProviderStateMixin {
int _quantity = 0;
late AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
duration: Duration(milliseconds: 300),
vsync: this,
);
}
void _updateQuantity(int delta) {
setState(() {
_quantity = (_quantity + delta).clamp(0, 99);
if (_quantity > 0) {
_animationController.forward().then((_) {
_animationController.reverse();
});
}
});
widget.onQuantityChanged?.call(_quantity);
}
@override
Widget build(BuildContext context) {
return Container(
margin: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 8,
offset: Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 商品图片
Expanded(
child: ClipRRect(
borderRadius: BorderRadius.vertical(top: Radius.circular(12)),
child: CachedNetworkImage(
imageUrl: widget.imageUrl,
fit: BoxFit.cover,
width: double.infinity,
placeholder: (context, url) => Container(
color: Colors.grey.shade100,
child: Center(child: CircularProgressIndicator()),
),
errorWidget: (context, url, error) => Container(
color: Colors.grey.shade100,
child: Icon(Icons.image_not_supported),
),
),
),
),
// 商品信息
Padding(
padding: EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 商品标题
Text(
widget.title,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 8),
// 价格信息
Row(
children: [
Text(
widget.currentPrice,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.red,
),
),
if (widget.originalPrice != null) ...[
SizedBox(width: 8),
Text(
widget.originalPrice!,
style: TextStyle(
fontSize: 12,
color: Colors.grey,
decoration: TextDecoration.lineThrough,
),
),
],
],
),
SizedBox(height: 8),
// 数量选择器
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (_quantity == 0)
GestureDetector(
onTap: () => _updateQuantity(1),
child: AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
return Transform.scale(
scale: 1.0 + _animationController.value * 0.1,
child: Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(6),
),
child: Icon(
Icons.add_shopping_cart,
color: Colors.white,
size: 16,
),
),
);
},
),
)
else
Row(
children: [
GestureDetector(
onTap: () => _updateQuantity(-1),
child: Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
border: Border.all(color: Colors.green),
borderRadius: BorderRadius.circular(4),
),
child: Icon(Icons.remove,
size: 16,
color: Colors.green),
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: 12),
child: Text('$_quantity',
style: TextStyle(fontWeight: FontWeight.bold)),
),
GestureDetector(
onTap: () => _updateQuantity(1),
child: Container(
padding: EdgeInsets.all(4),
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(4),
),
child: Icon(Icons.add,
size: 16,
color: Colors.white),
),
),
],
),
],
),
],
),
),
],
),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
}
5.3 图片缓存与性能优化
// pubspec.yaml 依赖配置
dependencies:
cached_network_image: ^3.3.1
path_provider: ^2.1.4
// 图片缓存实现
class OptimizedNetworkImage extends StatelessWidget {
final String imageUrl;
final double? width;
final double? height;
final BoxFit fit;
const OptimizedNetworkImage({
Key? key,
required this.imageUrl,
this.width,
this.height,
this.fit = BoxFit.cover,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return CachedNetworkImage(
imageUrl: imageUrl,
width: width,
height: height,
fit: fit,
// 内存缓存优化
memCacheWidth: width?.toInt(),
memCacheHeight: height?.toInt(),
// 加载状态
placeholder: (context, url) => Container(
width: width,
height: height,
color: Colors.grey.shade100,
child: Center(
child: CircularProgressIndicator(strokeWidth: 2),
),
),
// 错误状态
errorWidget: (context, url, error) => Container(
width: width,
height: height,
color: Colors.grey.shade100,
child: Icon(
Icons.image_not_supported,
color: Colors.grey.shade400,
),
),
// 缓存配置
cacheKey: imageUrl,
fadeInDuration: Duration(milliseconds: 200),
fadeOutDuration: Duration(milliseconds: 100),
);
}
}
⚡ 性能优化策略
6.1 构建优化
ARM64专用构建
# macOS/Linux 构建脚本 (build_arm64.sh)
#!/bin/bash
set -e
echo "🔧 开始构建 ARM64 专用 APK..."
echo "📱 目标平台: Android ARM64"
echo "⚡ 优化模式: Release"
# 清理旧的构建文件
flutter clean
# 获取依赖
flutter pub get
# ARM64 专用构建 - 大幅减少APK体积
flutter build apk --release --target-platform android-arm64
# 构建结果
APK_PATH="build/app/outputs/flutter-apk/app-arm64-v8a-release.apk"
if [ -f "$APK_PATH" ]; then
APK_SIZE=$(du -h "$APK_PATH" | cut -f1)
echo "✅ 构建成功!"
echo "📦 APK文件: $APK_PATH"
echo "📏 文件大小: $APK_SIZE"
echo "🚀 性能优化: ARM64专用构建,体积减少65%"
else
echo "❌ 构建失败!"
exit 1
fi
多平台构建命令
# Android 构建
flutter build apk --release --target-platform android-arm64 # ARM64优化
flutter build apk --release # 通用版本
# iOS 构建
flutter build ios --release
6.2 运行时优化
内存管理策略
// 图片内存优化
class ImageMemoryManager {
static const int MAX_CACHE_WIDTH = 800;
static const int MAX_CACHE_HEIGHT = 600;
static Widget buildOptimizedImage(String url, {
double? width,
double? height,
}) {
return CachedNetworkImage(
imageUrl: url,
memCacheWidth: (width ?? MAX_CACHE_WIDTH).toInt(),
memCacheHeight: (height ?? MAX_CACHE_HEIGHT).toInt(),
maxWidthDiskCache: MAX_CACHE_WIDTH,
maxHeightDiskCache: MAX_CACHE_HEIGHT,
);
}
}
// 组件懒加载
class LazyLoadingList extends StatefulWidget {
final List<Widget> children;
@override
State<LazyLoadingList> createState() => _LazyLoadingListState();
}
class _LazyLoadingListState extends State<LazyLoadingList> {
final ScrollController _controller = ScrollController();
final Set<int> _loadedIndices = {};
@override
Widget build(BuildContext context) {
return ListView.builder(
controller: _controller,
itemCount: widget.children.length,
itemBuilder: (context, index) {
// 懒加载逻辑
if (!_loadedIndices.contains(index)) {
_loadedIndices.add(index);
return widget.children[index];
}
return Container(); // 占位符
},
);
}
}
6.3 用户体验优化
加载状态管理
// 统一的加载状态组件
class LoadingStateWidget extends StatelessWidget {
final bool isLoading;
final String? loadingText;
final Widget child;
final Widget? errorWidget;
const LoadingStateWidget({
Key? key,
required this.isLoading,
required this.child,
this.loadingText,
this.errorWidget,
}) : super(key: key);
@override
Widget build(BuildContext context) {
if (isLoading) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
if (loadingText != null) ...[
SizedBox(height: 16),
Text(loadingText!),
],
],
),
);
}
return child;
}
}
// 错误处理组件
class ErrorStateWidget extends StatelessWidget {
final String message;
final VoidCallback? onRetry;
const ErrorStateWidget({
Key? key,
required this.message,
this.onRetry,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline, size: 64, color: Colors.grey),
SizedBox(height: 16),
Text(message, textAlign: TextAlign.center),
if (onRetry != null) ...[
SizedBox(height: 16),
ElevatedButton(
onPressed: onRetry,
child: Text('重试'),
),
],
],
),
);
}
}
🚀 开发与部署
7.1 开发环境配置
环境要求
开发环境:
Flutter SDK: ">=3.8.0 <4.0.0"
Dart SDK: ">=3.0.0 <4.0.0"
推荐IDE:
- Visual Studio Code + Flutter Extension
7.3 自动化构建
CI/CD 配置示例 (GitHub Actions)
# .github/workflows/build.yml
name: Build and Deploy
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: '3.8.0'
- name: Install dependencies
run: flutter pub get
- name: Run tests
run: flutter test
- name: Build APK (ARM64)
run: flutter build apk --release --target-platform android-arm64
- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: app-builds
path: |
build/app/outputs/flutter-apk/
build/web/
deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
steps:
- name: Deploy to Firebase Hosting
uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: '${{ secrets.GITHUB_TOKEN }}'
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT }}'
projectId: 'sungiven-app'
## 🧪 测试策略
### 8.1 测试架构
#### 测试配置
```yaml
# pubspec.yaml - 测试依赖
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
mockito: ^5.4.4
build_runner: ^2.4.7
json_annotation: ^4.8.1
8.2 单元测试实现
// test/components/coupon_card_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sungiven_app/components/ui/coupon_card.dart';
void main() {
group('CouponCard Tests', () {
testWidgets('应该正确显示优惠券信息', (WidgetTester tester) async {
// 准备测试数据
const String amount = '15';
const String condition = '满99元可用';
bool tapCalled = false;
// 构建组件
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: CouponCard(
amount: amount,
condition: condition,
onTap: () => tapCalled = true,
),
),
),
);
// 验证显示内容
expect(find.text('¥$amount'), findsOneWidget);
expect(find.text(condition), findsOneWidget);
// 测试点击事件
await tester.tap(find.byType(CouponCard));
expect(tapCalled, true);
});
testWidgets('应该支持自定义样式', (WidgetTester tester) async {
const Color customColor = Colors.blue;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: CouponCard(
amount: '20',
condition: '满150元可用',
backgroundColor: customColor,
textColor: Colors.white,
),
),
),
);
// 验证自定义样式
final container = tester.widget<Container>(
find.descendant(
of: find.byType(CouponCard),
matching: find.byType(Container).first,
),
);
final decoration = container.decoration as BoxDecoration;
expect(decoration.color, customColor);
});
});
}
8.3 Widget测试
// test/pages/home_page_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:sungiven_app/pages/home_page.dart';
void main() {
group('HomePage Tests', () {
testWidgets('应该显示所有必要的组件', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(home: HomePage()),
);
// 验证核心组件存在
expect(find.byType(CustomSearchBar), findsOneWidget);
expect(find.byType(CategoryGrid), findsOneWidget);
expect(find.byType(CarouselBanner), findsOneWidget);
expect(find.byType(FeaturedSection), findsOneWidget);
});
testWidgets('搜索栏应该响应用户输入', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(home: HomePage()),
);
// 查找搜索框
final searchBar = find.byType(TextField);
expect(searchBar, findsOneWidget);
// 模拟输入
await tester.enterText(searchBar, '牛奶');
await tester.pump();
// 验证输入内容
expect(find.text('牛奶'), findsOneWidget);
});
});
}
📊 项目数据指标
9.1 开发统计
| 指标类别 | 数值 | 说明 |
|---|---|---|
| 代码量 | ~15,000行 | Dart代码总量 |
| 组件数量 | 24个 | UI组件库规模 |
| 页面数量 | 5个 | 主要功能页面 |
| 支持平台 | 2个 | iOS/Android |
| 依赖包数 | 3个 | 核心外部依赖 |
9.2 性能指标
9.3 质量指标
🔧 开发工具与工作流
10.1 推荐开发工具
10.2 Git工作流
分支策略
提交规范
# 提交信息格式
<type>(<scope>): <subject>
<body>
<footer>
# 示例
feat(components): 添加优惠券卡片组件
- 实现基础优惠券UI设计
- 支持自定义颜色和金额
- 添加点击回调功能
Closes #123
提交类型
feat: 新功能fix: Bug修复docs: 文档更新style: 代码格式化refactor: 重构test: 测试相关chore: 构建过程或辅助工具的变动