Flutter 框架跨平台鸿蒙开发 - 手工DIY教程记录本:记录每一个创意时刻
✅ 教程创建和管理✅ 分类筛选(7个分类)✅ 难度标记(3个等级)✅ 材料和工具清单✅ 详细步骤记录✅ 注意事项提示✅ 时间估算✅ 多种排序方式✅ 数据持久化✅ 完整的增删改查。
Flutter手工DIY教程记录本:记录每一个创意时刻
项目简介
手工DIY教程记录本是一款专为手工爱好者设计的Flutter应用,可以帮助你记录和整理各种手工制作教程。无论是折纸、编织、刺绣还是木工,都能在这里找到完美的记录方式。
运行效果图




核心功能
- 教程管理:创建、编辑、删除DIY教程
- 分类筛选:按折纸、编织、刺绣等分类管理
- 难度标记:简单、中等、困难三个等级
- 材料清单:记录所需材料和工具
- 步骤记录:详细记录每个制作步骤
- 注意事项:为每个步骤添加提示
- 时间估算:记录预计完成时间
- 收藏功能:标记喜欢的教程
- 排序功能:按时间、标题、难度排序
- 数据持久化:本地保存所有数据
技术特点
- Material Design 3设计风格
- 网格布局展示教程卡片
- 难度颜色区分(绿色/橙色/红色)
- 响应式设计,适配各种屏幕
- SharedPreferences数据持久化
- 完善的表单验证
效果展示
应用包含以下主要界面:
- 教程列表页:网格展示所有教程,支持分类筛选和排序
- 添加教程页:录入教程的基本信息、材料、工具和步骤
- 教程详情页:查看完整的教程内容
- 编辑教程页:修改教程信息
- 分类筛选栏:快速切换不同类别的教程
项目结构
lib/
└── main.dart # 主程序文件(包含所有代码)
核心代码实现
1. 数据模型设计
首先定义教程步骤和教程的数据模型:
// 教程步骤模型
class TutorialStep {
String description;
String photo;
String tips;
TutorialStep({
required this.description,
this.photo = '',
this.tips = '',
});
Map<String, dynamic> toJson() => {
'description': description,
'photo': photo,
'tips': tips,
};
factory TutorialStep.fromJson(Map<String, dynamic> json) => TutorialStep(
description: json['description'],
photo: json['photo'] ?? '',
tips: json['tips'] ?? '',
);
}
步骤模型字段:
description:步骤描述photo:步骤照片(可选)tips:注意事项(可选)
// DIY教程模型
class DIYTutorial {
String id;
String title;
String category;
String difficulty;
int estimatedTime;
List<String> materials;
List<String> tools;
List<TutorialStep> steps;
String coverPhoto;
String notes;
DateTime createTime;
bool isFavorite;
DIYTutorial({
required this.id,
required this.title,
required this.category,
this.difficulty = '简单',
this.estimatedTime = 30,
List<String>? materials,
List<String>? tools,
List<TutorialStep>? steps,
this.coverPhoto = '',
this.notes = '',
required this.createTime,
this.isFavorite = false,
}) : materials = materials ?? [],
tools = tools ?? [],
steps = steps ?? [];
}
教程模型字段:
id:唯一标识符title:教程标题category:分类(折纸、编织等)difficulty:难度等级estimatedTime:预计时间(分钟)materials:材料列表tools:工具列表steps:步骤列表coverPhoto:封面照片notes:备注createTime:创建时间isFavorite:是否收藏
2. 数据持久化
使用SharedPreferences保存教程数据:
Future<void> _loadTutorials() async {
final prefs = await SharedPreferences.getInstance();
final tutorialsJson = prefs.getString('tutorials');
if (tutorialsJson != null) {
final List<dynamic> decoded = jsonDecode(tutorialsJson);
setState(() {
_tutorials = decoded.map((json) => DIYTutorial.fromJson(json)).toList();
});
}
}
Future<void> _saveTutorials() async {
final prefs = await SharedPreferences.getInstance();
final tutorialsJson = jsonEncode(_tutorials.map((t) => t.toJson()).toList());
await prefs.setString('tutorials', tutorialsJson);
}
3. 网格布局展示
使用GridView展示教程卡片:
GridView.builder(
padding: const EdgeInsets.all(16),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 12,
mainAxisSpacing: 12,
childAspectRatio: 0.75,
),
itemCount: _filteredTutorials.length,
itemBuilder: (context, index) {
return _buildTutorialCard(_filteredTutorials[index]);
},
)
网格参数说明:
crossAxisCount: 2:每行2列crossAxisSpacing: 12:列间距mainAxisSpacing: 12:行间距childAspectRatio: 0.75:宽高比
4. 分类筛选功能
实现分类筛选和排序:
List<DIYTutorial> get _filteredTutorials {
var filtered = _tutorials;
if (_filterCategory != '全部') {
filtered = filtered.where((t) => t.category == _filterCategory).toList();
}
switch (_sortBy) {
case '最新':
filtered.sort((a, b) => b.createTime.compareTo(a.createTime));
break;
case '最旧':
filtered.sort((a, b) => a.createTime.compareTo(b.createTime));
break;
case '标题':
filtered.sort((a, b) => a.title.compareTo(b.title));
break;
case '难度':
final difficultyOrder = {'简单': 0, '中等': 1, '困难': 2};
filtered.sort((a, b) =>
difficultyOrder[a.difficulty]!.compareTo(difficultyOrder[b.difficulty]!));
break;
}
return filtered;
}
5. 动态添加材料和工具
使用对话框添加材料和工具:
void _addMaterial() {
showDialog(
context: context,
builder: (context) {
final controller = TextEditingController();
return AlertDialog(
title: const Text('添加材料'),
content: TextField(
controller: controller,
decoration: const InputDecoration(
hintText: '输入材料名称',
border: OutlineInputBorder(),
),
autofocus: true,
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
FilledButton(
onPressed: () {
if (controller.text.isNotEmpty) {
setState(() {
_materials.add(controller.text);
});
Navigator.pop(context);
}
},
child: const Text('添加'),
),
],
);
},
);
}
6. 步骤管理
添加和展示制作步骤:
void _addStep() {
showDialog(
context: context,
builder: (context) {
final descController = TextEditingController();
final tipsController = TextEditingController();
return AlertDialog(
title: const Text('添加步骤'),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(
controller: descController,
decoration: const InputDecoration(
labelText: '步骤描述',
border: OutlineInputBorder(),
),
maxLines: 3,
autofocus: true,
),
const SizedBox(height: 12),
TextField(
controller: tipsController,
decoration: const InputDecoration(
labelText: '注意事项(可选)',
border: OutlineInputBorder(),
),
maxLines: 2,
),
],
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
FilledButton(
onPressed: () {
if (descController.text.isNotEmpty) {
setState(() {
_steps.add(TutorialStep(
description: descController.text,
tips: tipsController.text,
));
});
Navigator.pop(context);
}
},
child: const Text('添加'),
),
],
);
},
);
}
技术要点详解
Chip组件的使用
Chip是Flutter中用于显示标签的组件:
// 可删除的Chip
Chip(
label: Text('材料名称'),
deleteIcon: const Icon(Icons.close, size: 18),
onDeleted: () {
// 删除逻辑
},
)
// 可选择的ChoiceChip
ChoiceChip(
label: Text('简单'),
selected: _difficulty == '简单',
onSelected: (selected) {
if (selected) {
setState(() {
_difficulty = '简单';
});
}
},
)
// 筛选用的FilterChip
FilterChip(
label: Text('折纸'),
selected: _filterCategory == '折纸',
onSelected: (selected) {
setState(() {
_filterCategory = '折纸';
});
},
)
Slider滑块组件
用于选择预计时间:
Slider(
value: _estimatedTime.toDouble(),
min: 10,
max: 300,
divisions: 29,
label: '$_estimatedTime分钟',
onChanged: (value) {
setState(() {
_estimatedTime = value.toInt();
});
},
)
参数说明:
value:当前值min:最小值max:最大值divisions:分段数label:显示的标签
WillPopScope的使用
用于在返回时传递数据:
WillPopScope(
onWillPop: () async {
Navigator.pop(context, _tutorial);
return false;
},
child: Scaffold(
// ...
),
)
这样可以确保在用户点击返回按钮时,将修改后的数据传回上一页。
界面布局分析
主页面布局
Scaffold
├── AppBar
│ ├── title: "手工DIY教程记录本"
│ ├── actions
│ │ ├── 排序菜单
│ │ └── 使用说明按钮
├── body: Column
│ ├── 分类筛选栏
│ └── GridView (教程网格)
└── FloatingActionButton
└── 新建教程按钮
教程卡片布局
Card
└── InkWell
└── Column
├── 封面图片区域 (Expanded flex: 3)
│ ├── 收藏按钮 (右上角)
│ └── 难度标签 (右下角)
└── 信息区域 (Expanded flex: 2)
├── 标题
├── 分类和时间
└── 步骤数和删除按钮
详情页面布局
Scaffold
├── AppBar
│ ├── title: 教程标题
│ ├── 收藏按钮
│ └── 编辑按钮
└── body: ListView
├── 封面图片
├── 标签行(难度、分类、时间)
├── 材料清单
├── 工具清单
├── 制作步骤
└── 备注
功能扩展建议
1. 图片功能
集成真实的图片选择和展示:
import 'package:image_picker/image_picker.dart';
Future<void> _pickCoverImage() async {
final picker = ImagePicker();
final pickedFile = await picker.pickImage(source: ImageSource.gallery);
if (pickedFile != null) {
setState(() {
_coverPhoto = pickedFile.path;
});
}
}
2. 分享功能
分享教程给朋友:
import 'package:share_plus/share_plus.dart';
void _shareTutorial(DIYTutorial tutorial) {
final text = '''
${tutorial.title}
分类:${tutorial.category}
难度:${tutorial.difficulty}
时间:${tutorial.estimatedTime}分钟
材料:${tutorial.materials.join('、')}
工具:${tutorial.tools.join('、')}
步骤:
${tutorial.steps.asMap().entries.map((e) => '${e.key + 1}. ${e.value.description}').join('\n')}
''';
Share.share(text);
}
3. 搜索功能
添加教程搜索:
String _searchQuery = '';
List<DIYTutorial> get _searchedTutorials {
if (_searchQuery.isEmpty) return _filteredTutorials;
return _filteredTutorials.where((t) =>
t.title.toLowerCase().contains(_searchQuery.toLowerCase()) ||
t.category.toLowerCase().contains(_searchQuery.toLowerCase())
).toList();
}
4. 标签系统
为教程添加自定义标签:
class DIYTutorial {
// ... 其他字段
List<String> tags;
DIYTutorial({
// ... 其他参数
List<String>? tags,
}) : tags = tags ?? [];
}
5. 进度追踪
记录教程的完成进度:
class TutorialProgress {
String tutorialId;
List<bool> completedSteps;
DateTime startTime;
DateTime? completeTime;
double get progress {
if (completedSteps.isEmpty) return 0;
return completedSteps.where((s) => s).length / completedSteps.length;
}
}
常见问题解答
Q1: 如何批量导入教程?
A: 可以实现CSV或JSON导入功能:
import 'dart:io';
import 'package:file_picker/file_picker.dart';
Future<void> importTutorials() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['json'],
);
if (result != null) {
File file = File(result.files.single.path!);
String contents = await file.readAsString();
List<dynamic> jsonList = jsonDecode(contents);
List<DIYTutorial> imported = jsonList
.map((json) => DIYTutorial.fromJson(json))
.toList();
setState(() {
_tutorials.addAll(imported);
});
_saveTutorials();
}
}
Q2: 如何实现教程评分?
A: 添加评分字段:
class DIYTutorial {
// ... 其他字段
double rating;
DIYTutorial({
// ... 其他参数
this.rating = 0,
});
}
// 使用RatingBar组件
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
RatingBar.builder(
initialRating: tutorial.rating,
minRating: 1,
direction: Axis.horizontal,
itemCount: 5,
itemBuilder: (context, _) => Icon(
Icons.star,
color: Colors.amber,
),
onRatingUpdate: (rating) {
setState(() {
tutorial.rating = rating;
});
},
)
Q3: 如何添加视频教程?
A: 集成video_player:
import 'package:video_player/video_player.dart';
class TutorialStep {
String description;
String photo;
String video; // 添加视频字段
String tips;
}
// 播放视频
VideoPlayerController _controller = VideoPlayerController.file(
File(step.video),
);
await _controller.initialize();
_controller.play();
项目总结
实现的功能
✅ 教程创建和管理
✅ 分类筛选(7个分类)
✅ 难度标记(3个等级)
✅ 材料和工具清单
✅ 详细步骤记录
✅ 注意事项提示
✅ 时间估算
✅ 收藏功能
✅ 多种排序方式
✅ 数据持久化
✅ 完整的增删改查
技术亮点
- 网格布局:美观的卡片展示
- 动态表单:灵活添加材料、工具和步骤
- Chip组件:优雅的标签展示
- Slider组件:直观的时间选择
- 颜色区分:难度等级一目了然
- 数据持久化:完整的JSON序列化
- 响应式设计:适配各种屏幕
应用场景
- 🎨 手工爱好者记录创作
- 📚 DIY教程整理归档
- 👨🏫 手工课程教学辅助
- 🎁 创意礼物制作指南
- 🏠 家居装饰DIY记录
- 👶 亲子手工活动
- 🎭 艺术创作过程记录
手工DIY教程记录本是一款功能完善、界面美观的Flutter应用,非常适合手工爱好者使用。通过这个项目,你可以学习到网格布局、动态表单、Chip组件等Flutter开发技能。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)