Flutter实现日期切换式独立进度环与功能组件的首页方案
实现按日期切换的Flutter首页进度追踪功能
嘿,我明白你想要打造一个能按日期切换进度的Flutter首页,每个日期对应独立的环形进度条、记录体征(Record Vitals)和开始锻炼组件对吧?这其实可以通过数据模型+状态管理的组合来轻松实现,下面是具体的实现思路和完整代码示例:
核心思路拆解
- 定义数据模型:为每个日期创建独立的进度数据对象,存储进度百分比、锻炼状态等信息
- 状态管理:用
StatefulWidget维护当前选中的日期和所有日期的进度数据列表 - 日期列表:生成当月的日期列表,点击时切换选中状态并更新页面内容
- 动态组件:根据选中的日期,实时渲染对应的环形进度条和功能组件
完整代码实现
1. 先添加依赖(如果用现成的环形进度条包)
在pubspec.yaml中添加:
dependencies: flutter: sdk: flutter flutter_circular_progress_bar: ^2.0.1 # 可选,简化环形进度条实现
2. 数据模型与页面实现
import 'package:flutter/material.dart'; import 'package:flutter_circular_progress_bar/flutter_circular_progress_bar.dart'; import 'package:intl/intl.dart'; // 每个日期的进度数据模型 class DailyProgress { final DateTime date; final double progressPercentage; final bool hasRecordedVitals; final bool hasDoneWorkout; DailyProgress({ required this.date, required this.progressPercentage, this.hasRecordedVitals = false, this.hasDoneWorkout = false, }); } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { late List<DailyProgress> _dailyProgressList; late DailyProgress _selectedDay; @override void initState() { super.initState(); // 初始化当月的进度数据(这里模拟数据,实际可从API/本地存储获取) _dailyProgressList = _generateMockMonthlyProgress(); // 默认选中今天 _selectedDay = _dailyProgressList.firstWhere( (day) => DateFormat('yyyy-MM-dd').format(day.date) == DateFormat('yyyy-MM-dd').format(DateTime.now()), orElse: () => _dailyProgressList.first, ); } // 生成模拟的当月进度数据 List<DailyProgress> _generateMockMonthlyProgress() { final now = DateTime.now(); final firstDayOfMonth = DateTime(now.year, now.month, 1); final lastDayOfMonth = DateTime(now.year, now.month + 1, 0); final List<DailyProgress> progressList = []; for (int day = firstDayOfMonth.day; day <= lastDayOfMonth.day; day++) { final date = DateTime(now.year, now.month, day); // 模拟不同日期的进度:今天50%,昨天25%,其他随机0-100% double progress = 0; if (day == now.day) { progress = 50; } else if (day == now.day - 1) { progress = 25; } else { progress = (DateTime.now().millisecond % 100).toDouble(); } progressList.add(DailyProgress( date: date, progressPercentage: progress, hasRecordedVitals: day % 3 == 0, // 每3天模拟已记录体征 hasDoneWorkout: day % 2 == 0, // 每2天模拟已完成锻炼 )); } return progressList; } // 切换选中日期 void _onDaySelected(DailyProgress day) { setState(() { _selectedDay = day; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('健康追踪')), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 环形进度条 Center( child: SizedBox( width: 200, height: 200, child: CircularProgressBar( value: _selectedDay.progressPercentage, progressStrokeWidth: 15, backgroundStrokeWidth: 15, progressColor: Colors.green, backgroundColor: Colors.grey.shade200, centerWidget: Text( '${_selectedDay.progressPercentage.toInt()}%', style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), ), ), ), const SizedBox(height: 24), // 当月日期列表(横向滚动) SizedBox( height: 80, child: ListView.builder( scrollDirection: Axis.horizontal, itemCount: _dailyProgressList.length, itemBuilder: (context, index) { final day = _dailyProgressList[index]; final isSelected = _selectedDay.date == day.date; return GestureDetector( onTap: () => _onDaySelected(day), child: Container( width: 60, margin: const EdgeInsets.symmetric(horizontal: 4), decoration: BoxDecoration( color: isSelected ? Colors.green.shade100 : Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: isSelected ? Colors.green : Colors.grey.shade300), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( '${day.date.day}', style: TextStyle( fontSize: 18, fontWeight: isSelected ? FontWeight.bold : FontWeight.normal, color: isSelected ? Colors.green : Colors.black, ), ), Text( DateFormat('E').format(day.date), style: TextStyle( fontSize: 12, color: isSelected ? Colors.green : Colors.grey.shade600, ), ), ], ), ), ); }, ), ), const SizedBox(height: 24), // Record Vitals 组件 ElevatedButton( onPressed: () { // 处理记录体征逻辑,可根据_selectedDay.date区分日期 setState(() { // 模拟更新记录状态 final index = _dailyProgressList.indexOf(_selectedDay); _dailyProgressList[index] = DailyProgress( date: _selectedDay.date, progressPercentage: _selectedDay.progressPercentage + 10, // 记录后进度+10% hasRecordedVitals: true, hasDoneWorkout: _selectedDay.hasDoneWorkout, ); _selectedDay = _dailyProgressList[index]; }); }, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: _selectedDay.hasRecordedVitals ? Colors.grey.shade300 : Colors.blue, ), child: Text( _selectedDay.hasRecordedVitals ? '已记录体征' : 'Record Vitals', style: const TextStyle(fontSize: 16), ), ), const SizedBox(height: 12), // 开始锻炼组件 ElevatedButton( onPressed: () { // 跳转至锻炼页面,传递当前选中日期 Navigator.push( context, MaterialPageRoute( builder: (context) => WorkoutPage(selectedDate: _selectedDay.date), ), ); }, style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: _selectedDay.hasDoneWorkout ? Colors.grey.shade300 : Colors.orange, ), child: Text( _selectedDay.hasDoneWorkout ? '今日已锻炼' : '开始锻炼', style: const TextStyle(fontSize: 16), ), ), ], ), ), ); } } // 模拟锻炼页面 class WorkoutPage extends StatelessWidget { final DateTime selectedDate; const WorkoutPage({super.key, required this.selectedDate}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('${selectedDate.day}日锻炼')), body: const Center(child: Text('锻炼页面内容')), ); } } void main() { runApp(const MaterialApp(home: HomePage())); }
关键细节说明
- 数据模型:
DailyProgress类封装了每个日期的所有关联数据,确保每个日期的进度、状态都是独立的 - 日期切换:点击日期列表项时,通过
setState更新选中的日期,页面会自动渲染对应的数据 - 动态状态:功能按钮的样式和文本会根据当前选中日期的状态(是否已记录体征/锻炼)动态变化
- 进度更新:点击Record Vitals时会模拟更新该日期的进度,实际项目中可替换为API调用或本地存储操作
这样就能完美实现你想要的按日期追踪进度的功能啦,每个日期的进度条和组件都是完全独立的,切换日期时会自动展示对应的数据~
内容的提问来源于stack exchange,提问作者Olamide Osuolale
相关产品推荐
相关产品推荐

