当前位置: 首页 > news >正文

Flutter for OpenHarmony 植物养护 App:用数字花园培育你的绿色生活

Flutter for OpenHarmony 植物养护 App:用数字花园培育你的绿色生活

在快节奏的都市生活中,一盆绿植不仅是窗台的点缀,更是心灵的慰藉。然而,“忘记浇水”“过度溺爱”常常让我们的植物伙伴悄然枯萎。

🌐 加入社区 欢迎加入开源鸿蒙跨平台开发者社区,获取最新资源与技术支持: 👉开源鸿蒙跨平台开发者社区


完整效果

一、核心理念:让养护变得智能而有温度

该 App 围绕三个关键体验构建:

  1. 智能提醒:基于浇水间隔自动判断是否“口渴”;
  2. 可视化成长:植物图标随生长阶段变化,浇水可能触发“成长动画”;
  3. 情感反馈:健康状态文字(“茁壮成长”、“需浇水”)+ 进度条颜色变化,赋予植物“情绪”。

🌿技术不应冰冷,而应成为连接人与自然的桥梁


二、数据模型:会“思考”的Plant

classPlant{finalStringname;finalStringtype;// 观叶/多肉/开花finalint waterInterval;// 浇水周期(天)finalDateTimelastWatered;finalDateTimeplantedDate;finaldouble growthStage;// 0.0(幼苗)→ 1.0(成熟)boolgetneedsWater{...}// 是否需要浇水?StringgethealthStatus{...}// 健康状态描述IconDatagetplantIcon{...}// 动态图标}

💡浇水 ≠ 机械操作:每次浇水有50%概率促进生长,模拟“精心照料带来回报”的自然法则。


三、交互亮点:让每一次点击都有意义

1.动态植物图标 + 旋转动画

AnimatedSwitcher(transitionBuilder:(child,animation)=>RotationTransition(...),child:Icon(plant.plantIcon,key:ValueKey(plant.growthStage)),)

2.智能浇水按钮

3.浇水进度条:时间的可视化

LinearProgressIndicator(value:daysSinceWater/waterInterval,color:needsWater?Colors.red:Colors.green,)

4.撤销删除(Undo Pattern)


四、细节巧思:超越功能的情感化设计

✅ 随机生成新植物

finalplantNames=['薄荷','绿萝','芦荟',...];finaltypes=['观叶','多肉','开花'];// 自动分配合理浇水间隔

✅ 养护小贴士(Help Dialog)

✅ 健康状态语义化

状态条件用户感受
需浇水超过浇水间隔紧迫感,需立即行动
状态良好距离下次浇水 > 50%安心,维持现状
茁壮成长刚浇完水成就感,被认可

🌱 文字比颜色更能传递情感——“茁壮成长”比“健康”更有生命力。


五、UI/UX 设计:清新治愈的植物美学

元素设计说明
主色调Colors.green+ 深绿 AppBar (#2E7D32) —— 自然、专业
背景色#FCFDFC—— 接近纸张的米白,柔和不刺眼
卡片圆角20—— 比常规更大,模拟“花盆”轮廓
空状态绿色描边圆形 +local_florist图标,引导明确
FAB 按钮绿底白加号,符合 Material 规范

🎨 整体风格宁静、有机、无干扰,让用户专注于植物本身。


六、技术实现亮点

技术点应用说明
AnimatedSwitcher+RotationTransition实现图标切换的流畅旋转动画
ValueKey(plant.growthStage)确保动画在growthStage变化时触发
min(1.0, progress)防止进度条溢出,保持 UI 稳定
DateTime.now().millisecondsSinceEpoch生成唯一 ID,简单可靠
copyWith模式安全更新植物状态,避免直接修改

七、未来扩展方向

当前版本已具备完整核心体验,可进一步拓展:

  1. 本地持久化:使用hive保存植物数据,重启不丢失;
  2. 通知提醒:集成flutter_local_notifications,缺水时推送;
  3. 照片记录:允许用户上传植物照片,形成成长日记;
  4. 品种百科:点击植物查看详细养护指南;
  5. 成就系统:如“连续浇水30天”、“养活5种植物”等徽章;
  6. AR 预览:用 AR 将虚拟植物“放置”在真实桌面(arkit_flutter_plugin)。

八、结语:在数字世界,种下一棵真实的树

这个植物养护 App 的真正价值,不在于它多么“智能”,而在于它唤醒了我们对生命的关注

当你看到“小绿”的进度条变红,点击“立即浇水”,看着它图标旋转、状态变为“茁壮成长”——那一刻,你不是在操作一个 App,而是在履行一份对生命的承诺

完整代码

import'package:flutter/material.dart';import'dart:math';voidmain(){runApp(const PlantCareApp());}class PlantCareApp extends StatelessWidget{const PlantCareApp({super.key});@override Widget build(BuildContext context){returnMaterialApp(debugShowCheckedModeBanner: false, title:'🌿 植物养护', theme: ThemeData(brightness: Brightness.light, primarySwatch: Colors.green, scaffoldBackgroundColor: const Color(0xFFFCFDFC), appBarTheme: const AppBarTheme(backgroundColor: Colors.transparent, foregroundColor: const Color(0xFF2E7D32), elevation:0,), floatingActionButtonTheme: const FloatingActionButtonThemeData(backgroundColor: Colors.green, foregroundColor: Colors.white,),), home: const PlantCareScreen(),);}}// 植物数据模型 class Plant{final Stringid;final String name;final Stringtype;// 多肉/观叶/开花 final int waterInterval;// 浇水间隔(天) final DateTime lastWatered;final DateTime plantedDate;final double growthStage;//0.0-1.0 生长阶段 Plant({required this.id, required this.name, required this.type, required this.waterInterval, required this.lastWatered, required this.plantedDate, this.growthStage=0.0,});// 计算是否需要浇水 bool get needsWater{final daysSinceWater=DateTime.now().difference(lastWatered).inDays;returndaysSinceWater>=waterInterval;}// 计算健康状态 String get healthStatus{if(needsWater)return'需浇水';final daysSinceWater=DateTime.now().difference(lastWatered).inDays;if(daysSinceWater<=waterInterval ~/2)return'茁壮成长';return'状态良好';}// 获取植物图标(根据类型和生长阶段) IconData get plantIcon{if(growthStage<0.3)returnIcons.spa;if(type=='开花')returnIcons.local_florist;if(type=='多肉')returnIcons.local_florist;returnIcons.eco;}// 创建新实例(用于状态更新) Plant copyWith({DateTime? lastWatered, double? growthStage,}){returnPlant(id: id, name: name, type: type, waterInterval: waterInterval, lastWatered: lastWatered ?? this.lastWatered, plantedDate: plantedDate, growthStage: growthStage ?? this.growthStage,);}}class PlantCareScreen extends StatefulWidget{const PlantCareScreen({super.key});@override State<PlantCareScreen>createState()=>_PlantCareScreenState();}class _PlantCareScreenState extends State<PlantCareScreen>{// 初始植物数据 List<Plant>_plants=[Plant(id:'1', name:'小绿', type:'观叶', waterInterval:3, lastWatered: DateTime.now().subtract(const Duration(days:4)), plantedDate: DateTime.now().subtract(const Duration(days:30)), growthStage:0.7,), Plant(id:'2', name:'仙人球', type:'多肉', waterInterval:14, lastWatered: DateTime.now().subtract(const Duration(days:10)), plantedDate: DateTime.now().subtract(const Duration(days:60)), growthStage:0.9,), Plant(id:'3', name:'茉莉花', type:'开花', waterInterval:2, lastWatered: DateTime.now().subtract(const Duration(days:1)), plantedDate: DateTime.now().subtract(const Duration(days:15)), growthStage:0.4,),];final Random _random=Random();// 浇水操作 void _waterPlant(String plantId){setState((){ _plants=_plants.map((plant){ if(plant.id==plantId){//更新浇水时间 final newPlant=plant.copyWith(lastWatered:DateTime.now());// 随机促进生长(50%概率)if(_random.nextDouble()>0.5&&newPlant.growthStage<1.0){final growthIncrement=0.1+ _random.nextDouble()*0.15;returnnewPlant.copyWith(growthStage: min(1.0, newPlant.growthStage + growthIncrement),);}returnnewPlant;}returnplant;}).toList();});// 显示反馈 final plant=_plants.firstWhere((p)=>p.id==plantId);ScaffoldMessenger.of(context).showSnackBar(SnackBar(content:Text('${plant.name} 已浇水!${plant.growthStage>=1.0?"已成熟!":""}'),duration:const Duration(seconds:2),),);}//添加新植物 void _addNewPlant(){ final plantNames=['薄荷','绿萝','芦荟','吊兰','虎皮兰','长寿花','蟹爪兰'];final types=['观叶','多肉','开花'];final intervals=[2,3,5,7,10,14];final name=plantNames[_random.nextInt(plantNames.length)];final type=types[_random.nextInt(types.length)];final interval=intervals[_random.nextInt(intervals.length)];final newPlant=Plant(id:DateTime.now().millisecondsSinceEpoch.toString(),name:name,type:type,waterInterval:interval,lastWatered:DateTime.now(),plantedDate:DateTime.now(),);setState((){ _plants.add(newPlant);});ScaffoldMessenger.of(context).showSnackBar(SnackBar(content:Text('欢迎新成员:$name')),);}// 删除植物 void _deletePlant(String plantId){final plantToDelete=_plants.firstWhere((p)=>p.id==plantId);setState((){ _plants.removeWhere((p)=>p.id==plantId);});ScaffoldMessenger.of(context).showSnackBar(SnackBar(content:Text('${plantToDelete.name} 已移除'),action:SnackBarAction(label:'撤销',onPressed:(){ setState((){ _plants.insert(0,plantToDelete);});},),),);} @override Widget build(BuildContext context){ return Scaffold(appBar:AppBar(title:const Text('我的植物',style:TextStyle(fontSize:22,fontWeight:FontWeight.bold),),centerTitle:true,actions:[ IconButton(icon:const Icon(Icons.info_outline,size:24),onPressed:_showCareTips,),],),body:_plants.isEmpty?_buildEmptyState():ListView.builder(padding:const EdgeInsets.only(top:16,bottom:90),itemCount:_plants.length,itemBuilder:(context,index){ return _buildPlantCard(_plants[index]);},),floatingActionButton:FloatingActionButton(onPressed:_addNewPlant,child:const Icon(Icons.add),tooltip:'添加新植物',),);} Widget _buildEmptyState(){ return Center(child:Padding(padding:const EdgeInsets.all(32),child:Column(mainAxisAlignment:MainAxisAlignment.center,children:[ Container(padding:const EdgeInsets.all(24),decoration:BoxDecoration(color:Colors.green.withOpacity(0.1),shape:BoxShape.circle,),child:const Icon(Icons.local_florist,size:64,color:Colors.green,),),const SizedBox(height:24),const Text('还没有植物伙伴',style:TextStyle(fontSize:20,fontWeight:FontWeight.bold),),const SizedBox(height:8),const Text('点击下方按钮添加你的第一株植物',textAlign:TextAlign.center,style:TextStyle(color:Colors.grey,height:1.5),),],),),);} Widget _buildPlantCard(Plant plant){ final isNeedingWater=plant.needsWater;final healthColor=isNeedingWater?Colors.red:Colors.green;return Card(margin:const EdgeInsets.symmetric(horizontal:16,vertical:12),elevation:2,shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(20)), child: Padding(padding: const EdgeInsets.all(16), child: Row(children:[// 植物图标(带动画) Container(width:70, height:70, decoration: BoxDecoration(color: Colors.green.withOpacity(0.1), borderRadius: BorderRadius.circular(16),), child: AnimatedSwitcher(duration: const Duration(milliseconds:500), transitionBuilder:(Widget child, Animation<double>animation){returnRotationTransition(turns: Tween<double>(begin:0, end:1).animate(animation), child: child,);}, child: Icon(plant.plantIcon, key: ValueKey(plant.growthStage), size:40, color: Colors.green.shade700,),),), const SizedBox(width:16), // 植物信息 Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children:[// 名称和类型 Row(children:[Expanded(child: Text(plant.name, style: const TextStyle(fontSize:18, fontWeight: FontWeight.bold,), maxLines:1, overflow: TextOverflow.ellipsis,),), GestureDetector(onTap:()=>_deletePlant(plant.id), child: const Icon(Icons.delete_outline, size:20, color: Colors.grey,),),],), const SizedBox(height:4), Text('${plant.type} · 种植${DateTime.now().difference(plant.plantedDate).inDays}天', style: const TextStyle(color: Colors.grey, fontSize:14),), const SizedBox(height:12), // 健康状态 Row(children:[Container(width:12, height:12, decoration: BoxDecoration(color: healthColor, shape: BoxShape.circle,),), const SizedBox(width:8), Text(plant.healthStatus, style: TextStyle(color: healthColor, fontWeight: FontWeight.w600),),],), const SizedBox(height:8), // 浇水进度条 LinearProgressIndicator(value: min(1.0, DateTime.now().difference(plant.lastWatered).inDays / plant.waterInterval,), backgroundColor: Colors.grey.shade200, color: isNeedingWater ? Colors.red:Colors.green, minHeight:6, borderRadius: BorderRadius.circular(3),),],),), const SizedBox(width:16), // 浇水按钮 ElevatedButton(onPressed:()=>_waterPlant(plant.id), style: ElevatedButton.styleFrom(backgroundColor: isNeedingWater ? Colors.red:Colors.green, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), padding: const EdgeInsets.symmetric(vertical:12, horizontal:16),), child: Text(isNeedingWater ?'立即浇水':'浇水', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),),),],),),);}void_showCareTips(){showDialog(context: context, builder:(context)=>AlertDialog(title: const Text('🌱 养护小贴士'), content: SingleChildScrollView(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: const[Text('• 观叶植物:保持土壤微湿,避免阳光直射'), SizedBox(height:8), Text('• 多肉植物:宁干勿湿,充足光照'), SizedBox(height:8), Text('• 开花植物:花期需充足水分和磷钾肥'), SizedBox(height:16), Divider(), SizedBox(height:8), Text('浇水提示:当进度条变红时,请及时浇水!', style: TextStyle(color: Colors.red),),],),), actions:[TextButton(onPressed: Navigator.of(context).pop, child: const Text('知道了'),),],),);}}
http://www.jsqmd.com/news/362647/

相关文章:

  • 2026年评价高的假山设计公司推荐:溶洞假山设计/卡通民宿设计/护坡假山设计施工/景区民宿修建/民宿建造/选择指南 - 优质品牌商家
  • 国内进口的车膜品牌推荐几家
  • 2026年烟囱塔架厂家推荐:塔架式烟囱塔/工业烟囱塔/景观监控塔/火炬烟囱塔/监控铁塔/瞭望监控塔/碳钢烟囱塔/选择指南 - 优质品牌商家
  • 2/9 树的重心树的同根 总结
  • Linux 中断驱动程序--按键中断驱动
  • 【开题答辩全过程】以 基于SpringBoot云旅行微信小程序的设计和实现为例,包含答辩的问题和答案
  • 2026年高口碑系统门窗五金和五金配件产品推荐排行榜,提升家装体验 - 睿易优选
  • 2026年智能工厂规划全流程解析:浙江及江苏优秀企业推荐 - 孟哥商业圈
  • AI应用架构师分享:智能产品推荐AI系统的模型压缩技术
  • 2026年公司注册厂家权威推荐榜:南非公司注册、境外投资备案ODI公司、广州境外投资备案ODI、德国公司注册选择指南 - 优质品牌商家
  • AI 时代的前端技术:从系统编程到 JavaScript/TypeScript
  • 【claude】Claude 如何节省 Token?10 个实用技巧让成本降低 70%
  • 2026年评价高的不锈钢烟囱塔公司推荐:碳钢烟囱塔、角钢监控塔、道路监控塔、钢管监控塔、镀锌烟囱塔架选择指南 - 优质品牌商家
  • 2026年实测对比:宁波智能工厂规划服务商TOP5深度解析 - 孟哥商业圈
  • 【开题答辩全过程】以 河市富达购物微信小程序为例,包含答辩的问题和答案
  • 2026四川加气砖优选指南:从蒸压砌块到轻质隔墙的五家可靠之选 - 深度智识库
  • 递归思想的思路分享
  • Flutter for OpenHarmony 习惯养成 App:用打卡机制打造自律生活的可视化引擎
  • 【claude】最新Claude Opus 4.6 深度评测:AI编程工具的新王者诞生!
  • 深入解析:小白也能看懂的DeepSeek-R1本地部署指南
  • Flutter for OpenHarmony音乐播放器实战:打造动态波形可视化与沉浸式播放体验
  • 2026年自动洗瓶机厂家推荐:饮料瓶洗瓶机/啤酒瓶洗瓶机/回收瓶洗瓶机/实验室洗瓶机/毛刷式洗瓶机/选择指南 - 优质品牌商家
  • Flutter for OpenHarmony BMI 健康计算器:打造支持深色模式的智能健康工具
  • 【开题答辩全过程】以 基于Springboot图书管理系统为例,包含答辩的问题和答案
  • 2026年碰碰车厂家推荐:逍遥乐吧车/360摇滚乐吧车/亲子双人碰碰车/公园碰碰车/发光漂移碰碰车/商场碰碰车/选择指南 - 优质品牌商家
  • 2026年实测TOP3智能工厂规划服务商深度对比 - 孟哥商业圈
  • P3195 [HNOI2008] 玩具装箱
  • 题解:AWC 0001
  • 2026牛客寒假算法基础集训营4 题解
  • 2026年评价高的三柱避雷塔公司推荐:监控铁塔、角钢监控塔、角钢避雷塔、道路监控塔、钢管避雷塔、镀锌监控塔架选择指南 - 优质品牌商家