内容简介:作为一个会做饭的程序员,每天给女朋友和自己带饭是必须的,可是以前就想过要开发一个APP,来随机决定明天吃什么菜,然而世界上最痛苦的事情是:我是一个 Android 开发崽,而女朋友用的是 iPhone!这难道就是世界上最遥远的距离吗?!
作为一个会做饭的程序员,每天给女朋友和自己带饭是必须的,可是 每天要吃什么却是一个世纪难题!
以前就想过要开发一个APP,来随机决定明天吃什么菜,然而世界上最痛苦的事情是:
我是一个 Android 开发崽,而女朋友用的是 iPhone!这难道就是世界上最遥远的距离吗?!
就在这时,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'];
}
}
由于是个小项目,直接就用的 jsonDecode
/ jsonEncode
,使用该方法的时候必须定义 fromJson
/ toJson
,否则会报错。
如何判断已经过了七天
经过查找资料,发现 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
以上所述就是小编给大家介绍的《一个会做饭的程序员如何每天给女朋友带不同的便当?》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 程序员高薪盛宴背后:程序员正在消失?
- 大龄程序员的出路,程序员的人生
- 程序员有话说 | 平时的程序猿 VS 面试的程序员
- 程序员被沦陷!国内程序员真的饱和了?
- 1024程序员节,祝程序员们节日快乐!
- 不会使用虚拟机的程序员不是好程序员
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Data Structures and Algorithms in Java
Robert Lafore / Sams / 2002-11-06 / USD 64.99
Data Structures and Algorithms in Java, Second Edition is designed to be easy to read and understand although the topic itself is complicated. Algorithms are the procedures that software programs use......一起来看看 《Data Structures and Algorithms in Java》 这本书的介绍吧!