Как программист, умеющий готовить, должен каждый день приносить еду себе и девушке, ноЧто есть каждый день - проблема века!
Раньше я думал о разработке приложения, чтобы случайным образом решать, что есть завтра, но самая болезненная вещь в мире это:
Я Android-разработчик, а у моей девушки iPhone! Это самое дальнее расстояние в мире? !
В этот момент пришла Флаттер с ослепительным светом и кокетливыми словами:приходить! Садись на меня!
Это ™ еще не человек?
дисплей приложения
Разработка приложения была в основном завершена за целый день, после чего был внесен ряд корректировок спроса.Давайте сначала посмотрим на картинку:
Посуда шоу
Просто поставьте несколько 🤣
Определите потребности
Из приведенного выше вы можете видеть, что всего имеется четыре функции:
- Случайный выбор блюд, и одно может быть выбрано случайным образом индивидуально
- Подтвердите и сохраните скриншот на свой телефон.
- Посмотреть все рецепты и время приготовления
- добавить новый рецепт
Есть еще одна функция, которая не была отражена, но на самом деле это более важная функция:
Повторяющиеся блюда не могут появиться в течение семи дней.
Код
Давайте рассмотрим каждую функцию по порядку, в первую очередь взглянем на случайный выбор блюд на главной странице.
Функция случайного выбора
Страница кажется очень простым, столбец в порядке, но на самом деле?
Во-первых, определяем наши потребности.Эта функция представляет собой функцию случайного выбора блюд.Логика следующая:
- Сначала определите данные, затем щелкните меню
- Мясные и вегетарианские блюда случайны со случайными эффектами.
определить данные
Данные — это все блюда, которые люди могут приготовить, и они классифицируются как мясные или вегетарианские блюда.
После определения данных, учитывая функцию добавления новых блюд в будущем, используйтеSharedPreferences
сохрани это,
Каждый раз, когда вы открываете приложение, сначала оцените, есть ли кеш, если есть, используйте кеш, если нет, сохраните его.
Случайный выбор блюд со случайными эффектами
Так же нужно учитывать эту функцию.Как видно из картинки выше, блюда будут выбираться случайным образом много раз, а потом страница будет обновляться.
Это определенно не может быть использовано в настоящее времяsetState()
,потому чтоsetState()
Он будет создавать нашу страницу несколько раз, что очень некрасиво.
режим 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
чтобы уведомить поток об обновлении.
Пользовательский интерфейс используется следующим образом:
StreamBuilder(
stream: _bLoC.greenStream,
initialData: "选个菜吧",
builder: (context, snapshot) {
_greenName = snapshot.data;
return Text(
_greenName,
style: TextStyle(fontSize: 34, color: Colors.black87),
);
},
),
Это завершает наши требования на картинке выше, изменяя название блюда каждые 50 миллисекунд для достижения случайного эффекта.
Подтвердите и сохраните скриншот на свой телефон.
Это требование было предложено моей девушкой позже, потому что каждый раз, когда я подтверждаю использование, мне нужно вручную сохранить картинку, а затем поделиться ею со мной в WeChat, поэтому эта функция добавлена.
Таким образом, вам не нужно вручную сохранять изображение каждый раз.
Эта функция имеет следующие три точки:
- Как сохранить скриншоты
- показать скриншот
- Сохраняйте скриншоты на свой телефон
Как сохранить скриншоты
В первую очередь расскажу о том, как сохранять скриншоты, по поводу этой функции я тоже нашел информацию в интернете.
Адрес:FengY - Flutter Learning ---- Скриншоты и размытие по Гауссу
Здесь я также кратко скажу, вы можете проверить эту статью для деталей:
Flutter получает скриншот виджета, используя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
способ отображения изображения.
показать скриншот
Как видно из гифки, после скриншота сначала отобразится маленькая хризантема, а затем всплывет заснятая в данный момент картинка, которая через некоторое время исчезнет.showDialog
СотрудничатьFutureBuilder
.
Так как у снимка экрана будет определенная задержка, а возвращаемое значение — Future , у нас нет причин не делать этого.FutureBuilder
, если вы не понимаетеFutureBuilder
Да, вы можете проверить эту мою статью:Артефакт асинхронного пользовательского интерфейса Flutter FutureBuilder
Примерный код выглядит следующим образом:
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);
}
Повторяющиеся блюда не могут появиться в течение семи дней.
Эта функция также добавлена позже, ведь никто не хочет повторять заказ на ПО каждый день:Вчера я ел тушеную свинину, а сегодня?
Эта функция также имеет несколько незначительных трудностей:
-
SharedPreferences
не может хранить объекты - Как узнать, прошло ли семь дней?
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
, иначе будет сообщено об ошибке.
Как узнать, что прошло семь дней
Поискав информацию, я обнаружил, что есть дротикDateTime
class, в этом классе довольно много методов.
Логика определения того, что прошло семь дней, такова:Получите текущую дату, получите дату использования хранимого блюда и вычтите, больше ли она 6
Затем мы можем судить, когда мы инициализируем блюдо, зацикливаем все блюда, и если блюдо использовалось, то судим:
_meatData.forEach((f) {
if (f.isUsed) {
if (timeNow.difference(DateTime.parse(f.time)).inDays > 6) {
f.time = null;
f.isUsed = false;
}
}
});
Сначала определите, использовалась ли посуда, если использовалась, используйтеDateTime.difference
Метод определения разницы между двумя датами.
Таким образом, вы можете сказать, был ли он использован.
Посмотреть все рецепты и время приготовления
Эта функция в основном используется для силы нагрузки, другие, чтобы увидеть:Черт возьми, я умею готовить столько блюд, это офигенно 🐂🍺.
На самом деле есть несколько замечаний по поводу этой функции:
- Как выставлять вегетарианские и мясные блюда
- Как обновлять использованные/добавленные блюда в режиме реального времени?
Как выставлять вегетарианские и мясные блюда
Здесь я выбираю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
Есть баг, то есть всплывающее окно клавиатуры не выдерживает раскладку!
После моих неустанных усилий я наконец нашел чужую переделку в Интернете.showModalBottomSheetApp
.
Макет может всплывать плавно. Затем, когда вы нажмете «Сохранить», вызовите Scoped_Model, чтобы добавить метод рецепта.
Суммировать
Приложение может быть оптимизировано для ряда функций в будущем, таких как:
- Напишите рецепт фонового хранилища
- Добавляйте фотографии блюд
- Оптимизировать случайные эффекты?
Если у друзей есть какие-либо хорошие результаты или потребности, вы можете прийти ко мне, я это увижу 🌝