You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter实现日期切换式独立进度环与功能组件的首页方案

实现按日期切换的Flutter首页进度追踪功能

嘿,我明白你想要打造一个能按日期切换进度的Flutter首页,每个日期对应独立的环形进度条、记录体征(Record Vitals)和开始锻炼组件对吧?这其实可以通过数据模型+状态管理的组合来轻松实现,下面是具体的实现思路和完整代码示例:

核心思路拆解

  1. 定义数据模型:为每个日期创建独立的进度数据对象,存储进度百分比、锻炼状态等信息
  2. 状态管理:用StatefulWidget维护当前选中的日期和所有日期的进度数据列表
  3. 日期列表:生成当月的日期列表,点击时切换选中状态并更新页面内容
  4. 动态组件:根据选中的日期,实时渲染对应的环形进度条和功能组件

完整代码实现

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()));
}

关键细节说明

  1. 数据模型:DailyProgress类封装了每个日期的所有关联数据,确保每个日期的进度、状态都是独立的
  2. 日期切换:点击日期列表项时,通过setState更新选中的日期,页面会自动渲染对应的数据
  3. 动态状态:功能按钮的样式和文本会根据当前选中日期的状态(是否已记录体征/锻炼)动态变化
  4. 进度更新:点击Record Vitals时会模拟更新该日期的进度,实际项目中可替换为API调用或本地存储操作

这样就能完美实现你想要的按日期追踪进度的功能啦,每个日期的进度条和组件都是完全独立的,切换日期时会自动展示对应的数据~

内容的提问来源于stack exchange,提问作者Olamide Osuolale

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 16:30:33