Flutter手工DIY教程记录本:记录每一个创意时刻

项目简介

手工DIY教程记录本是一款专为手工爱好者设计的Flutter应用,可以帮助你记录和整理各种手工制作教程。无论是折纸、编织、刺绣还是木工,都能在这里找到完美的记录方式。
运行效果图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

核心功能

  • 教程管理:创建、编辑、删除DIY教程
  • 分类筛选:按折纸、编织、刺绣等分类管理
  • 难度标记:简单、中等、困难三个等级
  • 材料清单:记录所需材料和工具
  • 步骤记录:详细记录每个制作步骤
  • 注意事项:为每个步骤添加提示
  • 时间估算:记录预计完成时间
  • 收藏功能:标记喜欢的教程
  • 排序功能:按时间、标题、难度排序
  • 数据持久化:本地保存所有数据

技术特点

  • Material Design 3设计风格
  • 网格布局展示教程卡片
  • 难度颜色区分(绿色/橙色/红色)
  • 响应式设计,适配各种屏幕
  • SharedPreferences数据持久化
  • 完善的表单验证

效果展示

应用包含以下主要界面:

  1. 教程列表页:网格展示所有教程,支持分类筛选和排序
  2. 添加教程页:录入教程的基本信息、材料、工具和步骤
  3. 教程详情页:查看完整的教程内容
  4. 编辑教程页:修改教程信息
  5. 分类筛选栏:快速切换不同类别的教程

项目结构

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个等级)
✅ 材料和工具清单
✅ 详细步骤记录
✅ 注意事项提示
✅ 时间估算
✅ 收藏功能
✅ 多种排序方式
✅ 数据持久化
✅ 完整的增删改查

技术亮点

  1. 网格布局:美观的卡片展示
  2. 动态表单:灵活添加材料、工具和步骤
  3. Chip组件:优雅的标签展示
  4. Slider组件:直观的时间选择
  5. 颜色区分:难度等级一目了然
  6. 数据持久化:完整的JSON序列化
  7. 响应式设计:适配各种屏幕

应用场景

  • 🎨 手工爱好者记录创作
  • 📚 DIY教程整理归档
  • 👨‍🏫 手工课程教学辅助
  • 🎁 创意礼物制作指南
  • 🏠 家居装饰DIY记录
  • 👶 亲子手工活动
  • 🎭 艺术创作过程记录

手工DIY教程记录本是一款功能完善、界面美观的Flutter应用,非常适合手工爱好者使用。通过这个项目,你可以学习到网格布局、动态表单、Chip组件等Flutter开发技能。

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

Logo

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

更多推荐