Flutter for OpenHarmony衣橱管家App实战:穿搭日历实现
本文介绍了衣橱管家App中穿搭日历功能的实现方案。该功能通过日历组件记录每日穿搭,帮助用户规划服饰搭配。主要技术点包括: 使用table_calendar组件构建可交互日历,支持月/周视图切换 通过事件标记显示有穿搭记录的日期 实现日期选择功能,展示当日穿搭详情 提供添加新记录的入口,采用Provider管理数据状态 功能包含日历展示、记录标记、详情查看和新增记录等核心模块,界面采用Materia

你有没有过这样的经历:早上出门前想不起来昨天穿了什么,结果连续几天穿了同样的搭配。穿搭日历功能就是用来解决这个问题的,它能记录每天穿了什么,让你的穿搭更有规划。
今天这篇文章,我来详细讲讲衣橱管家App里穿搭日历功能的实现。这个功能用到了日历组件、穿搭记录管理、数据关联查询等技术点。
功能设计思路
穿搭日历功能需要实现以下几点:
第一,展示一个可交互的日历,用户可以选择日期。
第二,在日历上标记有穿搭记录的日期。
第三,选择某一天后,显示当天的穿搭记录。
第四,支持添加新的穿搭记录。
页面基础结构
先看CalendarTab的定义:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:table_calendar/table_calendar.dart';
import 'package:intl/intl.dart';
import '../../providers/wardrobe_provider.dart';
import '../calendar/add_wear_record_screen.dart';
import '../calendar/wear_history_screen.dart';
import '../calendar/weather_outfit_screen.dart';
class CalendarTab extends StatefulWidget {
const CalendarTab({super.key});
State<CalendarTab> createState() => _CalendarTabState();
}
class _CalendarTabState extends State<CalendarTab> {
CalendarFormat _calendarFormat = CalendarFormat.month;
DateTime _focusedDay = DateTime.now();
DateTime? _selectedDay;
}
用StatefulWidget是因为需要维护日历的状态,包括当前显示的月份、选中的日期等。
table_calendar是一个功能强大的日历组件,支持月视图、周视图、事件标记等功能。
_calendarFormat控制日历显示格式,可以是月视图或周视图。
_focusedDay是当前聚焦的日期,决定日历显示哪个月份。
_selectedDay是用户选中的日期,可能为null。
页面布局
build方法构建整个页面:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('穿搭日历'),
actions: [
IconButton(
icon: const Icon(Icons.history),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const WearHistoryScreen()),
),
),
IconButton(
icon: const Icon(Icons.wb_sunny),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const WeatherOutfitScreen()),
),
),
],
),
body: Column(
children: [
_buildCalendar(),
Expanded(child: _buildDayRecords()),
],
),
floatingActionButton: FloatingActionButton(
backgroundColor: const Color(0xFFE91E63),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => AddWearRecordScreen(selectedDate: _selectedDay ?? DateTime.now()),
),
),
child: const Icon(Icons.add, color: Colors.white),
),
);
}
AppBar右边有两个按钮:历史记录和天气穿搭,方便用户快速访问相关功能。
body分两部分:上面是日历,下面是当天的穿搭记录,用Column和Expanded实现。
FloatingActionButton用来添加新的穿搭记录,点击后跳转到添加页面,并传入当前选中的日期。
日历组件
日历是这个页面的核心:
Widget _buildCalendar() {
return Consumer<WardrobeProvider>(
builder: (context, provider, child) {
return Card(
margin: EdgeInsets.all(16.w),
child: TableCalendar(
firstDay: DateTime.utc(2020, 1, 1),
lastDay: DateTime.utc(2030, 12, 31),
focusedDay: _focusedDay,
calendarFormat: _calendarFormat,
locale: 'zh_CN',
headerStyle: HeaderStyle(
formatButtonVisible: true,
titleCentered: true,
formatButtonDecoration: BoxDecoration(
color: const Color(0xFFE91E63).withOpacity(0.1),
borderRadius: BorderRadius.circular(8.r),
),
),
calendarStyle: CalendarStyle(
selectedDecoration: const BoxDecoration(
color: Color(0xFFE91E63),
shape: BoxShape.circle,
),
todayDecoration: BoxDecoration(
color: const Color(0xFFE91E63).withOpacity(0.3),
shape: BoxShape.circle,
),
markerDecoration: const BoxDecoration(
color: Colors.orange,
shape: BoxShape.circle,
),
),
selectedDayPredicate: (day) => isSameDay(_selectedDay, day),
eventLoader: (day) {
return provider.wearRecords
.where((r) => isSameDay(r.date, day))
.toList();
},
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
},
onFormatChanged: (format) {
setState(() {
_calendarFormat = format;
});
},
),
);
},
);
}
firstDay和lastDay定义日历的可选范围,这里设置为2020年到2030年。
locale: 'zh_CN’让日历显示中文,需要在main.dart里配置intl的本地化。
headerStyle配置日历头部样式,formatButtonVisible显示格式切换按钮,titleCentered让标题居中。
calendarStyle配置日历主体样式,selectedDecoration是选中日期的样式,todayDecoration是今天的样式。
markerDecoration是事件标记的样式,有穿搭记录的日期会显示一个小圆点。
事件加载
eventLoader用来加载每天的事件:
eventLoader: (day) {
return provider.wearRecords
.where((r) => isSameDay(r.date, day))
.toList();
},
这个函数会被日历组件调用,传入每一天的日期。
从provider.wearRecords里筛选出当天的记录。
isSameDay是table_calendar提供的工具函数,比较两个日期是否是同一天。
返回的列表长度决定了日期下方显示几个标记点。
日期选择回调
用户点击日期时触发:
onDaySelected: (selectedDay, focusedDay) {
setState(() {
_selectedDay = selectedDay;
_focusedDay = focusedDay;
});
},
selectedDay是用户点击的日期,focusedDay是当前聚焦的日期。
更新状态后,日历会高亮显示选中的日期,下方会显示当天的穿搭记录。
setState触发页面重建,确保UI和状态同步。
当天穿搭记录
显示选中日期的穿搭记录:
Widget _buildDayRecords() {
return Consumer<WardrobeProvider>(
builder: (context, provider, child) {
final selectedDate = _selectedDay ?? DateTime.now();
final records = provider.wearRecords
.where((r) => isSameDay(r.date, selectedDate))
.toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Text(
DateFormat('yyyy年MM月dd日').format(selectedDate),
style: TextStyle(fontSize: 16.sp, fontWeight: FontWeight.bold),
),
),
SizedBox(height: 8.h),
Expanded(
child: records.isEmpty
? _buildEmptyState(selectedDate)
: ListView.builder(
padding: EdgeInsets.symmetric(horizontal: 16.w),
itemCount: records.length,
itemBuilder: (context, index) {
final record = records[index];
return _buildRecordCard(record, provider);
},
),
),
],
);
},
);
}
如果_selectedDay为null,默认显示今天的记录。
用DateFormat格式化日期,显示"2024年01月15日"这样的格式。
如果当天没有记录,显示空状态提示;有记录则用ListView展示。
Consumer确保穿搭记录变化时UI能自动更新。
空状态展示
当天没有穿搭记录时的展示:
Widget _buildEmptyState(DateTime selectedDate) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.event_note, size: 48.sp, color: Colors.grey),
SizedBox(height: 8.h),
Text('当天无穿搭记录', style: TextStyle(color: Colors.grey)),
SizedBox(height: 8.h),
TextButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => AddWearRecordScreen(selectedDate: selectedDate),
),
),
child: const Text('添加记录'),
),
],
),
);
}
用图标加文字的方式展示空状态,比空白页面友好。
提供一个"添加记录"按钮,方便用户快速添加。
点击按钮跳转到添加页面,并传入当前选中的日期。
穿搭记录卡片
展示单条穿搭记录:
Widget _buildRecordCard(dynamic record, WardrobeProvider provider) {
String itemName = '未知';
if (record.clothingId != null) {
final item = provider.clothes.firstWhere(
(c) => c.id == record.clothingId,
orElse: () => throw Exception(),
);
itemName = item.name;
} else if (record.outfitId != null) {
final outfit = provider.outfits.firstWhere(
(o) => o.id == record.outfitId,
orElse: () => throw Exception(),
);
itemName = '搭配: ${outfit.name}';
}
return Card(
margin: EdgeInsets.only(bottom: 8.h),
child: ListTile(
leading: CircleAvatar(
backgroundColor: const Color(0xFFE91E63).withOpacity(0.1),
child: const Icon(Icons.checkroom, color: Color(0xFFE91E63)),
),
title: Text(itemName),
subtitle: Row(
children: [
if (record.weather != null) ...[
Icon(Icons.wb_sunny, size: 14.sp, color: Colors.orange),
SizedBox(width: 4.w),
Text(record.weather!, style: TextStyle(fontSize: 12.sp)),
SizedBox(width: 8.w),
],
if (record.mood != null) ...[
Icon(Icons.mood, size: 14.sp, color: Colors.green),
SizedBox(width: 4.w),
Text(record.mood!, style: TextStyle(fontSize: 12.sp)),
],
],
),
),
);
}
穿搭记录可能关联单件衣物或整套搭配,需要根据clothingId或outfitId查询对应的名称。
orElse处理找不到的情况,可能是衣物或搭配被删除了。
subtitle显示天气和心情信息,用图标加文字的方式展示。
…是Dart的展开操作符,配合if使用可以条件性地添加多个Widget。
日历格式切换
用户可以切换月视图和周视图:
onFormatChanged: (format) {
setState(() {
_calendarFormat = format;
});
},
table_calendar支持月视图、双周视图、周视图三种格式。
点击日历头部的格式按钮可以切换。
周视图在手机上更紧凑,月视图能看到更多日期。
日历本地化配置
要让日历显示中文,需要在main.dart里配置:
import 'package:intl/date_symbol_data_local.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await initializeDateFormatting('zh_CN', null);
runApp(const MyApp());
}
initializeDateFormatting初始化日期格式化数据。
'zh_CN’是中文简体的locale代码。
这样日历的星期、月份等都会显示中文。
穿搭记录数据模型
穿搭记录的数据结构:
class WearRecord {
final String id;
final DateTime date;
final String? clothingId; // 单件衣物ID
final String? outfitId; // 搭配ID
final String? weather; // 天气
final String? mood; // 心情
WearRecord({
required this.id,
required this.date,
this.clothingId,
this.outfitId,
this.weather,
this.mood,
});
}
clothingId和outfitId二选一,表示穿的是单件衣物还是整套搭配。
weather和mood是可选的附加信息,记录当天的天气和心情。
这些信息可以用来做穿搭分析,比如什么天气穿什么衣服。
isSameDay的实现
比较两个日期是否是同一天:
bool isSameDay(DateTime? a, DateTime? b) {
if (a == null || b == null) return false;
return a.year == b.year && a.month == b.month && a.day == b.day;
}
只比较年、月、日,忽略时、分、秒。
处理null的情况,两个都是null返回false。
table_calendar包里已经提供了这个函数,直接用就行。
添加穿搭记录
点击FloatingActionButton跳转到添加页面:
FloatingActionButton(
backgroundColor: const Color(0xFFE91E63),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (_) => AddWearRecordScreen(selectedDate: _selectedDay ?? DateTime.now()),
),
),
child: const Icon(Icons.add, color: Colors.white),
)
传入当前选中的日期,这样添加页面知道要为哪一天添加记录。
如果没有选中日期,默认用今天。
添加成功后返回,日历上会显示新的标记点。
总结
穿搭日历功能的实现涉及到日历组件、事件标记、数据关联查询等多个方面。关键点在于:
用table_calendar组件实现可交互的日历,支持日期选择和事件标记。
用eventLoader加载每天的穿搭记录,在日历上显示标记点。
选中日期后显示当天的穿搭记录,支持查看详情和添加新记录。
在OpenHarmony平台上,这套实现方式完全适用。穿搭日历是一个很实用的功能,能帮助用户回顾和规划自己的穿搭。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐
所有评论(0)