一个会做饭的程序员如何每天给女朋友带不同的便当?

栏目: IT资讯 · 发布时间: 5年前

内容简介:作为一个会做饭的程序员,每天给女朋友和自己带饭是必须的,可是以前就想过要开发一个APP,来随机决定明天吃什么菜,然而世界上最痛苦的事情是:我是一个 Android 开发崽,而女朋友用的是 iPhone!这难道就是世界上最遥远的距离吗?!

作为一个会做饭的程序员,每天给女朋友和自己带饭是必须的,可是 每天要吃什么却是一个世纪难题!

以前就想过要开发一个APP,来随机决定明天吃什么菜,然而世界上最痛苦的事情是:

我是一个 Android 开发崽,而女朋友用的是 iPhone!这难道就是世界上最遥远的距离吗?!

一个会做饭的 <a href='https://www.codercto.com'>程序员</a> 如何每天给女朋友带不同的便当?

就在这时,Flutter 来了,它带着耀眼的光芒和风骚的话语: 来啊!上我啊!

这™不上还是男人?

APP 展示

APP基本上一个整天就开发完成了,后续进行了一系列的需求调整,先来看图:

一个会做饭的程序员如何每天给女朋友带不同的便当?

一个会做饭的程序员如何每天给女朋友带不同的便当?

一个会做饭的程序员如何每天给女朋友带不同的便当?

一个会做饭的程序员如何每天给女朋友带不同的便当?

菜品展示

一个会做饭的程序员如何每天给女朋友带不同的便当?

一个会做饭的程序员如何每天给女朋友带不同的便当?

一个会做饭的程序员如何每天给女朋友带不同的便当?

简单放几个

确定需求

从上面可以看到一共有四个功能:

1. 随机选菜,并且可以单独随机某一个 2. 确认并保存截图到手机 3. 查看所有菜谱和菜谱使用的时间 4. 添加新的菜谱

还有一个功能没有体现出来,其实也是比较重要的功能:

七天之内不能有重复的菜出现。

代码实现

我们逐个功能来看,首先看一下首页随机选菜。

随机选菜功能

一个会做饭的程序员如何每天给女朋友带不同的便当?

页面看似很简单,一个 Column 包裹住就 OK,但实际呢?

首先确定我们的需求,该功能就是一个随机选菜的功能,那逻辑如下:

1. 先定义数据,然后点击选菜 2. 荤菜 素菜 全部随机 并附带随机效果

定义数据

该数据为个人所有会做的菜品,并且自己分类为 荤菜 还是 素菜。

一个会做饭的程序员如何每天给女朋友带不同的便当?

定义好数据后,因为考虑到后续有添加新菜的功能,使用 SharedPreferences 保存起来,

每次打开APP的时候先判断一下是否有缓存,如果有缓存则用缓存,没有则存入。

随机选菜并附带随机效果

该功能我们也需要考虑一下,从上图也可以看到,会多次随机菜品,然后刷新页面,

那这个时候肯定不能用 setState() ,因为  setState() 会多次 build 我们的页面,这样很不优雅。

BLoC模式

所以我决定使用 BLoC 模式,因为不需要在其他页面使用,所以就定义了一个局部的:


 

class RandomMenuBLoC {

StreamController<String> _meatController;

StreamController<String> _greenController;

Random _random;


RandomMenuBLoC() {

_meatController = StreamController();

_greenController = StreamController();

_random = Random();

}


Stream<String> get meatStream => _meatController.stream;


Stream<String> get greenStream => _greenController.stream;


random(BuildContext context) async {

var meatData = ScopedModel.of<DishModel>(context).meatData;

var greenStuffData = ScopedModel.of<DishModel>(context).greenStuffData;

for (int i = 0; i < 20; i++) {

await Future.delayed(new Duration(milliseconds: 50), () {

return "${meatData.length == 0 ? "暂无可用菜品" : meatData[_random.nextInt(meatData.length)].name}+${greenStuffData.length == 0 ? "暂无可用菜品" : greenStuffData[_random.nextInt(greenStuffData.length)].name}";

}).then((s) {

_meatController.sink.add(s.substring(0, s.indexOf("+")));

_greenController.sink.add(s.substring(s.indexOf("+")+1));

});


}

}


randomMeat(BuildContext context) async{

var meatData = ScopedModel.of<DishModel>(context).meatData;

for (int i = 0; i < 20; i++) {

await Future.delayed(new Duration(milliseconds: 50), () {

return "${meatData.length == 0 ? "暂无可用菜品" : meatData[_random.nextInt(meatData.length)].name}";

}).then((s) {

_meatController.sink.add(s);

});

}

}


randomGreen(BuildContext context) async{

var greenStuffData = ScopedModel.of<DishModel>(context).greenStuffData;

for (int i = 0; i < 20; i++) {

await Future.delayed(new Duration(milliseconds: 50), () {

return "${greenStuffData.length == 0 ? "暂无可用菜品" : greenStuffData[_random.nextInt(greenStuffData.length)].name}";

}).then((s) {

_greenController.sink.add(s);

});

}

}


dispose() {

_meatController.close();

_greenController.close();

}

}


首先因为考虑到会单独刷新某一个数据,所以定义了两个 streamController,一个素菜,一个荤菜。

然后下面就是随机菜品的方法,通过 Future.delayed 来进行一个50毫秒的延时后返回荤菜和素菜随机的结果,并且在  then 方法中调用  streamController.sink.add 来通知 stream 刷新。

UI使用如下:


 

StreamBuilder(

stream: _bLoC.greenStream,

initialData: "选个菜吧",

builder: (context, snapshot) {

_greenName = snapshot.data;

return Text(

_greenName,

style: TextStyle(fontSize: 34, color: Colors.black87),

);

},

),

这样就完成了我们上图的需求,每隔50毫秒就改变一下菜名,来达到随机的效果。

确认并保存截图到手机

该需求是女朋友后续提出来的,因为每次确认使用后,都需要手动保存图片,然后微信分享给我,所以添加了这个功能。

这样就不用每次都手动保存图片了。

一个会做饭的程序员如何每天给女朋友带不同的便当?

该功能有如下三个小点:

1. 如何保存截图 2. 显示截图 3. 保存截图到手机

如何保存截图

首先说如何保存截图,关于该功能,我也是网上查找资料所得,

地址为: FengY - Flutter学习 ---- 屏幕截图和高斯模糊 [1]

这里我也简单说一下,具体可以查看该文章:

Flutter 获取 widget 的截图 使用到的是 RepaintBoundary ,代码如下:


 

return RepaintBoundary(

key: rootWidgetKey,

child: Scaffold(),

);

通过 RepaintBoundary 包裹住  Scaffold ,然后给定一个  globalKey ,这样就可以进行截图了:


 

// 代码为 FengY 所写

// 截图boundary,并且返回图片的二进制数据。

Future<Uint8List> _capturePng() async {

RenderRepaintBoundary boundary = globalKey.currentContext.findRenderObject();

ui.Image image = await boundary.toImage();

// 注意:png是压缩后格式,如果需要图片的原始像素数据,请使用rawRgba

ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png);

Uint8List pngBytes = byteData.buffer.asUint8List();

return pngBytes;

}

调用该方法后,返回的就是一个 Future<Uint8List> 对象了,后续使用  Image.memory 方法即可显示该图片。

显示截图

从 gif 可以看到,在截图以后会先显示一个小菊花,然后弹出当前所截图片,一会以后会消失,这里使用的是 showDialog 配合  FutureBuilder

因为截图会有一定的延时,并且返回值为一个 Future ,那我们没有理由不用 FutureBuilder ,如有不了解  FutureBuilder 的,可以查看我的这篇文章: Flutter FutureBuilder 异步UI神器

大概代码如下:


 

showDialog(

context: context,

builder: (context) {

return FutureBuilder<Uint8List>(

future: _future,

builder: (BuildContext context,

AsyncSnapshot snapshot) {

switch (snapshot.connectionState) {

case ConnectionState.none:

case ConnectionState.active:

case ConnectionState.waiting:

return Center(

child: CupertinoActivityIndicator());

case ConnectionState.done:

_saveImage(snapshot.data);


Future.delayed(

Duration(milliseconds: 1500), () {

Navigator.of(context,rootNavigator: true).pop();

});

return Container(

margin:

EdgeInsets.symmetric(vertical: 50),

decoration: BoxDecoration(

borderRadius: BorderRadius.all(

Radius.circular(18)),

color: Colors.transparent,

),

child: Image.memory(snapshot.data),

);

}

},

);

});

保存截图到手机

该功能使用的是 image_gallery_saver 库,该库通过调用原生方法来实现。由于要保存图片,所以必须要添加手机图片读写权限。

使用方法也很简单,一行代码就搞定:


 

_saveImage(Uint8List img) async {

await ImageGallerySaver.save(img);

}

七天之内不能出现重复菜品

该功能也是后续添加的,因为毕竟谁也不想每天在软件上点菜都有重复: 我昨天吃红烧肉了,今天还吃?

该功能也有几个小难点:

1. SharedPreferences 不能存储对象 2. 如何判断已经过了七天?

SharedPreferences 不能存储对象

最开始的时候只是存储了菜名,并没有该菜是否已经使用,所以要定义一个对象来存储数据,

后来发现 SharedPreferences 不能存储对象,那没办法,只能转 json 了:


 

class Food {

String name;

String time;

bool isUsed;


Food(

this.name, {

this.time, // 确认吃的时间,用于七天自动过期

this.isUsed = false,

});


Map toJson() {

return {'name': this.name, 'time': this.time, 'isUsed': this.isUsed};

}


Food.fromJson(Map<String, dynamic> json) {

this.name = json['name'];

this.time = json['time'];

this.isUsed = json['isUsed'];

}

}

由于是个小项目,直接就用的 jsonDecodejsonEncode ,使用该方法的时候必须定义  fromJsontoJson ,否则会报错。

如何判断已经过了七天

经过查找资料,发现 dart 中有一个 DateTime 类,该类的方法确实不少。

判断过了七天的逻辑就是: 获取当前日期,获取存储的菜的使用日期,相减是否大于6

那我们在初始化菜的时候就可以判断,循环所有的菜品,如果该菜品已经被使用,那么则去判断:


 

_meatData.forEach((f) {

if (f.isUsed) {

if (timeNow.difference(DateTime.parse(f.time)).inDays > 6) {

f.time = null;

f.isUsed = false;

}

}

});

首先判断该菜品是否被使用过,如果已经被使用过,则使用 DateTime.difference 方法来判断两个日期之间的差。

这样就能判断出来是否已经被使用过了。

查看所有菜谱和菜谱使用的时间

该功能主要为装逼所用,别人一看: 卧槽,会做这么多菜,牛逼:ox::beer:。

一个会做饭的程序员如何每天给女朋友带不同的便当?

该功能其实也有几个需要注意的点:

1. 如何展示素菜和荤菜 2. 如何实时更新已经使用过/新增的菜?

如何展示素菜和荤菜

这里我选用的是 ExpansionPanelList ,用它来实现最合适不过。

如果你还没有了解过 ExpansionPanelList ,那么我建议读我的这篇文章: Flutter ExpansionPanel 超级实用展开控件

剩下的就很简单了,通过数据来判断是否展示 已使用标识 和 已使用时间。

简单代码如下:


 

return Padding(

child: Row(

children: <Widget>[

data.isUsed

? Icon(

Icons.done,

color: Colors.red,

)

: Container(),

Expanded(

child: Padding(

padding:

const EdgeInsets.symmetric(horizontal: 12.0),

child: Text(

data.name,

style: TextStyle(fontSize: 16),

),

),

),

data.isUsed

? Text(

data.time.substring(0, data.time.indexOf('.')))

: Container(),

],

),

padding: EdgeInsets.all(20),

);

如何实时更新已经使用过/新增的菜?

该功能就需要用到我们所说的 状态管理 ,这里我使用的是  Scoped_Model

在首页和该页都会使用到该功能,当已经使用一个菜的时候,所有菜品里应实时更新,新增菜品的时候也应如此。

使用菜品代码如下:


 

/// 确认使用该食物

useFood(String greenName, String meatName) {

var time = DateTime.now();


for (int i = 0; i < _greenStuffData.length; i++) {

if (_greenStuffData[i].name == greenName) {

_greenStuffData[i].isUsed = true;

_greenStuffData[i].time = time.toString();

break;

}

}


for (int i = 0; i < _meatData.length; i++) {

if (_meatData[i].name == meatName) {

_meatData[i].isUsed = true;

_meatData[i].time = time.toString();

break;

}

}


updateData('greenStuffData', _greenStuffData);

updateData('meatData', _meatData);

showToast('使用成功并保存至相册',

textStyle: TextStyle(fontSize: 20),

textPadding: EdgeInsets.symmetric(horizontal: 20, vertical: 10),

position: ToastPosition(align: Alignment.bottomCenter),

radius: 30,

backgroundColor: Colors.grey[400]);

notifyListeners();

}

代码很简单,就是两个循环查找,然后 notifyListeners()

添加新的菜谱

菜谱是自己写的,如果女朋友想吃别的菜怎么办?新增啊!

一个会做饭的程序员如何每天给女朋友带不同的便当?

这里的弹出框使用的是 showModalBottomSheet ,但是用过该方法的人都知道  BottomSheetDialog 有个 bug,那就是键盘弹出框不能顶起布局!

经过我不懈努力,终于,在网上找到了别人重写的 showModalBottomSheetApp

可以顺利弹起布局了。然后在点击保存时,调用 Scoped_Model 中增加菜谱方法。

总结

后续可能会对该APP进行一系列的功能优化,比如:

写个后台存储菜谱 增加菜品图片 优化随机效果?

如果朋友们有什么好的效果或者需求可以找我呀,我来实现看看:full_moon_with_face:

一个会做饭的程序员如何每天给女朋友带不同的便当?

References

[1] FengY - Flutter学习 ---- 屏幕截图和高斯模糊:  https://juejin.im/post/5b03ea7e51882565bd2594b0


以上所述就是小编给大家介绍的《一个会做饭的程序员如何每天给女朋友带不同的便当?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

深入React技术栈

深入React技术栈

陈屹 / 人民邮电出版社 / 2016-11-1 / CNY 79.00

全面讲述React技术栈的第一本原创图书,pure render专栏主创倾力打造 覆盖React、Flux、Redux及可视化,帮助开发者在实践中深入理解技术和源码 前端组件化主流解决方案,一本书玩转React“全家桶” 本书讲解了非常多的内容,不仅介绍了面向普通用户的API、应用架构和周边工具,还深入介绍了底层实现。此外,本书非常重视实战,每一节都有实际的例子,细节丰富。我从这......一起来看看 《深入React技术栈》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

MD5 加密
MD5 加密

MD5 加密工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具