news 2026/4/3 3:20:21

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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('知道了'),),],),);}}
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/1 23:41:24

CANN高性能集合通信库HCCL的架构设计与分布式训练优化技术解析

CANN高性能集合通信库HCCL的架构设计与分布式训练优化技术解析 cann 组织链接&#xff1a;https://atomgit.com/cann hccl仓库解读链接&#xff1a;https://atomgit.com/cann/hccl 随着深度学习模型规模的不断增长&#xff0c;单卡训练已经无法满足大模型训练的需求&#xff…

作者头像 李华
网站建设 2026/4/1 22:24:10

ET交易员采访|技术分析不再用来预测,而是用来约束自己

在EagleTrader的交易员采访中&#xff0c;我们接触过不少以技术分析见长的交易者&#xff0c;技术往往在他们的策略体系中占据重要位置。而在与林显豪的交流中&#xff0c;我们听到了另一种声音——交易并不是预测市场&#xff0c;而是管理自己的行为。正是这种不同于“看准行情…

作者头像 李华
网站建设 2026/3/15 19:16:14

APP广告变现:如何根据产品特性与用户场景配置广告形式

在应用商业化道路上&#xff0c;选择合适的广告形式是平衡用户体验与收益的关键。下面分享一些实践思路&#xff0c;希望能为你的广告策略设计提供参考。理解主流广告形式的收益特性不同广告形式在收益表现和用户体验上各有特点。激励视频广告通常能带来最高的eCPM&#xff0c;…

作者头像 李华
网站建设 2026/3/31 14:30:45

大数据面试必问:Doris 核心原理与高频考点解析

大数据面试必问&#xff1a;Doris 核心原理与高频考点解析关键词&#xff1a;Doris数据库、MPP架构、列式存储、预聚合、面试高频考点、数据模型、性能调优摘要&#xff1a;本文深度解析大数据领域明星数据库 Doris 的核心原理&#xff0c;结合面试高频考点&#xff0c;通过生活…

作者头像 李华