Flutter 框架跨平台鸿蒙开发 - 喝水提醒应用开发指南

Flutter实战:喝水提醒应用开发指南

前言

喝水提醒是一款实用的健康类应用,帮助用户养成良好的饮水习惯。这个项目涵盖了定时提醒、数据持久化、自定义绘制、动画效果等核心技术,是学习Flutter实用应用开发的优秀案例。

效果预览



应用特性:

  • 每日饮水目标设置
  • 定时提醒功能
  • 水杯动画和水波效果
  • 饮水记录管理
  • 数据本地持久化
  • 快捷添加和自定义容量

技术架构

UI层
数据层
核心功能
饮水记录
数据统计
定时提醒
通知弹窗
目标设置
进度计算
SharedPreferences
记录存储
设置存储
水杯动画
CustomPainter
进度显示
AnimatedContainer

核心数据结构

饮水记录模型

dart 复制代码
class WaterRecord {
  final DateTime time;
  final int amount; // 毫升

  WaterRecord({required this.time, required this.amount});

  Map<String, dynamic> toJson() => {
        'time': time.toIso8601String(),
        'amount': amount,
      };

  factory WaterRecord.fromJson(Map<String, dynamic> json) => WaterRecord(
        time: DateTime.parse(json['time']),
        amount: json['amount'],
      );
}

应用状态

dart 复制代码
// 设置
int _dailyGoal = 2000;           // 每日目标(毫升)
int _reminderInterval = 60;      // 提醒间隔(分钟)
bool _reminderEnabled = false;   // 是否启用提醒

// 今日数据
List<WaterRecord> _todayRecords = [];
int _todayTotal = 0;

// 提醒
Timer? _reminderTimer;
DateTime? _nextReminderTime;

数据持久化

SharedPreferences存储

dart 复制代码
Future<void> _saveRecords() async {
  final prefs = await SharedPreferences.getInstance();
  final recordsJson = jsonEncode(
    _todayRecords.map((e) => e.toJson()).toList()
  );
  await prefs.setString('records', recordsJson);
}

Future<void> _loadTodayRecords() async {
  final prefs = await SharedPreferences.getInstance();
  final recordsJson = prefs.getString('records');

  if (recordsJson != null) {
    final List<dynamic> list = jsonDecode(recordsJson);
    final allRecords = list.map((e) => WaterRecord.fromJson(e)).toList();

    // 只保留今天的记录
    final today = DateTime.now();
    _todayRecords = allRecords.where((record) {
      return record.time.year == today.year &&
          record.time.month == today.month &&
          record.time.day == today.day;
    }).toList();

    _todayTotal = _todayRecords.fold(0, (sum, record) => sum + record.amount);
  }
}

设置存储

dart 复制代码
Future<void> _saveSettings() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setInt('dailyGoal', _dailyGoal);
  await prefs.setInt('reminderInterval', _reminderInterval);
  await prefs.setBool('reminderEnabled', _reminderEnabled);
}

定时提醒系统

提醒逻辑

Dialog Timer 应用 用户 Dialog Timer 应用 用户 alt [到达提醒时间] loop [每秒检查] 启用提醒 启动定时器 检查时间 显示提醒弹窗 该喝水啦! 去喝水/稍后 喝水 重置计时

实现代码

dart 复制代码
void _startReminder() {
  _reminderTimer?.cancel();

  _lastReminderTime = DateTime.now();
  _nextReminderTime = _lastReminderTime!.add(
    Duration(minutes: _reminderInterval)
  );

  _reminderTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
    final now = DateTime.now();
    if (now.isAfter(_nextReminderTime!)) {
      _showReminder();
      _nextReminderTime = now.add(Duration(minutes: _reminderInterval));
    }
    setState(() {});
  });
}

void _showReminder() {
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Row(
        children: [
          Text('💧 '),
          Text('该喝水啦!'),
        ],
      ),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          const Text('记得补充水分哦~'),
          Text('今日已喝: $_todayTotal ml'),
          Text('目标: $_dailyGoal ml'),
        ],
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('稍后'),
        ),
        ElevatedButton(
          onPressed: () {
            Navigator.pop(context);
            _showAddWaterDialog();
          },
          child: const Text('去喝水'),
        ),
      ],
    ),
  );
}

倒计时显示

dart 复制代码
String _getTimeUntilReminder() {
  if (!_reminderEnabled || _nextReminderTime == null) return '';

  final now = DateTime.now();
  final diff = _nextReminderTime!.difference(now);

  if (diff.isNegative) return '即将提醒';

  final minutes = diff.inMinutes;
  final seconds = diff.inSeconds % 60;

  return '$minutes:${seconds.toString().padLeft(2, '0')}';
}

水波动画

CustomPainter实现

dart 复制代码
class WavePainter extends CustomPainter {
  final double progress;
  final Color color;

  WavePainter({required this.progress, required this.color});

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = color
      ..style = PaintingStyle.fill;

    final path = Path();
    final waveHeight = 10.0;
    final waveLength = size.width / 2;

    path.moveTo(0, size.height);

    // 绘制正弦波
    for (double x = 0; x <= size.width; x++) {
      final y = waveHeight * sin(
        (x / waveLength * 2 * pi) + (progress * 2 * pi)
      );
      path.lineTo(x, y);
    }

    path.lineTo(size.width, size.height);
    path.close();

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(WavePainter oldDelegate) => true;
}

正弦波公式

y=A⋅sin⁡(2πxλ+ϕ)y = A \cdot \sin\left(\frac{2\pi x}{\lambda} + \phi\right)y=A⋅sin(λ2πx+ϕ)

其中:

  • AAA = 波幅(waveHeight)
  • λ\lambdaλ = 波长(waveLength)
  • ϕ\phiϕ = 相位(progress * 2π)

动画控制

dart 复制代码
late AnimationController _waveController;

_waveController = AnimationController(
  vsync: this,
  duration: const Duration(seconds: 2),
)..repeat();

// 使用
AnimatedBuilder(
  animation: _waveController,
  builder: (context, child) {
    return CustomPainter(
      painter: WavePainter(
        progress: _waveController.value,
        color: Colors.blue.shade400,
      ),
      size: const Size(200, 300),
    );
  },
)

水杯可视化

水杯结构

dart 复制代码
Container(
  width: 200,
  height: 300,
  decoration: BoxDecoration(
    color: Colors.white.withValues(alpha: 0.3),
    borderRadius: const BorderRadius.only(
      bottomLeft: Radius.circular(20),
      bottomRight: Radius.circular(20),
    ),
    border: Border.all(color: Colors.white, width: 3),
  ),
  child: Stack(
    children: [
      // 水波(底部对齐)
      Align(
        alignment: Alignment.bottomCenter,
        child: AnimatedContainer(
          duration: const Duration(milliseconds: 500),
          height: 300 * progress,
          child: WaveAnimation(),
        ),
      ),
      // 刻度线
      ...List.generate(5, (i) {
        final percent = (i + 1) * 20;
        return Positioned(
          bottom: 300 * (percent / 100),
          child: ScaleMark(percent: percent),
        );
      }),
    ],
  ),
)

进度计算

dart 复制代码
final progress = (_todayTotal / _dailyGoal).clamp(0.0, 1.0);

使用 clamp 确保进度在0-1之间,防止溢出。

添加饮水功能

快捷按钮

dart 复制代码
final List<int> _quickAmounts = [100, 200, 250, 300, 500];

void _showAddWaterDialog() {
  showModalBottomSheet(
    context: context,
    builder: (context) {
      return Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          const Text('选择饮水量'),
          Wrap(
            spacing: 12,
            runSpacing: 12,
            children: _quickAmounts.map((amount) {
              return ElevatedButton(
                onPressed: () {
                  Navigator.pop(context);
                  _addWater(amount);
                },
                child: Text('$amount ml'),
              );
            }).toList(),
          ),
          OutlinedButton(
            onPressed: _showCustomAmountDialog,
            child: const Text('自定义'),
          ),
        ],
      );
    },
  );
}

添加记录

dart 复制代码
void _addWater(int amount) {
  setState(() {
    _todayRecords.add(WaterRecord(time: DateTime.now(), amount: amount));
    _todayTotal += amount;
  });

  _saveRecords();
  _addController.forward().then((_) => _addController.reverse());

  // 重置提醒计时
  if (_reminderEnabled) {
    _startReminder();
  }

  // 检查是否达成目标
  if (_todayTotal >= _dailyGoal) {
    _showCongratulations();
  }
}

记录管理

列表显示

dart 复制代码
ListView.builder(
  itemCount: _todayRecords.length,
  reverse: true,  // 最新记录在上
  itemBuilder: (context, index) {
    final record = _todayRecords[_todayRecords.length - 1 - index];
    return Dismissible(
      key: Key(record.time.toString()),
      direction: DismissDirection.endToStart,
      background: Container(
        alignment: Alignment.centerRight,
        color: Colors.red,
        child: const Icon(Icons.delete, color: Colors.white),
      ),
      onDismissed: (_) => _deleteRecord(index),
      child: ListTile(
        leading: const Icon(Icons.water_drop),
        title: Text('${record.amount} ml'),
        trailing: Text(_formatTime(record.time)),
      ),
    );
  },
)

滑动删除

使用 Dismissible 实现滑动删除功能:

  • direction: DismissDirection.endToStart - 只能从右向左滑
  • background - 滑动时显示的背景
  • onDismissed - 删除回调

设置界面

滑块设置

dart 复制代码
// 每日目标
Row(
  children: [
    Expanded(
      child: Slider(
        value: tempGoal.toDouble(),
        min: 1000,
        max: 5000,
        divisions: 40,
        label: '$tempGoal ml',
        onChanged: (value) {
          setState(() => tempGoal = value.toInt());
        },
      ),
    ),
    Text('$tempGoal ml'),
  ],
)

// 提醒间隔
Slider(
  value: tempInterval.toDouble(),
  min: 15,
  max: 180,
  divisions: 11,
  label: '$tempInterval 分钟',
  onChanged: (value) {
    setState(() => tempInterval = value.toInt());
  },
)

开关设置

dart 复制代码
SwitchListTile(
  title: const Text('启用提醒'),
  value: tempEnabled,
  onChanged: (value) {
    setState(() => tempEnabled = value);
  },
)

动画效果

添加水动画

dart 复制代码
late AnimationController _addController;

_addController = AnimationController(
  vsync: this,
  duration: const Duration(milliseconds: 300),
);

// 触发动画
_addController.forward().then((_) => _addController.reverse());

// 应用到水杯
AnimatedBuilder(
  animation: _addController,
  builder: (context, child) {
    return Transform.scale(
      scale: 1.0 + (_addController.value * 0.1),
      child: waterCupWidget,
    );
  },
)

水位变化动画

dart 复制代码
AnimatedContainer(
  duration: const Duration(milliseconds: 500),
  height: 300 * progress,
  child: WaveAnimation(),
)

健康建议

推荐饮水量

人群 每日推荐量
成年男性 2500-3000 ml
成年女性 2000-2500 ml
儿童 1500-2000 ml
老年人 1500-2000 ml

饮水时机

渲染错误: Mermaid 渲染失败: Parse error on line 3: ...itle 一天的饮水时间表 06:30 : 起床后 : 200-300m ----------------------^ Expecting 'EOF', 'SPACE', 'NEWLINE', 'title', 'acc_title', 'acc_descr', 'acc_descr_multiline_value', 'section', 'period', 'event', got 'INVALID'

扩展思路

喝水提醒扩展
数据分析
周报月报
饮水趋势图
达标率统计
健康评分
社交功能
好友PK
排行榜
打卡分享
组队挑战
智能提醒
天气关联
运动检测
作息适配
个性化建议
健康管理
体重记录
BMI计算
卡路里追踪
健康档案

性能优化

1. 定时器管理

dart 复制代码
@override
void dispose() {
  _waveController.dispose();
  _addController.dispose();
  _reminderTimer?.cancel();  // 取消定时器
  super.dispose();
}

2. 数据过滤

dart 复制代码
// 只保留今天的记录,避免数据过多
_todayRecords = allRecords.where((record) {
  return record.time.year == today.year &&
      record.time.month == today.month &&
      record.time.day == today.day;
}).toList();

3. 动画优化

dart 复制代码
// 使用 shouldRepaint 控制重绘
@override
bool shouldRepaint(WavePainter oldDelegate) => true;

总结

这个喝水提醒应用实现了完整的健康管理功能,核心技术点包括:

  1. 定时提醒 - 使用Timer实现定时检查和通知
  2. 数据持久化 - SharedPreferences存储记录和设置
  3. 自定义绘制 - CustomPainter绘制水波动画
  4. 动画系统 - 多个AnimationController协同工作
  5. 交互设计 - 快捷添加、滑动删除等便捷操作

通过这个项目,可以学习到实用应用开发的完整流程,从数据管理到UI设计,从动画效果到用户体验,是Flutter开发的优秀实践案例。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
奋斗的小青年!!2 小时前
Flutter开发鸿蒙应用实战:位置分享组件的跨平台实现
flutter·harmonyos·鸿蒙
鸣弦artha2 小时前
Flutter框架跨平台鸿蒙开发——Embedding层架构概览
flutter·embedding·harmonyos
御承扬2 小时前
鸿蒙原生系列之懒加载分组列表
华为·harmonyos·懒加载·鸿蒙ndk ui
哈哈你是真的厉害3 小时前
React Native 鸿蒙跨平台开发:FlatList 基础列表代码指南
react native·react.js·harmonyos
ljt27249606613 小时前
Flutter笔记--ValueNotifier
笔记·flutter
南村群童欺我老无力.3 小时前
Flutter 框架跨平台鸿蒙开发 - 阅读进度追踪应用开发指南
flutter·华为·harmonyos
世人万千丶3 小时前
鸿蒙跨端框架 Flutter 学习 Day 4:程序生存法则——异常捕获与异步错误处理的熔断艺术
学习·flutter·华为·harmonyos·鸿蒙
向前V3 小时前
Flutter for OpenHarmony数独游戏App实战:底部导航栏
javascript·flutter·游戏
小白阿龙3 小时前
鸿蒙+Flutter 跨平台开发——简易猜数字竞猜游戏实现
flutter·游戏·harmonyos