Flutter运动打卡社交:打造全功能健身社交平台

项目概述

在全民健身的时代背景下,运动社交应用成为连接健身爱好者的重要平台。本教程将带你使用Flutter开发一个功能完整的运动打卡社交应用,集成运动记录、社交分享、挑战活动、好友互动等核心功能。
运行效果图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

应用特色

  • 运动记录:多种运动类型支持,详细数据统计
  • 社交分享:运动动态发布,点赞评论互动
  • 挑战活动:多样化运动挑战,激励用户坚持
  • 数据统计:个人运动数据可视化展示
  • 好友系统:关注好友,分享运动成果

技术架构

核心技术栈

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8

项目结构

lib/
├── main.dart              # 应用入口和主要逻辑
├── models/               # 数据模型(集成在main.dart中)
│   ├── workout_record.dart # 运动记录模型
│   ├── fitness_user.dart   # 用户模型
│   ├── challenge.dart      # 挑战活动模型
│   └── comment.dart        # 评论模型
├── screens/              # 页面组件(集成在main.dart中)
│   ├── home_page.dart      # 首页
│   ├── discover_page.dart  # 发现页面
│   ├── record_page.dart    # 记录页面
│   ├── social_page.dart    # 社交页面
│   └── profile_page.dart   # 个人页面
└── widgets/              # 自定义组件(集成在main.dart中)
    ├── workout_card.dart   # 运动卡片
    ├── challenge_card.dart # 挑战卡片
    └── user_card.dart      # 用户卡片

数据模型设计

运动记录模型(WorkoutRecord)

运动记录是应用的核心数据结构,记录用户的运动详情:

class WorkoutRecord {
  final String id;              // 记录唯一标识
  final String userId;          // 用户ID
  final String userName;        // 用户姓名
  final String userAvatar;      // 用户头像
  final String workoutType;     // 运动类型
  final String title;           // 运动标题
  final String description;     // 运动描述
  final DateTime startTime;     // 开始时间
  final DateTime endTime;       // 结束时间
  final int duration;           // 运动时长(分钟)
  final double distance;        // 运动距离(公里)
  final int calories;           // 消耗卡路里
  final int steps;              // 步数
  final double avgHeartRate;    // 平均心率
  final List<String> photos;    // 运动照片
  final String location;        // 运动地点
  final Map<String, dynamic> stats; // 详细统计数据
  final List<String> tags;      // 运动标签
  final int likes;              // 点赞数
  final int comments;           // 评论数
  final bool isLiked;           // 是否已点赞
  final DateTime createdAt;     // 创建时间
}

运动记录模型包含了丰富的运动数据,支持多种运动类型的记录和展示。

用户模型(FitnessUser)

用户模型存储用户的基本信息和运动统计:

class FitnessUser {
  final String id;              // 用户唯一标识
  final String name;            // 用户姓名
  final String avatar;          // 用户头像
  final String bio;             // 个人简介
  final int age;                // 年龄
  final String gender;          // 性别
  final double height;          // 身高(cm)
  final double weight;          // 体重(kg)
  final int totalWorkouts;      // 总运动次数
  final double totalDistance;   // 总运动距离
  final int totalCalories;      // 总消耗卡路里
  final int totalDuration;      // 总运动时长
  final int followers;          // 粉丝数
  final int following;          // 关注数
  final List<String> achievements; // 成就列表
  final bool isFollowing;       // 是否已关注
  final DateTime joinDate;      // 加入时间
}

用户模型还包含BMI计算功能:

double get bmi => weight / ((height / 100) * (height / 100));

String get bmiCategory {
  if (bmi < 18.5) return '偏瘦';
  if (bmi < 24) return '正常';
  if (bmi < 28) return '偏胖';
  return '肥胖';
}

挑战活动模型(Challenge)

挑战活动模型定义各种运动挑战:

class Challenge {
  final String id;              // 挑战唯一标识
  final String title;           // 挑战标题
  final String description;     // 挑战描述
  final String type;            // 挑战类型(distance, duration, calories, steps)
  final double target;          // 目标值
  final String unit;            // 单位
  final DateTime startDate;     // 开始日期
  final DateTime endDate;       // 结束日期
  final int participants;       // 参与人数
  final String reward;          // 奖励
  final bool isJoined;          // 是否已参加
  final double progress;        // 当前进度
  final String coverImage;      // 封面图片
}

挑战模型包含进度计算功能:

int get daysLeft => endDate.difference(DateTime.now()).inDays;
double get progressPercentage => (progress / target * 100).clamp(0, 100);
bool get isActive => DateTime.now().isBefore(endDate) && DateTime.now().isAfter(startDate);

评论模型(Comment)

评论模型处理用户互动:

class Comment {
  final String id;              // 评论唯一标识
  final String userId;          // 评论者ID
  final String userName;        // 评论者姓名
  final String userAvatar;      // 评论者头像
  final String content;         // 评论内容
  final DateTime createdAt;     // 评论时间
  final int likes;              // 点赞数
  final bool isLiked;           // 是否已点赞
}

应用主体结构

应用入口

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '运动打卡社交',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.orange),
        useMaterial3: true,
      ),
      home: const FitnessHomePage(),
    );
  }
}

应用采用橙色作为主题色,营造活力四射的运动氛围。

主页面结构

主页面使用底部导航栏实现五个核心功能模块:

class FitnessHomePage extends StatefulWidget {
  
  State<FitnessHomePage> createState() => _FitnessHomePageState();
}

class _FitnessHomePageState extends State<FitnessHomePage> {
  int _selectedIndex = 0;
  final List<WorkoutRecord> _workoutRecords = [];
  final List<FitnessUser> _users = [];
  final List<Challenge> _challenges = [];
  final List<Comment> _comments = [];
  FitnessUser? _currentUser;

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('运动打卡社交'),
        backgroundColor: Colors.orange.withValues(alpha: 0.1),
        actions: [
          IconButton(
            onPressed: () => _showSearchDialog(),
            icon: const Icon(Icons.search),
          ),
          IconButton(
            onPressed: () => _showNotificationDialog(),
            icon: const Icon(Icons.notifications),
          ),
        ],
      ),
      body: IndexedStack(
        index: _selectedIndex,
        children: [
          _buildHomePage(),
          _buildDiscoverPage(),
          _buildRecordPage(),
          _buildSocialPage(),
          _buildProfilePage(),
        ],
      ),
      bottomNavigationBar: NavigationBar(
        selectedIndex: _selectedIndex,
        onDestinationSelected: (index) {
          setState(() => _selectedIndex = index);
        },
        destinations: const [
          NavigationDestination(icon: Icon(Icons.home), label: '首页'),
          NavigationDestination(icon: Icon(Icons.explore), label: '发现'),
          NavigationDestination(icon: Icon(Icons.add_circle), label: '记录'),
          NavigationDestination(icon: Icon(Icons.people), label: '社交'),
          NavigationDestination(icon: Icon(Icons.person), label: '我的'),
        ],
      ),
    );
  }
}
## 数据生成与管理

### 模拟数据生成

应用使用模拟数据生成器创建丰富的运动和用户数据:

```dart

void initState() {
  super.initState();
  _generateUsers();
  _generateWorkoutRecords();
  _generateChallenges();
  _generateComments();
  _setCurrentUser();
}

void _generateUsers() {
  final names = ['张健', '李跑', '王游', '赵骑', '钱举', '孙瑜', '周舞', '吴球', '郑拳', '陈跳'];
  final bios = [
    '热爱跑步的马拉松爱好者',
    '健身达人,专注力量训练',
    '游泳教练,水中自由者',
    '骑行爱好者,追风少年',
    '瑜伽导师,身心平衡',
  ];

  final random = Random();

  for (int i = 0; i < 10; i++) {
    _users.add(FitnessUser(
      id: 'user_$i',
      name: names[i],
      avatar: 'avatar_${i + 1}.jpg',
      bio: bios[random.nextInt(bios.length)],
      age: 20 + random.nextInt(30),
      gender: i % 2 == 0 ? '男' : '女',
      height: 160 + random.nextInt(25).toDouble(),
      weight: 50 + random.nextInt(30).toDouble(),
      totalWorkouts: random.nextInt(500),
      totalDistance: random.nextDouble() * 1000,
      totalCalories: random.nextInt(50000),
      totalDuration: random.nextInt(10000),
      followers: random.nextInt(1000),
      following: random.nextInt(500),
      achievements: ['新手上路', '坚持不懈', '运动达人'].take(random.nextInt(3) + 1).toList(),
      isFollowing: random.nextBool(),
      joinDate: DateTime.now().subtract(Duration(days: random.nextInt(365))),
    ));
  }
}

void _generateWorkoutRecords() {
  final workoutTypes = ['跑步', '骑行', '游泳', '健身', '瑜伽', '篮球', '足球', '网球'];
  final titles = [
    '晨跑打卡',
    '夜骑归来',
    '游泳训练',
    '力量训练',
    '瑜伽冥想',
    '篮球对战',
    '足球训练',
    '网球练习',
  ];
  final descriptions = [
    '今天的运动感觉很棒!',
    '挑战自己,突破极限',
    '坚持就是胜利',
    '运动让我更健康',
    '享受运动的快乐',
  ];
  final locations = ['体育公园', '健身房', '游泳馆', '篮球场', '足球场', '网球场', '瑜伽馆'];
  final tags = ['健康', '坚持', '挑战', '快乐', '成长', '突破'];

  final random = Random();

  for (int i = 0; i < 20; i++) {
    final user = _users[random.nextInt(_users.length)];
    final workoutType = workoutTypes[random.nextInt(workoutTypes.length)];
    final startTime = DateTime.now().subtract(Duration(
      days: random.nextInt(30),
      hours: random.nextInt(24),
      minutes: random.nextInt(60),
    ));
    final duration = 30 + random.nextInt(120);
    final endTime = startTime.add(Duration(minutes: duration));

    _workoutRecords.add(WorkoutRecord(
      id: 'workout_$i',
      userId: user.id,
      userName: user.name,
      userAvatar: user.avatar,
      workoutType: workoutType,
      title: titles[workoutTypes.indexOf(workoutType)],
      description: descriptions[random.nextInt(descriptions.length)],
      startTime: startTime,
      endTime: endTime,
      duration: duration,
      distance: workoutType == '跑步' || workoutType == '骑行' 
          ? 1 + random.nextDouble() * 20 
          : 0,
      calories: duration * (2 + random.nextInt(8)),
      steps: workoutType == '跑步' ? duration * (80 + random.nextInt(40)) : 0,
      avgHeartRate: 120 + random.nextDouble() * 60,
      photos: i % 3 == 0 ? ['workout_${i + 1}.jpg'] : [],
      location: locations[random.nextInt(locations.length)],
      stats: {
        'maxSpeed': workoutType == '跑步' ? 8 + random.nextDouble() * 7 : 0,
        'avgSpeed': workoutType == '跑步' ? 6 + random.nextDouble() * 5 : 0,
        'elevation': random.nextInt(100),
      },
      tags: tags.take(random.nextInt(3) + 1).toList(),
      likes: random.nextInt(50),
      comments: random.nextInt(20),
      isLiked: random.nextBool(),
      createdAt: startTime,
    ));
  }

  // 按时间排序
  _workoutRecords.sort((a, b) => b.createdAt.compareTo(a.createdAt));
}

void _generateChallenges() {
  final challengeData = [
    {
      'title': '30天跑步挑战',
      'description': '连续30天每天跑步至少3公里',
      'type': 'distance',
      'target': 90.0,
      'unit': '公里',
      'reward': '专属徽章 + 运动装备优惠券',
    },
    {
      'title': '燃脂大作战',
      'description': '7天内消耗5000卡路里',
      'type': 'calories',
      'target': 5000.0,
      'unit': '卡路里',
      'reward': '健身课程免费体验',
    },
    {
      'title': '万步达人',
      'description': '单日步数达到15000步',
      'type': 'steps',
      'target': 15000.0,
      'unit': '步',
      'reward': '运动手环',
    },
    {
      'title': '坚持之星',
      'description': '连续14天运动打卡',
      'type': 'duration',
      'target': 14.0,
      'unit': '天',
      'reward': '年度会员升级',
    },
  ];

  final random = Random();

  for (int i = 0; i < challengeData.length; i++) {
    final data = challengeData[i];
    final startDate = DateTime.now().subtract(Duration(days: random.nextInt(10)));
    final endDate = startDate.add(Duration(days: 7 + random.nextInt(30)));

    _challenges.add(Challenge(
      id: 'challenge_$i',
      title: data['title'] as String,
      description: data['description'] as String,
      type: data['type'] as String,
      target: data['target'] as double,
      unit: data['unit'] as String,
      startDate: startDate,
      endDate: endDate,
      participants: 100 + random.nextInt(500),
      reward: data['reward'] as String,
      isJoined: random.nextBool(),
      progress: random.nextDouble() * (data['target'] as double),
      coverImage: 'challenge_${i + 1}.jpg',
    ));
  }
}

void _generateComments() {
  final commentTexts = [
    '太棒了!继续加油!',
    '你的坚持让我很佩服',
    '一起运动,一起进步',
    '数据很不错呢',
    '下次一起运动吧',
    '运动使人快乐',
    '健康生活,从运动开始',
    '你是我的榜样',
  ];

  final random = Random();

  for (int i = 0; i < 50; i++) {
    final user = _users[random.nextInt(_users.length)];
    _comments.add(Comment(
      id: 'comment_$i',
      userId: user.id,
      userName: user.name,
      userAvatar: user.avatar,
      content: commentTexts[random.nextInt(commentTexts.length)],
      createdAt: DateTime.now().subtract(Duration(
        days: random.nextInt(7),
        hours: random.nextInt(24),
      )),
      likes: random.nextInt(20),
      isLiked: random.nextBool(),
    ));
  }
}

void _setCurrentUser() {
  _currentUser = _users.first;
}

核心功能实现

首页设计

首页展示用户的运动概览和最新动态:

Widget _buildHomePage() {
  return SingleChildScrollView(
    padding: const EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        _buildWelcomeCard(),
        const SizedBox(height: 16),
        _buildTodayStats(),
        const SizedBox(height: 16),
        _buildQuickActions(),
        const SizedBox(height: 16),
        _buildRecentWorkouts(),
      ],
    ),
  );
}

Widget _buildWelcomeCard() {
  return Card(
    child: Container(
      width: double.infinity,
      padding: const EdgeInsets.all(20),
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(12),
        gradient: LinearGradient(
          colors: [Colors.orange.withValues(alpha: 0.8), Colors.deepOrange.withValues(alpha: 0.6)],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            '你好,${_currentUser?.name ?? "运动达人"}!',
            style: const TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          ),
          const SizedBox(height: 8),
          const Text(
            '今天也要保持运动哦!',
            style: TextStyle(
              fontSize: 16,
              color: Colors.white70,
            ),
          ),
          const SizedBox(height: 16),
          Row(
            children: [
              _buildStatItem('总运动', '${_currentUser?.totalWorkouts ?? 0}', '次'),
              const SizedBox(width: 24),
              _buildStatItem('总距离', '${(_currentUser?.totalDistance ?? 0).toStringAsFixed(1)}', 'km'),
              const SizedBox(width: 24),
              _buildStatItem('总卡路里', '${_currentUser?.totalCalories ?? 0}', 'cal'),
            ],
          ),
        ],
      ),
    ),
  );
}

Widget _buildStatItem(String label, String value, String unit) {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      Text(
        label,
        style: const TextStyle(
          fontSize: 12,
          color: Colors.white70,
        ),
      ),
      const SizedBox(height: 4),
      Row(
        crossAxisAlignment: CrossAxisAlignment.end,
        children: [
          Text(
            value,
            style: const TextStyle(
              fontSize: 20,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          ),
          const SizedBox(width: 2),
          Text(
            unit,
            style: const TextStyle(
              fontSize: 12,
              color: Colors.white70,
            ),
          ),
        ],
      ),
    ],
  );
}

Widget _buildTodayStats() {
  final today = DateTime.now();
  final todayWorkouts = _workoutRecords.where((record) =>
      record.createdAt.year == today.year &&
      record.createdAt.month == today.month &&
      record.createdAt.day == today.day).toList();

  final todayDistance = todayWorkouts.fold<double>(0, (sum, record) => sum + record.distance);
  final todayCalories = todayWorkouts.fold<int>(0, (sum, record) => sum + record.calories);
  final todayDuration = todayWorkouts.fold<int>(0, (sum, record) => sum + record.duration);

  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            '今日数据',
            style: TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 16),
          Row(
            children: [
              Expanded(
                child: _buildTodayStatCard(
                  Icons.directions_run,
                  '距离',
                  '${todayDistance.toStringAsFixed(1)} km',
                  Colors.blue,
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: _buildTodayStatCard(
                  Icons.local_fire_department,
                  '卡路里',
                  '$todayCalories cal',
                  Colors.red,
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: _buildTodayStatCard(
                  Icons.timer,
                  '时长',
                  '${todayDuration} min',
                  Colors.green,
                ),
              ),
            ],
          ),
        ],
      ),
    ),
  );
}

Widget _buildTodayStatCard(IconData icon, String label, String value, Color color) {
  return Container(
    padding: const EdgeInsets.all(12),
    decoration: BoxDecoration(
      color: color.withValues(alpha: 0.1),
      borderRadius: BorderRadius.circular(8),
    ),
    child: Column(
      children: [
        Icon(icon, color: color, size: 24),
        const SizedBox(height: 8),
        Text(
          label,
          style: TextStyle(
            fontSize: 12,
            color: Colors.grey[600],
          ),
        ),
        const SizedBox(height: 4),
        Text(
          value,
          style: TextStyle(
            fontSize: 14,
            fontWeight: FontWeight.bold,
            color: color,
          ),
        ),
      ],
    ),
  );
}

Widget _buildQuickActions() {
  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            '快速开始',
            style: TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 16),
          Row(
            children: [
              Expanded(
                child: _buildQuickActionButton(
                  Icons.directions_run,
                  '跑步',
                  Colors.blue,
                  () => _startWorkout('跑步'),
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: _buildQuickActionButton(
                  Icons.directions_bike,
                  '骑行',
                  Colors.green,
                  () => _startWorkout('骑行'),
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: _buildQuickActionButton(
                  Icons.pool,
                  '游泳',
                  Colors.cyan,
                  () => _startWorkout('游泳'),
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: _buildQuickActionButton(
                  Icons.fitness_center,
                  '健身',
                  Colors.orange,
                  () => _startWorkout('健身'),
                ),
              ),
            ],
          ),
        ],
      ),
    ),
  );
}

Widget _buildQuickActionButton(IconData icon, String label, Color color, VoidCallback onTap) {
  return InkWell(
    onTap: onTap,
    borderRadius: BorderRadius.circular(8),
    child: Container(
      padding: const EdgeInsets.symmetric(vertical: 16),
      decoration: BoxDecoration(
        color: color.withValues(alpha: 0.1),
        borderRadius: BorderRadius.circular(8),
      ),
      child: Column(
        children: [
          Icon(icon, color: color, size: 28),
          const SizedBox(height: 8),
          Text(
            label,
            style: TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.w500,
              color: color,
            ),
          ),
        ],
      ),
    ),
  );
}

Widget _buildRecentWorkouts() {
  final recentWorkouts = _workoutRecords.take(3).toList();

  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              const Text(
                '最近运动',
                style: TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const Spacer(),
              TextButton(
                onPressed: () => setState(() => _selectedIndex = 3),
                child: const Text('查看更多'),
              ),
            ],
          ),
          const SizedBox(height: 16),
          ...recentWorkouts.map((workout) => _buildRecentWorkoutItem(workout)),
        ],
      ),
    ),
  );
}

Widget _buildRecentWorkoutItem(WorkoutRecord workout) {
  return Padding(
    padding: const EdgeInsets.only(bottom: 12),
    child: Row(
      children: [
        Container(
          width: 40,
          height: 40,
          decoration: BoxDecoration(
            color: _getWorkoutColor(workout.workoutType).withValues(alpha: 0.1),
            borderRadius: BorderRadius.circular(8),
          ),
          child: Icon(
            _getWorkoutIcon(workout.workoutType),
            color: _getWorkoutColor(workout.workoutType),
            size: 20,
          ),
        ),
        const SizedBox(width: 12),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                workout.title,
                style: const TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.w500,
                ),
              ),
              const SizedBox(height: 2),
              Text(
                '${workout.duration}分钟 • ${workout.calories}卡路里',
                style: TextStyle(
                  fontSize: 12,
                  color: Colors.grey[600],
                ),
              ),
            ],
          ),
        ),
        Text(
          _formatDate(workout.createdAt),
          style: TextStyle(
            fontSize: 12,
            color: Colors.grey[500],
          ),
        ),
      ],
    ),
  );
}

发现页面

发现页面展示热门挑战和推荐用户:

Widget _buildDiscoverPage() {
  return SingleChildScrollView(
    padding: const EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        _buildFeaturedChallenge(),
        const SizedBox(height: 16),
        _buildChallengesList(),
        const SizedBox(height: 16),
        _buildRecommendedUsers(),
      ],
    ),
  );
}

Widget _buildFeaturedChallenge() {
  final featuredChallenge = _challenges.isNotEmpty ? _challenges.first : null;
  if (featuredChallenge == null) return const SizedBox();

  return Card(
    child: Container(
      width: double.infinity,
      height: 200,
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(12),
        gradient: LinearGradient(
          colors: [Colors.purple.withValues(alpha: 0.8), Colors.pink.withValues(alpha: 0.6)],
          begin: Alignment.topLeft,
          end: Alignment.bottomRight,
        ),
      ),
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              '精选挑战',
              style: TextStyle(
                fontSize: 14,
                color: Colors.white70,
                fontWeight: FontWeight.w500,
              ),
            ),
            const SizedBox(height: 8),
            Text(
              featuredChallenge.title,
              style: const TextStyle(
                fontSize: 24,
                fontWeight: FontWeight.bold,
                color: Colors.white,
              ),
            ),
            const SizedBox(height: 8),
            Text(
              featuredChallenge.description,
              style: const TextStyle(
                fontSize: 14,
                color: Colors.white70,
              ),
              maxLines: 2,
              overflow: TextOverflow.ellipsis,
            ),
            const Spacer(),
            Row(
              children: [
                Icon(Icons.people, color: Colors.white70, size: 16),
                const SizedBox(width: 4),
                Text(
                  '${featuredChallenge.participants}人参与',
                  style: const TextStyle(
                    fontSize: 12,
                    color: Colors.white70,
                  ),
                ),
                const Spacer(),
                ElevatedButton(
                  onPressed: () => _joinChallenge(featuredChallenge),
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.white,
                    foregroundColor: Colors.purple,
                    padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
                  ),
                  child: Text(featuredChallenge.isJoined ? '已参加' : '立即参加'),
                ),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}

Widget _buildChallengesList() {
  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      const Text(
        '热门挑战',
        style: TextStyle(
          fontSize: 18,
          fontWeight: FontWeight.bold,
        ),
      ),
      const SizedBox(height: 12),
      ...(_challenges.skip(1).take(3).map((challenge) => _buildChallengeCard(challenge))),
    ],
  );
}

Widget _buildChallengeCard(Challenge challenge) {
  return Card(
    margin: const EdgeInsets.only(bottom: 12),
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Container(
                width: 50,
                height: 50,
                decoration: BoxDecoration(
                  color: _getChallengeColor(challenge.type).withValues(alpha: 0.1),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Icon(
                  _getChallengeIcon(challenge.type),
                  color: _getChallengeColor(challenge.type),
                  size: 24,
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      challenge.title,
                      style: const TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      challenge.description,
                      style: TextStyle(
                        fontSize: 12,
                        color: Colors.grey[600],
                      ),
                      maxLines: 2,
                      overflow: TextOverflow.ellipsis,
                    ),
                  ],
                ),
              ),
            ],
          ),
          const SizedBox(height: 16),
          Row(
            children: [
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      '目标:${challenge.target.toInt()} ${challenge.unit}',
                      style: const TextStyle(
                        fontSize: 12,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                    const SizedBox(height: 4),
                    LinearProgressIndicator(
                      value: challenge.progressPercentage / 100,
                      backgroundColor: Colors.grey[300],
                      valueColor: AlwaysStoppedAnimation(_getChallengeColor(challenge.type)),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      '进度:${challenge.progressPercentage.toStringAsFixed(1)}%',
                      style: TextStyle(
                        fontSize: 10,
                        color: Colors.grey[600],
                      ),
                    ),
                  ],
                ),
              ),
              const SizedBox(width: 16),
              Column(
                crossAxisAlignment: CrossAxisAlignment.end,
                children: [
                  Text(
                    '${challenge.participants}人',
                    style: const TextStyle(
                      fontSize: 12,
                      fontWeight: FontWeight.w500,
                    ),
                  ),
                  const SizedBox(height: 4),
                  Text(
                    '剩余${challenge.daysLeft}天',
                    style: TextStyle(
                      fontSize: 10,
                      color: Colors.grey[600],
                    ),
                  ),
                ],
              ),
            ],
          ),
          const SizedBox(height: 12),
          SizedBox(
            width: double.infinity,
            child: OutlinedButton(
              onPressed: () => _joinChallenge(challenge),
              child: Text(challenge.isJoined ? '已参加' : '立即参加'),
            ),
          ),
        ],
      ),
    ),
  );
}

Widget _buildRecommendedUsers() {
  final recommendedUsers = _users.where((user) => !user.isFollowing).take(4).toList();

  return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: [
      const Text(
        '推荐关注',
        style: TextStyle(
          fontSize: 18,
          fontWeight: FontWeight.bold,
        ),
      ),
      const SizedBox(height: 12),
      ...recommendedUsers.map((user) => _buildUserCard(user)),
    ],
  );
}

Widget _buildUserCard(FitnessUser user) {
  return Card(
    margin: const EdgeInsets.only(bottom: 12),
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Row(
        children: [
          CircleAvatar(
            radius: 25,
            backgroundColor: Colors.orange.withValues(alpha: 0.1),
            child: Text(
              user.name.substring(0, 1),
              style: const TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
                color: Colors.orange,
              ),
            ),
          ),
          const SizedBox(width: 12),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  user.name,
                  style: const TextStyle(
                    fontSize: 16,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 4),
                Text(
                  user.bio,
                  style: TextStyle(
                    fontSize: 12,
                    color: Colors.grey[600],
                  ),
                  maxLines: 1,
                  overflow: TextOverflow.ellipsis,
                ),
                const SizedBox(height: 8),
                Row(
                  children: [
                    Text(
                      '${user.totalWorkouts}次运动',
                      style: const TextStyle(fontSize: 10),
                    ),
                    const SizedBox(width: 12),
                    Text(
                      '${user.followers}粉丝',
                      style: const TextStyle(fontSize: 10),
                    ),
                  ],
                ),
              ],
            ),
          ),
          OutlinedButton(
            onPressed: () => _followUser(user),
            style: OutlinedButton.styleFrom(
              padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
            ),
            child: Text(user.isFollowing ? '已关注' : '关注'),
          ),
        ],
      ),
    ),
  );
}

运动记录页面

运动记录页面提供运动数据录入功能:

Widget _buildRecordPage() {
  return SingleChildScrollView(
    padding: const EdgeInsets.all(16),
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        const Text(
          '开始运动',
          style: TextStyle(
            fontSize: 24,
            fontWeight: FontWeight.bold,
          ),
        ),
        const SizedBox(height: 16),
        _buildWorkoutTypeGrid(),
        const SizedBox(height: 24),
        _buildManualRecord(),
      ],
    ),
  );
}

Widget _buildWorkoutTypeGrid() {
  final workoutTypes = [
    {'name': '跑步', 'icon': Icons.directions_run, 'color': Colors.blue},
    {'name': '骑行', 'icon': Icons.directions_bike, 'color': Colors.green},
    {'name': '游泳', 'icon': Icons.pool, 'color': Colors.cyan},
    {'name': '健身', 'icon': Icons.fitness_center, 'color': Colors.orange},
    {'name': '瑜伽', 'icon': Icons.self_improvement, 'color': Colors.purple},
    {'name': '篮球', 'icon': Icons.sports_basketball, 'color': Colors.brown},
    {'name': '足球', 'icon': Icons.sports_soccer, 'color': Colors.red},
    {'name': '网球', 'icon': Icons.sports_tennis, 'color': Colors.teal},
  ];

  return GridView.builder(
    shrinkWrap: true,
    physics: const NeverScrollableScrollPhysics(),
    gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisCount: 2,
      crossAxisSpacing: 12,
      mainAxisSpacing: 12,
      childAspectRatio: 1.5,
    ),
    itemCount: workoutTypes.length,
    itemBuilder: (context, index) {
      final workout = workoutTypes[index];
      return _buildWorkoutTypeCard(
        workout['name'] as String,
        workout['icon'] as IconData,
        workout['color'] as Color,
      );
    },
  );
}

Widget _buildWorkoutTypeCard(String name, IconData icon, Color color) {
  return Card(
    child: InkWell(
      onTap: () => _startWorkout(name),
      borderRadius: BorderRadius.circular(12),
      child: Container(
        padding: const EdgeInsets.all(16),
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(12),
          gradient: LinearGradient(
            colors: [color.withValues(alpha: 0.1), color.withValues(alpha: 0.05)],
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
          ),
        ),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(icon, color: color, size: 32),
            const SizedBox(height: 8),
            Text(
              name,
              style: TextStyle(
                fontSize: 16,
                fontWeight: FontWeight.w500,
                color: color,
              ),
            ),
          ],
        ),
      ),
    ),
  );
}

Widget _buildManualRecord() {
  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            '手动记录',
            style: TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 16),
          const Text(
            '如果你已经完成了运动,可以手动添加记录',
            style: TextStyle(
              fontSize: 14,
              color: Colors.grey,
            ),
          ),
          const SizedBox(height: 16),
          SizedBox(
            width: double.infinity,
            child: ElevatedButton.icon(
              onPressed: () => _showManualRecordDialog(),
              icon: const Icon(Icons.add),
              label: const Text('添加运动记录'),
              style: ElevatedButton.styleFrom(
                padding: const EdgeInsets.symmetric(vertical: 12),
              ),
            ),
          ),
        ],
      ),
    ),
  );
}

社交页面

社交页面展示好友动态和运动分享:

Widget _buildSocialPage() {
  return Column(
    children: [
      _buildSocialTabs(),
      Expanded(
        child: TabBarView(
          children: [
            _buildFeedList(),
            _buildFollowingList(),
          ],
        ),
      ),
    ],
  );
}

Widget _buildSocialTabs() {
  return Container(
    color: Colors.white,
    child: const TabBar(
      tabs: [
        Tab(text: '动态'),
        Tab(text: '关注'),
      ],
    ),
  );
}

Widget _buildFeedList() {
  return ListView.builder(
    padding: const EdgeInsets.all(16),
    itemCount: _workoutRecords.length,
    itemBuilder: (context, index) {
      return _buildWorkoutCard(_workoutRecords[index]);
    },
  );
}

Widget _buildWorkoutCard(WorkoutRecord workout) {
  return Card(
    margin: const EdgeInsets.only(bottom: 16),
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 用户信息
          Row(
            children: [
              CircleAvatar(
                radius: 20,
                backgroundColor: Colors.orange.withValues(alpha: 0.1),
                child: Text(
                  workout.userName.substring(0, 1),
                  style: const TextStyle(
                    fontSize: 14,
                    fontWeight: FontWeight.bold,
                    color: Colors.orange,
                  ),
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      workout.userName,
                      style: const TextStyle(
                        fontSize: 14,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 2),
                    Text(
                      _formatDateTime(workout.createdAt),
                      style: TextStyle(
                        fontSize: 12,
                        color: Colors.grey[600],
                      ),
                    ),
                  ],
                ),
              ),
              IconButton(
                onPressed: () => _showWorkoutMenu(workout),
                icon: const Icon(Icons.more_horiz),
              ),
            ],
          ),
          const SizedBox(height: 12),
          
          // 运动内容
          Row(
            children: [
              Container(
                width: 40,
                height: 40,
                decoration: BoxDecoration(
                  color: _getWorkoutColor(workout.workoutType).withValues(alpha: 0.1),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Icon(
                  _getWorkoutIcon(workout.workoutType),
                  color: _getWorkoutColor(workout.workoutType),
                  size: 20,
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      workout.title,
                      style: const TextStyle(
                        fontSize: 16,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      workout.description,
                      style: TextStyle(
                        fontSize: 14,
                        color: Colors.grey[600],
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
          
          const SizedBox(height: 16),
          
          // 运动数据
          Container(
            padding: const EdgeInsets.all(12),
            decoration: BoxDecoration(
              color: Colors.grey[50],
              borderRadius: BorderRadius.circular(8),
            ),
            child: Row(
              children: [
                if (workout.distance > 0) ...[
                  _buildWorkoutStat(Icons.straighten, '${workout.distance.toStringAsFixed(1)} km'),
                  const SizedBox(width: 16),
                ],
                _buildWorkoutStat(Icons.timer, '${workout.duration} min'),
                const SizedBox(width: 16),
                _buildWorkoutStat(Icons.local_fire_department, '${workout.calories} cal'),
                if (workout.steps > 0) ...[
                  const SizedBox(width: 16),
                  _buildWorkoutStat(Icons.directions_walk, '${workout.steps} 步'),
                ],
              ],
            ),
          ),
          
          // 运动照片
          if (workout.photos.isNotEmpty) ...[
            const SizedBox(height: 12),
            Container(
              height: 120,
              width: double.infinity,
              decoration: BoxDecoration(
                color: Colors.grey[200],
                borderRadius: BorderRadius.circular(8),
              ),
              child: const Center(
                child: Icon(Icons.image, size: 40, color: Colors.grey),
              ),
            ),
          ],
          
          // 标签
          if (workout.tags.isNotEmpty) ...[
            const SizedBox(height: 12),
            Wrap(
              spacing: 8,
              children: workout.tags.map((tag) {
                return Container(
                  padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
                  decoration: BoxDecoration(
                    color: Colors.orange.withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(12),
                  ),
                  child: Text(
                    '#$tag',
                    style: const TextStyle(
                      fontSize: 12,
                      color: Colors.orange,
                    ),
                  ),
                );
              }).toList(),
            ),
          ],
          
          const SizedBox(height: 16),
          
          // 互动按钮
          Row(
            children: [
              InkWell(
                onTap: () => _toggleLike(workout),
                child: Row(
                  children: [
                    Icon(
                      workout.isLiked ? Icons.favorite : Icons.favorite_border,
                      color: workout.isLiked ? Colors.red : Colors.grey,
                      size: 20,
                    ),
                    const SizedBox(width: 4),
                    Text(
                      '${workout.likes}',
                      style: TextStyle(
                        fontSize: 12,
                        color: Colors.grey[600],
                      ),
                    ),
                  ],
                ),
              ),
              const SizedBox(width: 24),
              InkWell(
                onTap: () => _showComments(workout),
                child: Row(
                  children: [
                    Icon(Icons.comment_outlined, color: Colors.grey, size: 20),
                    const SizedBox(width: 4),
                    Text(
                      '${workout.comments}',
                      style: TextStyle(
                        fontSize: 12,
                        color: Colors.grey[600],
                      ),
                    ),
                  ],
                ),
              ),
              const SizedBox(width: 24),
              InkWell(
                onTap: () => _shareWorkout(workout),
                child: Row(
                  children: [
                    Icon(Icons.share_outlined, color: Colors.grey, size: 20),
                    const SizedBox(width: 4),
                    Text(
                      '分享',
                      style: TextStyle(
                        fontSize: 12,
                        color: Colors.grey[600],
                      ),
                    ),
                  ],
                ),
              ),
            ],
          ),
        ],
      ),
    ),
  );
}

Widget _buildWorkoutStat(IconData icon, String text) {
  return Row(
    children: [
      Icon(icon, size: 16, color: Colors.grey[600]),
      const SizedBox(width: 4),
      Text(
        text,
        style: TextStyle(
          fontSize: 12,
          color: Colors.grey[600],
        ),
      ),
    ],
  );
}

Widget _buildFollowingList() {
  final followingUsers = _users.where((user) => user.isFollowing).toList();

  return ListView.builder(
    padding: const EdgeInsets.all(16),
    itemCount: followingUsers.length,
    itemBuilder: (context, index) {
      return _buildUserCard(followingUsers[index]);
    },
  );
}

个人页面

个人页面展示用户信息和运动统计:

Widget _buildProfilePage() {
  if (_currentUser == null) return const Center(child: CircularProgressIndicator());

  return SingleChildScrollView(
    padding: const EdgeInsets.all(16),
    child: Column(
      children: [
        _buildProfileHeader(),
        const SizedBox(height: 16),
        _buildStatsGrid(),
        const SizedBox(height: 16),
        _buildAchievements(),
        const SizedBox(height: 16),
        _buildMyWorkouts(),
      ],
    ),
  );
}

Widget _buildProfileHeader() {
  return Card(
    child: Padding(
      padding: const EdgeInsets.all(20),
      child: Column(
        children: [
          CircleAvatar(
            radius: 40,
            backgroundColor: Colors.orange.withValues(alpha: 0.1),
            child: Text(
              _currentUser!.name.substring(0, 1),
              style: const TextStyle(
                fontSize: 32,
                fontWeight: FontWeight.bold,
                color: Colors.orange,
              ),
            ),
          ),
          const SizedBox(height: 16),
          Text(
            _currentUser!.name,
            style: const TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 8),
          Text(
            _currentUser!.bio,
            style: TextStyle(
              fontSize: 14,
              color: Colors.grey[600],
            ),
            textAlign: TextAlign.center,
          ),
          const SizedBox(height: 16),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              _buildProfileStat('关注', '${_currentUser!.following}'),
              _buildProfileStat('粉丝', '${_currentUser!.followers}'),
              _buildProfileStat('运动', '${_currentUser!.totalWorkouts}'),
            ],
          ),
          const SizedBox(height: 16),
          Row(
            children: [
              Expanded(
                child: OutlinedButton(
                  onPressed: () => _editProfile(),
                  child: const Text('编辑资料'),
                ),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: ElevatedButton(
                  onPressed: () => _shareProfile(),
                  child: const Text('分享'),
                ),
              ),
            ],
          ),
        ],
      ),
    ),
  );
}

Widget _buildProfileStat(String label, String value) {
  return Column(
    children: [
      Text(
        value,
        style: const TextStyle(
          fontSize: 20,
          fontWeight: FontWeight.bold,
        ),
      ),
      const SizedBox(height: 4),
      Text(
        label,
        style: TextStyle(
          fontSize: 12,
          color: Colors.grey[600],
        ),
      ),
    ],
  );
}

Widget _buildStatsGrid() {
  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            '运动统计',
            style: TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 16),
          GridView.count(
            shrinkWrap: true,
            physics: const NeverScrollableScrollPhysics(),
            crossAxisCount: 2,
            crossAxisSpacing: 12,
            mainAxisSpacing: 12,
            childAspectRatio: 1.5,
            children: [
              _buildStatCard(
                Icons.straighten,
                '总距离',
                '${_currentUser!.totalDistance.toStringAsFixed(1)} km',
                Colors.blue,
              ),
              _buildStatCard(
                Icons.local_fire_department,
                '总卡路里',
                '${_currentUser!.totalCalories} cal',
                Colors.red,
              ),
              _buildStatCard(
                Icons.timer,
                '总时长',
                '${(_currentUser!.totalDuration / 60).toStringAsFixed(1)} h',
                Colors.green,
              ),
              _buildStatCard(
                Icons.monitor_weight,
                'BMI',
                _currentUser!.bmi.toStringAsFixed(1),
                Colors.purple,
              ),
            ],
          ),
        ],
      ),
    ),
  );
}

Widget _buildStatCard(IconData icon, String label, String value, Color color) {
  return Container(
    padding: const EdgeInsets.all(16),
    decoration: BoxDecoration(
      color: color.withValues(alpha: 0.1),
      borderRadius: BorderRadius.circular(12),
    ),
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: color, size: 24),
        const SizedBox(height: 8),
        Text(
          label,
          style: TextStyle(
            fontSize: 12,
            color: Colors.grey[600],
          ),
        ),
        const SizedBox(height: 4),
        Text(
          value,
          style: TextStyle(
            fontSize: 16,
            fontWeight: FontWeight.bold,
            color: color,
          ),
        ),
      ],
    ),
  );
}

Widget _buildAchievements() {
  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          const Text(
            '成就徽章',
            style: TextStyle(
              fontSize: 18,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 16),
          Wrap(
            spacing: 12,
            runSpacing: 12,
            children: _currentUser!.achievements.map((achievement) {
              return Container(
                padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
                decoration: BoxDecoration(
                  color: Colors.orange.withValues(alpha: 0.1),
                  borderRadius: BorderRadius.circular(20),
                  border: Border.all(color: Colors.orange.withValues(alpha: 0.3)),
                ),
                child: Row(
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Icon(Icons.emoji_events, color: Colors.orange, size: 16),
                    const SizedBox(width: 4),
                    Text(
                      achievement,
                      style: const TextStyle(
                        fontSize: 12,
                        color: Colors.orange,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ],
                ),
              );
            }).toList(),
          ),
        ],
      ),
    ),
  );
}

Widget _buildMyWorkouts() {
  final myWorkouts = _workoutRecords
      .where((workout) => workout.userId == _currentUser!.id)
      .take(5)
      .toList();

  return Card(
    child: Padding(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              const Text(
                '我的运动',
                style: TextStyle(
                  fontSize: 18,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const Spacer(),
              TextButton(
                onPressed: () => _showAllMyWorkouts(),
                child: const Text('查看全部'),
              ),
            ],
          ),
          const SizedBox(height: 16),
          ...myWorkouts.map((workout) => _buildMyWorkoutItem(workout)),
        ],
      ),
    ),
  );
}

Widget _buildMyWorkoutItem(WorkoutRecord workout) {
  return Padding(
    padding: const EdgeInsets.only(bottom: 12),
    child: Row(
      children: [
        Container(
          width: 40,
          height: 40,
          decoration: BoxDecoration(
            color: _getWorkoutColor(workout.workoutType).withValues(alpha: 0.1),
            borderRadius: BorderRadius.circular(8),
          ),
          child: Icon(
            _getWorkoutIcon(workout.workoutType),
            color: _getWorkoutColor(workout.workoutType),
            size: 20,
          ),
        ),
        const SizedBox(width: 12),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                workout.title,
                style: const TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.w500,
                ),
              ),
              const SizedBox(height: 2),
              Text(
                '${workout.duration}分钟 • ${workout.calories}卡路里',
                style: TextStyle(
                  fontSize: 12,
                  color: Colors.grey[600],
                ),
              ),
            ],
          ),
        ),
        Column(
          crossAxisAlignment: CrossAxisAlignment.end,
          children: [
            Text(
              _formatDate(workout.createdAt),
              style: TextStyle(
                fontSize: 12,
                color: Colors.grey[500],
              ),
            ),
            const SizedBox(height: 2),
            Row(
              children: [
                Icon(Icons.favorite, color: Colors.red, size: 12),
                const SizedBox(width: 2),
                Text(
                  '${workout.likes}',
                  style: TextStyle(
                    fontSize: 10,
                    color: Colors.grey[500],
                  ),
                ),
              ],
            ),
          ],
        ),
      ],
    ),
  );
}

辅助方法和工具函数

工具方法实现

// 获取运动类型对应的图标
IconData _getWorkoutIcon(String workoutType) {
  switch (workoutType) {
    case '跑步':
      return Icons.directions_run;
    case '骑行':
      return Icons.directions_bike;
    case '游泳':
      return Icons.pool;
    case '健身':
      return Icons.fitness_center;
    case '瑜伽':
      return Icons.self_improvement;
    case '篮球':
      return Icons.sports_basketball;
    case '足球':
      return Icons.sports_soccer;
    case '网球':
      return Icons.sports_tennis;
    default:
      return Icons.sports;
  }
}

// 获取运动类型对应的颜色
Color _getWorkoutColor(String workoutType) {
  switch (workoutType) {
    case '跑步':
      return Colors.blue;
    case '骑行':
      return Colors.green;
    case '游泳':
      return Colors.cyan;
    case '健身':
      return Colors.orange;
    case '瑜伽':
      return Colors.purple;
    case '篮球':
      return Colors.brown;
    case '足球':
      return Colors.red;
    case '网球':
      return Colors.teal;
    default:
      return Colors.grey;
  }
}

// 获取挑战类型对应的图标
IconData _getChallengeIcon(String challengeType) {
  switch (challengeType) {
    case 'distance':
      return Icons.straighten;
    case 'calories':
      return Icons.local_fire_department;
    case 'steps':
      return Icons.directions_walk;
    case 'duration':
      return Icons.timer;
    default:
      return Icons.flag;
  }
}

// 获取挑战类型对应的颜色
Color _getChallengeColor(String challengeType) {
  switch (challengeType) {
    case 'distance':
      return Colors.blue;
    case 'calories':
      return Colors.red;
    case 'steps':
      return Colors.green;
    case 'duration':
      return Colors.orange;
    default:
      return Colors.grey;
  }
}

// 格式化日期
String _formatDate(DateTime date) {
  final now = DateTime.now();
  final difference = now.difference(date);

  if (difference.inDays == 0) {
    return '今天';
  } else if (difference.inDays == 1) {
    return '昨天';
  } else if (difference.inDays < 7) {
    return '${difference.inDays}天前';
  } else {
    return '${date.month}${date.day}日';
  }
}

// 格式化日期时间
String _formatDateTime(DateTime dateTime) {
  final now = DateTime.now();
  final difference = now.difference(dateTime);

  if (difference.inMinutes < 60) {
    return '${difference.inMinutes}分钟前';
  } else if (difference.inHours < 24) {
    return '${difference.inHours}小时前';
  } else if (difference.inDays < 7) {
    return '${difference.inDays}天前';
  } else {
    return '${dateTime.month}${dateTime.day}日';
  }
}

交互功能实现

// 开始运动
void _startWorkout(String workoutType) {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: Text('开始$workoutType'),
      content: Text('准备开始$workoutType运动吗?'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('取消'),
        ),
        ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
            _showWorkoutTimer(workoutType);
          },
          child: const Text('开始'),
        ),
      ],
    ),
  );
}

// 显示运动计时器
void _showWorkoutTimer(String workoutType) {
  showDialog(
    context: context,
    barrierDismissible: false,
    builder: (context) => AlertDialog(
      title: Text('$workoutType进行中'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(
            _getWorkoutIcon(workoutType),
            size: 64,
            color: _getWorkoutColor(workoutType),
          ),
          const SizedBox(height: 16),
          const Text(
            '00:00:00',
            style: TextStyle(
              fontSize: 32,
              fontWeight: FontWeight.bold,
            ),
          ),
          const SizedBox(height: 16),
          const Text('运动进行中,保持节奏!'),
        ],
      ),
      actions: [
        TextButton(
          onPressed: () {
            Navigator.pop(context);
            _finishWorkout(workoutType);
          },
          child: const Text('结束运动'),
        ),
      ],
    ),
  );
}

// 完成运动
void _finishWorkout(String workoutType) {
  final random = Random();
  final duration = 30 + random.nextInt(60);
  final calories = duration * (3 + random.nextInt(5));
  
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('运动完成!'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Icon(
            Icons.check_circle,
            size: 64,
            color: Colors.green,
          ),
          const SizedBox(height: 16),
          Text('恭喜完成$workoutType运动!'),
          const SizedBox(height: 16),
          Container(
            padding: const EdgeInsets.all(12),
            decoration: BoxDecoration(
              color: Colors.grey[100],
              borderRadius: BorderRadius.circular(8),
            ),
            child: Column(
              children: [
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    const Text('运动时长'),
                    Text('${duration}分钟'),
                  ],
                ),
                const SizedBox(height: 8),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: [
                    const Text('消耗卡路里'),
                    Text('${calories}卡路里'),
                  ],
                ),
              ],
            ),
          ),
        ],
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('稍后分享'),
        ),
        ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
            _shareWorkoutResult(workoutType, duration, calories);
          },
          child: const Text('分享动态'),
        ),
      ],
    ),
  );
}

// 分享运动结果
void _shareWorkoutResult(String workoutType, int duration, int calories) {
  // 创建新的运动记录
  final newWorkout = WorkoutRecord(
    id: 'workout_${_workoutRecords.length}',
    userId: _currentUser!.id,
    userName: _currentUser!.name,
    userAvatar: _currentUser!.avatar,
    workoutType: workoutType,
    title: '刚刚完成$workoutType运动',
    description: '感觉很棒!继续加油💪',
    startTime: DateTime.now().subtract(Duration(minutes: duration)),
    endTime: DateTime.now(),
    duration: duration,
    distance: workoutType == '跑步' ? duration * 0.1 : 0,
    calories: calories,
    steps: workoutType == '跑步' ? duration * 100 : 0,
    avgHeartRate: 120 + Random().nextDouble() * 40,
    photos: [],
    location: '健身房',
    stats: {},
    tags: ['坚持', '健康'],
    likes: 0,
    comments: 0,
    isLiked: false,
    createdAt: DateTime.now(),
  );

  setState(() {
    _workoutRecords.insert(0, newWorkout);
  });

  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('运动记录已分享到动态!')),
  );
}

// 参加挑战
void _joinChallenge(Challenge challenge) {
  setState(() {
    final index = _challenges.indexWhere((c) => c.id == challenge.id);
    if (index != -1) {
      // 这里应该创建新的Challenge对象,但为了简化示例,直接修改
      // 在实际应用中,应该使用不可变的数据结构
    }
  });

  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('已参加${challenge.title}!')),
  );
}

// 关注用户
void _followUser(FitnessUser user) {
  setState(() {
    final index = _users.indexWhere((u) => u.id == user.id);
    if (index != -1) {
      // 同样,这里应该创建新对象
    }
  });

  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('已关注${user.name}!')),
  );
}

// 点赞运动记录
void _toggleLike(WorkoutRecord workout) {
  setState(() {
    final index = _workoutRecords.indexWhere((w) => w.id == workout.id);
    if (index != -1) {
      // 创建新的WorkoutRecord对象
    }
  });
}

// 显示评论
void _showComments(WorkoutRecord workout) {
  showModalBottomSheet(
    context: context,
    isScrollControlled: true,
    builder: (context) => DraggableScrollableSheet(
      initialChildSize: 0.7,
      maxChildSize: 0.9,
      minChildSize: 0.5,
      builder: (context, scrollController) => Container(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            const Text(
              '评论',
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 16),
            Expanded(
              child: ListView.builder(
                controller: scrollController,
                itemCount: _comments.length,
                itemBuilder: (context, index) {
                  final comment = _comments[index];
                  return _buildCommentItem(comment);
                },
              ),
            ),
            _buildCommentInput(),
          ],
        ),
      ),
    ),
  );
}

Widget _buildCommentItem(Comment comment) {
  return Padding(
    padding: const EdgeInsets.only(bottom: 16),
    child: Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        CircleAvatar(
          radius: 16,
          backgroundColor: Colors.orange.withValues(alpha: 0.1),
          child: Text(
            comment.userName.substring(0, 1),
            style: const TextStyle(
              fontSize: 12,
              fontWeight: FontWeight.bold,
              color: Colors.orange,
            ),
          ),
        ),
        const SizedBox(width: 12),
        Expanded(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                children: [
                  Text(
                    comment.userName,
                    style: const TextStyle(
                      fontSize: 14,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                  const SizedBox(width: 8),
                  Text(
                    _formatDateTime(comment.createdAt),
                    style: TextStyle(
                      fontSize: 12,
                      color: Colors.grey[600],
                    ),
                  ),
                ],
              ),
              const SizedBox(height: 4),
              Text(
                comment.content,
                style: const TextStyle(fontSize: 14),
              ),
              const SizedBox(height: 8),
              Row(
                children: [
                  InkWell(
                    onTap: () => _toggleCommentLike(comment),
                    child: Row(
                      children: [
                        Icon(
                          comment.isLiked ? Icons.favorite : Icons.favorite_border,
                          color: comment.isLiked ? Colors.red : Colors.grey,
                          size: 16,
                        ),
                        const SizedBox(width: 4),
                        Text(
                          '${comment.likes}',
                          style: TextStyle(
                            fontSize: 12,
                            color: Colors.grey[600],
                          ),
                        ),
                      ],
                    ),
                  ),
                  const SizedBox(width: 16),
                  InkWell(
                    onTap: () => _replyComment(comment),
                    child: Text(
                      '回复',
                      style: TextStyle(
                        fontSize: 12,
                        color: Colors.grey[600],
                      ),
                    ),
                  ),
                ],
              ),
            ],
          ),
        ),
      ],
    ),
  );
}

Widget _buildCommentInput() {
  return Container(
    padding: const EdgeInsets.all(12),
    decoration: BoxDecoration(
      color: Colors.grey[100],
      borderRadius: BorderRadius.circular(24),
    ),
    child: Row(
      children: [
        const Expanded(
          child: TextField(
            decoration: InputDecoration(
              hintText: '写评论...',
              border: InputBorder.none,
              contentPadding: EdgeInsets.symmetric(horizontal: 16),
            ),
          ),
        ),
        IconButton(
          onPressed: () => _sendComment(),
          icon: const Icon(Icons.send, color: Colors.orange),
        ),
      ],
    ),
  );
}

// 其他交互方法
void _showSearchDialog() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('搜索'),
      content: const TextField(
        decoration: InputDecoration(
          hintText: '搜索用户、运动记录...',
          prefixIcon: Icon(Icons.search),
        ),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('取消'),
        ),
        ElevatedButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('搜索'),
        ),
      ],
    ),
  );
}

void _showNotificationDialog() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('通知'),
      content: const Text('暂无新通知'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('确定'),
        ),
      ],
    ),
  );
}

void _showManualRecordDialog() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('手动添加记录'),
      content: const Text('手动记录功能开发中...'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('取消'),
        ),
        ElevatedButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('保存'),
        ),
      ],
    ),
  );
}

void _editProfile() {
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('编辑资料功能开发中...')),
  );
}

void _shareProfile() {
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('分享资料功能开发中...')),
  );
}

void _showAllMyWorkouts() {
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('查看全部运动记录功能开发中...')),
  );
}

void _shareWorkout(WorkoutRecord workout) {
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text('分享运动记录功能开发中...')),
  );
}

void _showWorkoutMenu(WorkoutRecord workout) {
  showModalBottomSheet(
    context: context,
    builder: (context) => Column(
      mainAxisSize: MainAxisSize.min,
      children: [
        ListTile(
          leading: const Icon(Icons.edit),
          title: const Text('编辑'),
          onTap: () => Navigator.pop(context),
        ),
        ListTile(
          leading: const Icon(Icons.delete),
          title: const Text('删除'),
          onTap: () => Navigator.pop(context),
        ),
        ListTile(
          leading: const Icon(Icons.report),
          title: const Text('举报'),
          onTap: () => Navigator.pop(context),
        ),
      ],
    ),
  );
}

void _toggleCommentLike(Comment comment) {
  // 切换评论点赞状态
}

void _replyComment(Comment comment) {
  // 回复评论
}

void _sendComment() {
  // 发送评论
}

高级功能扩展

数据可视化

为了更好地展示用户的运动数据,我们可以添加图表功能:

// 添加依赖
dependencies:
  fl_chart: ^0.65.0

// 运动数据图表组件
class WorkoutChart extends StatelessWidget {
  final List<WorkoutRecord> workouts;

  const WorkoutChart({Key? key, required this.workouts}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              '运动趋势',
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 16),
            SizedBox(
              height: 200,
              child: LineChart(
                LineChartData(
                  gridData: FlGridData(show: false),
                  titlesData: FlTitlesData(show: false),
                  borderData: FlBorderData(show: false),
                  lineBarsData: [
                    LineChartBarData(
                      spots: _getChartData(),
                      isCurved: true,
                      color: Colors.orange,
                      barWidth: 3,
                      dotData: FlDotData(show: false),
                      belowBarData: BarAreaData(
                        show: true,
                        color: Colors.orange.withValues(alpha: 0.1),
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  List<FlSpot> _getChartData() {
    final last7Days = <FlSpot>[];
    final now = DateTime.now();
    
    for (int i = 6; i >= 0; i--) {
      final date = now.subtract(Duration(days: i));
      final dayWorkouts = workouts.where((w) =>
          w.createdAt.year == date.year &&
          w.createdAt.month == date.month &&
          w.createdAt.day == date.day).toList();
      
      final totalCalories = dayWorkouts.fold<double>(
          0, (sum, w) => sum + w.calories);
      
      last7Days.add(FlSpot((6 - i).toDouble(), totalCalories));
    }
    
    return last7Days;
  }
}

// 运动类型分布饼图
class WorkoutTypePieChart extends StatelessWidget {
  final List<WorkoutRecord> workouts;

  const WorkoutTypePieChart({Key? key, required this.workouts}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              '运动类型分布',
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 16),
            SizedBox(
              height: 200,
              child: PieChart(
                PieChartData(
                  sections: _getPieChartSections(),
                  centerSpaceRadius: 40,
                  sectionsSpace: 2,
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  List<PieChartSectionData> _getPieChartSections() {
    final typeCount = <String, int>{};
    
    for (final workout in workouts) {
      typeCount[workout.workoutType] = 
          (typeCount[workout.workoutType] ?? 0) + 1;
    }

    final colors = [
      Colors.blue,
      Colors.green,
      Colors.orange,
      Colors.purple,
      Colors.red,
      Colors.cyan,
    ];

    return typeCount.entries.map((entry) {
      final index = typeCount.keys.toList().indexOf(entry.key);
      final percentage = (entry.value / workouts.length * 100);
      
      return PieChartSectionData(
        value: entry.value.toDouble(),
        title: '${percentage.toStringAsFixed(1)}%',
        color: colors[index % colors.length],
        radius: 60,
        titleStyle: const TextStyle(
          fontSize: 12,
          fontWeight: FontWeight.bold,
          color: Colors.white,
        ),
      );
    }).toList();
  }
}

地理位置功能

集成地图功能,记录运动轨迹:

// 添加依赖
dependencies:
  geolocator: ^9.0.2
  google_maps_flutter: ^2.5.0

// 位置服务类
class LocationService {
  static Future<Position?> getCurrentLocation() async {
    bool serviceEnabled;
    LocationPermission permission;

    serviceEnabled = await Geolocator.isLocationServiceEnabled();
    if (!serviceEnabled) {
      return null;
    }

    permission = await Geolocator.checkPermission();
    if (permission == LocationPermission.denied) {
      permission = await Geolocator.requestPermission();
      if (permission == LocationPermission.denied) {
        return null;
      }
    }

    if (permission == LocationPermission.deniedForever) {
      return null;
    }

    return await Geolocator.getCurrentPosition();
  }

  static Stream<Position> getLocationStream() {
    return Geolocator.getPositionStream(
      locationSettings: const LocationSettings(
        accuracy: LocationAccuracy.high,
        distanceFilter: 10,
      ),
    );
  }
}

// 运动轨迹记录组件
class WorkoutTracker extends StatefulWidget {
  final String workoutType;

  const WorkoutTracker({Key? key, required this.workoutType}) : super(key: key);

  
  State<WorkoutTracker> createState() => _WorkoutTrackerState();
}

class _WorkoutTrackerState extends State<WorkoutTracker> {
  GoogleMapController? _mapController;
  List<LatLng> _routePoints = [];
  StreamSubscription<Position>? _positionStream;
  bool _isTracking = false;
  double _totalDistance = 0;
  Duration _duration = Duration.zero;
  Timer? _timer;

  
  void initState() {
    super.initState();
    _initializeLocation();
  }

  Future<void> _initializeLocation() async {
    final position = await LocationService.getCurrentLocation();
    if (position != null) {
      setState(() {
        _routePoints.add(LatLng(position.latitude, position.longitude));
      });
    }
  }

  void _startTracking() {
    setState(() {
      _isTracking = true;
      _duration = Duration.zero;
    });

    _timer = Timer.periodic(const Duration(seconds: 1), (timer) {
      setState(() {
        _duration = Duration(seconds: _duration.inSeconds + 1);
      });
    });

    _positionStream = LocationService.getLocationStream().listen((position) {
      final newPoint = LatLng(position.latitude, position.longitude);
      
      if (_routePoints.isNotEmpty) {
        final lastPoint = _routePoints.last;
        final distance = Geolocator.distanceBetween(
          lastPoint.latitude,
          lastPoint.longitude,
          newPoint.latitude,
          newPoint.longitude,
        );
        
        setState(() {
          _totalDistance += distance / 1000; // 转换为公里
          _routePoints.add(newPoint);
        });
      }
    });
  }

  void _stopTracking() {
    setState(() {
      _isTracking = false;
    });

    _timer?.cancel();
    _positionStream?.cancel();
    
    _showWorkoutSummary();
  }

  void _showWorkoutSummary() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('运动完成!'),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            _buildSummaryItem('运动时长', _formatDuration(_duration)),
            _buildSummaryItem('运动距离', '${_totalDistance.toStringAsFixed(2)} km'),
            _buildSummaryItem('平均配速', _calculatePace()),
          ],
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text('取消'),
          ),
          ElevatedButton(
            onPressed: () {
              Navigator.pop(context);
              _saveWorkout();
            },
            child: const Text('保存'),
          ),
        ],
      ),
    );
  }

  Widget _buildSummaryItem(String label, String value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(label),
          Text(value, style: const TextStyle(fontWeight: FontWeight.bold)),
        ],
      ),
    );
  }

  String _formatDuration(Duration duration) {
    String twoDigits(int n) => n.toString().padLeft(2, '0');
    final hours = twoDigits(duration.inHours);
    final minutes = twoDigits(duration.inMinutes.remainder(60));
    final seconds = twoDigits(duration.inSeconds.remainder(60));
    return '$hours:$minutes:$seconds';
  }

  String _calculatePace() {
    if (_totalDistance == 0) return '0:00';
    final paceMinutes = _duration.inMinutes / _totalDistance;
    final minutes = paceMinutes.floor();
    final seconds = ((paceMinutes - minutes) * 60).round();
    return '$minutes:${seconds.toString().padLeft(2, '0')}';
  }

  void _saveWorkout() {
    // 保存运动记录到数据库
    Navigator.pop(context);
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('${widget.workoutType}追踪'),
        actions: [
          if (_isTracking)
            IconButton(
              onPressed: _stopTracking,
              icon: const Icon(Icons.stop),
            ),
        ],
      ),
      body: Column(
        children: [
          // 运动数据显示
          Container(
            padding: const EdgeInsets.all(16),
            color: Colors.orange.withValues(alpha: 0.1),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: [
                _buildStatColumn('时长', _formatDuration(_duration)),
                _buildStatColumn('距离', '${_totalDistance.toStringAsFixed(2)} km'),
                _buildStatColumn('配速', _calculatePace()),
              ],
            ),
          ),
          
          // 地图显示
          Expanded(
            child: GoogleMap(
              onMapCreated: (controller) => _mapController = controller,
              initialCameraPosition: CameraPosition(
                target: _routePoints.isNotEmpty 
                    ? _routePoints.first 
                    : const LatLng(0, 0),
                zoom: 15,
              ),
              polylines: {
                if (_routePoints.length > 1)
                  Polyline(
                    polylineId: const PolylineId('route'),
                    points: _routePoints,
                    color: Colors.orange,
                    width: 5,
                  ),
              },
              markers: {
                if (_routePoints.isNotEmpty)
                  Marker(
                    markerId: const MarkerId('current'),
                    position: _routePoints.last,
                    icon: BitmapDescriptor.defaultMarkerWithHue(
                      BitmapDescriptor.hueOrange,
                    ),
                  ),
              },
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _isTracking ? _stopTracking : _startTracking,
        backgroundColor: _isTracking ? Colors.red : Colors.green,
        child: Icon(_isTracking ? Icons.stop : Icons.play_arrow),
      ),
    );
  }

  Widget _buildStatColumn(String label, String value) {
    return Column(
      children: [
        Text(
          value,
          style: const TextStyle(
            fontSize: 20,
            fontWeight: FontWeight.bold,
          ),
        ),
        const SizedBox(height: 4),
        Text(
          label,
          style: TextStyle(
            fontSize: 12,
            color: Colors.grey[600],
          ),
        ),
      ],
    );
  }

  
  void dispose() {
    _timer?.cancel();
    _positionStream?.cancel();
    super.dispose();
  }
}

社交功能增强

添加更丰富的社交互动功能:

// 运动挑战邀请功能
class ChallengeInvitation {
  final String id;
  final String challengeId;
  final String fromUserId;
  final String fromUserName;
  final String toUserId;
  final String message;
  final DateTime createdAt;
  final String status; // pending, accepted, declined

  ChallengeInvitation({
    required this.id,
    required this.challengeId,
    required this.fromUserId,
    required this.fromUserName,
    required this.toUserId,
    required this.message,
    required this.createdAt,
    required this.status,
  });
}

// 运动小组功能
class WorkoutGroup {
  final String id;
  final String name;
  final String description;
  final String coverImage;
  final String creatorId;
  final List<String> memberIds;
  final List<String> tags;
  final bool isPrivate;
  final DateTime createdAt;

  WorkoutGroup({
    required this.id,
    required this.name,
    required this.description,
    required this.coverImage,
    required this.creatorId,
    required this.memberIds,
    required this.tags,
    required this.isPrivate,
    required this.createdAt,
  });

  int get memberCount => memberIds.length;
}

// 运动伙伴匹配
class WorkoutPartnerMatcher {
  static List<FitnessUser> findCompatiblePartners(
    FitnessUser currentUser,
    List<FitnessUser> allUsers,
    String workoutType,
  ) {
    return allUsers.where((user) {
      if (user.id == currentUser.id) return false;
      
      // 基于位置、运动偏好、时间等因素匹配
      final hasCommonInterests = _hasCommonWorkoutInterests(currentUser, user);
      final isSimilarLevel = _isSimilarFitnessLevel(currentUser, user);
      
      return hasCommonInterests && isSimilarLevel;
    }).toList();
  }

  static bool _hasCommonWorkoutInterests(FitnessUser user1, FitnessUser user2) {
    // 简化的兴趣匹配逻辑
    return true;
  }

  static bool _isSimilarFitnessLevel(FitnessUser user1, FitnessUser user2) {
    // 基于运动数据判断健身水平相似度
    final level1 = _calculateFitnessLevel(user1);
    final level2 = _calculateFitnessLevel(user2);
    return (level1 - level2).abs() <= 1;
  }

  static int _calculateFitnessLevel(FitnessUser user) {
    // 根据运动频率、强度等计算健身等级
    if (user.totalWorkouts < 10) return 1;
    if (user.totalWorkouts < 50) return 2;
    if (user.totalWorkouts < 100) return 3;
    return 4;
  }
}

// 运动排行榜
class LeaderboardPage extends StatefulWidget {
  
  State<LeaderboardPage> createState() => _LeaderboardPageState();
}

class _LeaderboardPageState extends State<LeaderboardPage>
    with SingleTickerProviderStateMixin {
  late TabController _tabController;
  List<FitnessUser> _users = [];

  
  void initState() {
    super.initState();
    _tabController = TabController(length: 4, vsync: this);
    _loadUsers();
  }

  void _loadUsers() {
    // 加载用户数据
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('排行榜'),
        bottom: TabBar(
          controller: _tabController,
          tabs: const [
            Tab(text: '总距离'),
            Tab(text: '总卡路里'),
            Tab(text: '运动次数'),
            Tab(text: '本周'),
          ],
        ),
      ),
      body: TabBarView(
        controller: _tabController,
        children: [
          _buildLeaderboard('distance'),
          _buildLeaderboard('calories'),
          _buildLeaderboard('workouts'),
          _buildLeaderboard('weekly'),
        ],
      ),
    );
  }

  Widget _buildLeaderboard(String type) {
    final sortedUsers = _getSortedUsers(type);
    
    return ListView.builder(
      padding: const EdgeInsets.all(16),
      itemCount: sortedUsers.length,
      itemBuilder: (context, index) {
        final user = sortedUsers[index];
        final rank = index + 1;
        
        return Card(
          margin: const EdgeInsets.only(bottom: 8),
          child: ListTile(
            leading: _buildRankBadge(rank),
            title: Text(user.name),
            subtitle: Text(_getSubtitleText(user, type)),
            trailing: _buildStatValue(user, type),
          ),
        );
      },
    );
  }

  Widget _buildRankBadge(int rank) {
    Color color;
    if (rank == 1) color = Colors.amber;
    else if (rank == 2) color = Colors.grey;
    else if (rank == 3) color = Colors.brown;
    else color = Colors.blue;

    return CircleAvatar(
      backgroundColor: color,
      child: Text(
        '$rank',
        style: const TextStyle(
          color: Colors.white,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }

  List<FitnessUser> _getSortedUsers(String type) {
    switch (type) {
      case 'distance':
        return _users..sort((a, b) => b.totalDistance.compareTo(a.totalDistance));
      case 'calories':
        return _users..sort((a, b) => b.totalCalories.compareTo(a.totalCalories));
      case 'workouts':
        return _users..sort((a, b) => b.totalWorkouts.compareTo(a.totalWorkouts));
      default:
        return _users;
    }
  }

  String _getSubtitleText(FitnessUser user, String type) {
    return user.bio;
  }

  Widget _buildStatValue(FitnessUser user, String type) {
    String value;
    switch (type) {
      case 'distance':
        value = '${user.totalDistance.toStringAsFixed(1)} km';
        break;
      case 'calories':
        value = '${user.totalCalories} cal';
        break;
      case 'workouts':
        value = '${user.totalWorkouts} 次';
        break;
      default:
        value = '';
    }
    
    return Text(
      value,
      style: const TextStyle(
        fontWeight: FontWeight.bold,
        color: Colors.orange,
      ),
    );
  }

  
  void dispose() {
    _tabController.dispose();
    super.dispose();
  }
}

健康数据集成

集成健康数据监测功能:

// 添加依赖
dependencies:
  health: ^4.4.0

// 健康数据服务
class HealthDataService {
  static Future<bool> requestPermissions() async {
    final types = [
      HealthDataType.STEPS,
      HealthDataType.HEART_RATE,
      HealthDataType.ACTIVE_ENERGY_BURNED,
      HealthDataType.DISTANCE_WALKING_RUNNING,
    ];

    return await Health().requestAuthorization(types);
  }

  static Future<Map<String, dynamic>> getTodayHealthData() async {
    final now = DateTime.now();
    final startOfDay = DateTime(now.year, now.month, now.day);

    final steps = await Health().getTotalStepsInInterval(startOfDay, now);
    final heartRate = await Health().getHealthDataFromTypes(
      [HealthDataType.HEART_RATE],
      startOfDay,
      now,
    );
    final calories = await Health().getHealthDataFromTypes(
      [HealthDataType.ACTIVE_ENERGY_BURNED],
      startOfDay,
      now,
    );

    return {
      'steps': steps ?? 0,
      'heartRate': heartRate.isNotEmpty ? heartRate.last.value : 0,
      'calories': calories.fold<double>(0, (sum, data) => sum + (data.value as num)),
    };
  }
}

// 健康数据展示组件
class HealthDataWidget extends StatefulWidget {
  
  State<HealthDataWidget> createState() => _HealthDataWidgetState();
}

class _HealthDataWidgetState extends State<HealthDataWidget> {
  Map<String, dynamic> _healthData = {};
  bool _isLoading = true;

  
  void initState() {
    super.initState();
    _loadHealthData();
  }

  Future<void> _loadHealthData() async {
    final hasPermission = await HealthDataService.requestPermissions();
    if (hasPermission) {
      final data = await HealthDataService.getTodayHealthData();
      setState(() {
        _healthData = data;
        _isLoading = false;
      });
    } else {
      setState(() {
        _isLoading = false;
      });
    }
  }

  
  Widget build(BuildContext context) {
    if (_isLoading) {
      return const Card(
        child: Padding(
          padding: EdgeInsets.all(16),
          child: Center(child: CircularProgressIndicator()),
        ),
      );
    }

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            const Text(
              '今日健康数据',
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            const SizedBox(height: 16),
            Row(
              children: [
                Expanded(
                  child: _buildHealthStat(
                    Icons.directions_walk,
                    '步数',
                    '${_healthData['steps'] ?? 0}',
                    Colors.green,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _buildHealthStat(
                    Icons.favorite,
                    '心率',
                    '${(_healthData['heartRate'] ?? 0).toInt()} bpm',
                    Colors.red,
                  ),
                ),
                const SizedBox(width: 12),
                Expanded(
                  child: _buildHealthStat(
                    Icons.local_fire_department,
                    '卡路里',
                    '${(_healthData['calories'] ?? 0).toInt()}',
                    Colors.orange,
                  ),
                ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildHealthStat(IconData icon, String label, String value, Color color) {
    return Container(
      padding: const EdgeInsets.all(12),
      decoration: BoxDecoration(
        color: color.withValues(alpha: 0.1),
        borderRadius: BorderRadius.circular(8),
      ),
      child: Column(
        children: [
          Icon(icon, color: color, size: 24),
          const SizedBox(height: 8),
          Text(
            label,
            style: TextStyle(
              fontSize: 12,
              color: Colors.grey[600],
            ),
          ),
          const SizedBox(height: 4),
          Text(
            value,
            style: TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.bold,
              color: color,
            ),
          ),
        ],
      ),
    );
  }
}

性能优化和最佳实践

状态管理优化

使用Provider进行更好的状态管理:

// 添加依赖
dependencies:
  provider: ^6.1.1

// 应用状态管理
class FitnessAppState extends ChangeNotifier {
  List<WorkoutRecord> _workoutRecords = [];
  List<FitnessUser> _users = [];
  List<Challenge> _challenges = [];
  FitnessUser? _currentUser;
  bool _isLoading = false;

  // Getters
  List<WorkoutRecord> get workoutRecords => _workoutRecords;
  List<FitnessUser> get users => _users;
  List<Challenge> get challenges => _challenges;
  FitnessUser? get currentUser => _currentUser;
  bool get isLoading => _isLoading;

  // 加载数据
  Future<void> loadData() async {
    _isLoading = true;
    notifyListeners();

    try {
      // 模拟数据加载
      await Future.delayed(const Duration(seconds: 1));
      _generateMockData();
    } finally {
      _isLoading = false;
      notifyListeners();
    }
  }

  // 添加运动记录
  void addWorkoutRecord(WorkoutRecord record) {
    _workoutRecords.insert(0, record);
    notifyListeners();
  }

  // 切换点赞状态
  void toggleLike(String recordId) {
    final index = _workoutRecords.indexWhere((r) => r.id == recordId);
    if (index != -1) {
      final record = _workoutRecords[index];
      final newRecord = WorkoutRecord(
        id: record.id,
        userId: record.userId,
        userName: record.userName,
        userAvatar: record.userAvatar,
        workoutType: record.workoutType,
        title: record.title,
        description: record.description,
        startTime: record.startTime,
        endTime: record.endTime,
        duration: record.duration,
        distance: record.distance,
        calories: record.calories,
        steps: record.steps,
        avgHeartRate: record.avgHeartRate,
        photos: record.photos,
        location: record.location,
        stats: record.stats,
        tags: record.tags,
        likes: record.isLiked ? record.likes - 1 : record.likes + 1,
        comments: record.comments,
        isLiked: !record.isLiked,
        createdAt: record.createdAt,
      );
      _workoutRecords[index] = newRecord;
      notifyListeners();
    }
  }

  void _generateMockData() {
    // 生成模拟数据的逻辑
  }
}

// 在main.dart中使用Provider
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (context) => FitnessAppState(),
      child: const MyApp(),
    ),
  );
}

// 在Widget中使用Provider
class _FitnessHomePageState extends State<FitnessHomePage> {
  
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      context.read<FitnessAppState>().loadData();
    });
  }

  
  Widget build(BuildContext context) {
    return Consumer<FitnessAppState>(
      builder: (context, appState, child) {
        if (appState.isLoading) {
          return const Scaffold(
            body: Center(child: CircularProgressIndicator()),
          );
        }

        return Scaffold(
          // 原有的UI代码
        );
      },
    );
  }
}

缓存和离线支持

// 添加依赖
dependencies:
  shared_preferences: ^2.2.2
  sqflite: ^2.3.0

// 本地存储服务
class LocalStorageService {
  static const String _workoutRecordsKey = 'workout_records';
  static const String _userDataKey = 'user_data';

  static Future<void> saveWorkoutRecords(List<WorkoutRecord> records) async {
    final prefs = await SharedPreferences.getInstance();
    final recordsJson = records.map((r) => r.toJson()).toList();
    await prefs.setString(_workoutRecordsKey, jsonEncode(recordsJson));
  }

  static Future<List<WorkoutRecord>> loadWorkoutRecords() async {
    final prefs = await SharedPreferences.getInstance();
    final recordsString = prefs.getString(_workoutRecordsKey);
    if (recordsString != null) {
      final List<dynamic> recordsJson = jsonDecode(recordsString);
      return recordsJson.map((json) => WorkoutRecord.fromJson(json)).toList();
    }
    return [];
  }

  static Future<void> saveUserData(FitnessUser user) async {
    final prefs = await SharedPreferences.getInstance();
    await prefs.setString(_userDataKey, jsonEncode(user.toJson()));
  }

  static Future<FitnessUser?> loadUserData() async {
    final prefs = await SharedPreferences.getInstance();
    final userString = prefs.getString(_userDataKey);
    if (userString != null) {
      return FitnessUser.fromJson(jsonDecode(userString));
    }
    return null;
  }
}

// 数据库服务
class DatabaseService {
  static Database? _database;

  static Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDatabase();
    return _database!;
  }

  static Future<Database> _initDatabase() async {
    final path = await getDatabasesPath();
    return await openDatabase(
      '$path/fitness_app.db',
      version: 1,
      onCreate: _onCreate,
    );
  }

  static Future<void> _onCreate(Database db, int version) async {
    await db.execute('''
      CREATE TABLE workout_records (
        id TEXT PRIMARY KEY,
        user_id TEXT,
        workout_type TEXT,
        title TEXT,
        description TEXT,
        start_time INTEGER,
        end_time INTEGER,
        duration INTEGER,
        distance REAL,
        calories INTEGER,
        steps INTEGER,
        avg_heart_rate REAL,
        location TEXT,
        likes INTEGER,
        comments INTEGER,
        is_liked INTEGER,
        created_at INTEGER
      )
    ''');
  }

  static Future<void> insertWorkoutRecord(WorkoutRecord record) async {
    final db = await database;
    await db.insert('workout_records', record.toMap());
  }

  static Future<List<WorkoutRecord>> getWorkoutRecords() async {
    final db = await database;
    final maps = await db.query('workout_records', orderBy: 'created_at DESC');
    return maps.map((map) => WorkoutRecord.fromMap(map)).toList();
  }
}

总结

本教程完整展示了如何使用Flutter开发一个功能丰富的运动打卡社交应用。应用包含了运动记录、社交分享、挑战活动、数据统计等核心功能,采用了现代化的UI设计和良好的用户体验。

主要特点

  1. 完整的功能体系:涵盖了运动社交的完整流程
  2. 丰富的交互体验:点赞、评论、分享等社交功能
  3. 数据可视化:图表展示运动趋势和统计
  4. 地理位置集成:运动轨迹记录和地图显示
  5. 健康数据集成:与系统健康应用的数据同步

技术亮点

  • 模块化设计:清晰的代码结构和组件划分
  • 状态管理:使用Provider进行全局状态管理
  • 数据持久化:本地存储和数据库支持
  • 性能优化:列表优化和缓存策略
  • 扩展性强:便于添加新功能和模块

学习收获

通过本教程,你将掌握:

  • Flutter应用的完整开发流程
  • 复杂UI界面的设计和实现
  • 社交功能的开发技巧
  • 数据可视化的实现方法
  • 地理位置和健康数据的集成
  • 性能优化和最佳实践

这个运动打卡社交应用为你提供了一个完整的Flutter开发实践案例,可以作为学习Flutter开发的重要参考,也可以作为实际项目开发的基础框架。

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

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐