flutter 项目实践2
本片文章来自与我自己的有道云笔记 要看图片请点击链接
文档:Day 4_3 项目实践2.md
链接:http://note.youdao.com/noteshare?id=f28e3058fea4d26f1b32bdc21f1a220c&sub=A3757D0FAF4C4BC29A8CF2E6F1C8DBD6
上次做了什么
- 我们把框架搭好了IndexedStack 分开了基础的页面
- 制作了简单的首页 读取json文件 然后展示
我们的首页基本就算制作完了
然后我们想做一些展示
home_content.dart里面的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UVw3V5Ty-1589428979450)(26B3A8A9B8744F819E0460F9C6DB908E)]
其实算是一个单独的组件的一样的东西 所以 我们这里 最好使用 一个单独的东西将这个东西封装起来
不然的话 我们的嵌套层级就有点多了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VClTa8N7-1589428979455)(F5170614F12041D394BFEBC85B11D566)]
这里也可以使用自动的抽取来完成 对代码的分解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qBzTJNuB-1589428979458)(601B2EC614E1414AAC27E9E8645B59BB)]
ctrl + alt + W 自动抽取代码
我这里把它改成了CTRL + alt + 1 好像这里ctrl + alt + w是有冲突的
所以我们使用这个来做
但是你用快捷键做抽取 不太好
这里如果依赖的太多了 他就会把这里依赖当成 参数 传过来
这里 背景的颜色都被当作参数来传了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dBm2Xlcf-1589428979462)(E9F1C3D10A6B4594B5A6486B85A48DBB)]
当做参数传过来了 这个样式我们是直接抽到一个文件里面根本不需要这样做
所以如果我们自己抽取的的话我们可以这样 抽取
class HYHomeCategoryItem extends StatelessWidget {final HYCategoryModel _category;HYHomeCategoryItem(this._category);@overrideWidget build(BuildContext context) {return Container(decoration: BoxDecoration(color: _category.cColor,borderRadius: BorderRadius.circular(12.px),gradient: LinearGradient(colors: [_category.cColor.withOpacity(.5),_category.cColor])),alignment: Alignment.center,child: Text(_category.title,style: Theme.of(context).textTheme.display2.copyWith(fontWeight: FontWeight.bold,)));}
}
但是这样也不是我们最终想要得到的
对于这种代码我们希望 他能单独再某一个文件里面
import 'package:flutter/material.dart';
import 'package:project03/core/model/category_model.dart';import "../../../core/extension/int_extension.dart";class HYHomeCategoryItem extends StatelessWidget {final HYCategoryModel _category;HYHomeCategoryItem(this._category);@overrideWidget build(BuildContext context) {return Container(decoration: BoxDecoration(color: _category.cColor,borderRadius: BorderRadius.circular(12.px),gradient: LinearGradient(colors: [_category.cColor.withOpacity(.5),_category.cColor])),alignment: Alignment.center,child: Text(_category.title,style: Theme.of(context).textTheme.display2.copyWith(fontWeight: FontWeight.bold,)));}
}
这样抽离代码然后使用就可以了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bmwZgUn4-1589428979464)(668FD6B085014E34A086F2008103CE40)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vnolFiAX-1589428979468)(2FF56D628ABD43748FA82590477D701A)]
现在这个地方展示的数据是在一个文件里面的 我们 搞了一个工具类 然后异步加载这个文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7qs1KR14-1589428979471)(B97FB12C620E4BD6B3705405115C28A6)]
所以我们这里多了很多的东西 多了很多的冗余代码
因为这个东西不是写死的 所以我们需要 加载了以后对 展示的内容做变化然后再展示 所以我们就需要 statfulWidget 同时 这个东西需要变量来存储 更新以后还需要setState刷新页面
我们定义一个
- fulWidget 定义变量 然后
- 还得搞一个变量来存储这个变量
- 在对应的位置加载数据 setState
其实我们这里有一个方法
import "package:flutter/material.dart";
import 'package:project03/core/model/category_model.dart';
import 'package:project03/core/services/json_parse.dart';import "../../../core/extension/int_extension.dart";class HYHomeContent extends StatefulWidget {@override_HYHomeContentState createState() => _HYHomeContentState();
}class _HYHomeContentState extends State<HYHomeContent> {List<HYCategoryModel> _categories = [];@overridevoid initState() {// TODO: implement initStatesuper.initState();// 加载数据JsonParse.getCategoryData().then((res) {setState(() {_categories = res;})});}@overrideWidget build(BuildContext context) {return GridView.builder(padding: EdgeInsets.all(20.px),itemCount: _categories.length,gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2,crossAxisSpacing: 20.px,mainAxisSpacing: 20.px,childAspectRatio: 1.5),itemBuilder: (ctx, index) {return HYHomeCategoryItem(_categories[index]);});}
}
我们这里 直接不要initiStatus里面的东西
import "package:flutter/material.dart";
import 'package:project03/core/model/category_model.dart';
import 'package:project03/core/services/json_parse.dart';import "../../../core/extension/int_extension.dart";
import "home_catagory_item.dart";class HYHomeContent extends StatelessWidget {@overrideWidget build(BuildContext context) {return GridView.builder(padding: EdgeInsets.all(20.px),itemCount: _categories.length,gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2,crossAxisSpacing: 20.px,mainAxisSpacing: 20.px,childAspectRatio: 1.5),itemBuilder: (ctx, index) {return HYHomeCategoryItem(_categories[index]);});}
}
那我们的数据要怎么样才能异步加载呢
FutureBuilder
这个时候 你需要 换一个新的Widget FutureBuilder
Future 这个因该比较熟悉了吧 这个Future的意思就是 未来的意思 我们这里就是想用 未来的数据 来构建这个项目
这个东西一般有两个参数 future 和 builder
- future 是放异步请求的东西 并且它必须返回一个Future
- builder就是一个展示的东西
class HYHomeContent extends StatelessWidget {@overrideWidget build(BuildContext context) {return FutureBuilder(future: ,builder: ,)}
}
因为我们的JsonParse解析文件返回的就是一个 Future所以我们也不需要专门处理
class HYHomeContent extends StatelessWidget {@overrideWidget build(BuildContext context) {return FutureBuilder(future: JsonParse.getCategoryData(),builder: ,)}
}
然后我们看到builder 它有两个参数 context 这个很熟悉 就是上下文 然后还又 snapshot快照
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gXkQihLK-1589428979473)(D19B5F928B8646E2811E64CFC95F148A)]
class HYHomeContent extends StatelessWidget {@overrideWidget build(BuildContext context) {return FutureBuilder(future: JsonParse.getCategoryData(),builder: (ctx, snapshot) {return GridView.builder(padding: EdgeInsets.all(20.px),itemCount: _categories.length,gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2,crossAxisSpacing: 20.px,mainAxisSpacing: 20.px,childAspectRatio: 1.5),itemBuilder: (ctx, index) {return HYHomeCategoryItem(_categories[index]);});},)}
}
返回值就是展示的内容
但是我们还有一个问题就是我们没有数据的时候我们希望他是 一个转圈的图片
这里我们就可以使用snapshot快照了
if(snapshot.hasData)这个就是判断是否有返回数据 如果没有返回数据 我们就可以 显示我们的转圈图片
class HYHomeContent extends StatelessWidget {@overrideWidget build(BuildContext context) {return FutureBuilder(future: JsonParse.getCategoryData(),builder: (ctx, snapshot) {if(snapshot.hasData) {
// 这个就是判断是否有返回数据 如果没有返回数据 我们就可以 显示我们的转圈图片}return GridView.builder(padding: EdgeInsets.all(20.px),itemCount: _categories.length,gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2,crossAxisSpacing: 20.px,mainAxisSpacing: 20.px,childAspectRatio: 1.5),itemBuilder: (ctx, index) {return HYHomeCategoryItem(_categories[index]);});},);}
}
转圈的东西也是有Widget的 CircularProgressIndicator();
我们来看看这个东西长什么样
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KLLFRCwq-1589428979476)(19C0FDDF9B084F9BB9A6FB45FEA84F51)]
这样我们 就可以在没有数据的时候 展示圆圈了
class HYHomeContent extends StatelessWidget {@overrideWidget build(BuildContext context) {return FutureBuilder(future: JsonParse.getCategoryData(),builder: (ctx, snapshot) {if(snapshot.hasData) {
// 这个就是判断是否有返回数据 如果没有返回数据 我们就可以 显示我们的转圈图片return Center(child: CircularProgressIndicator());}return GridView.builder(padding: EdgeInsets.all(20.px),itemCount: _categories.length,gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2,crossAxisSpacing: 20.px,mainAxisSpacing: 20.px,childAspectRatio: 1.5),itemBuilder: (ctx, index) {return HYHomeCategoryItem(_categories[index]);});},);}
}
但是我们的数据在哪里呢
这个东西就在快照里面
import "package:flutter/material.dart";
import 'package:project03/core/model/category_model.dart';
import 'package:project03/core/services/json_parse.dart';import "../../../core/extension/int_extension.dart";
import "home_catagory_item.dart";class HYHomeContent extends StatelessWidget {@overrideWidget build(BuildContext context) {return FutureBuilder<List<HYCategoryModel>>(future: JsonParse.getCategoryData(),builder: (ctx, snapshot) {if(!snapshot.hasData) {
// 这个就是判断是否有返回数据 如果没有返回数据 我们就可以 显示我们的转圈图片return Center(child: CircularProgressIndicator());}
// 如果直接返回是一个dynamic的类型final categories = snapshot.data;return GridView.builder(padding: EdgeInsets.all(20.px),itemCount: categories.length,gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2,crossAxisSpacing: 20.px,mainAxisSpacing: 20.px,childAspectRatio: 1.5),itemBuilder: (ctx, index) {return HYHomeCategoryItem(categories[index]);});},);}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mLLzdGxJ-1589428979478)(DB9FDCE2C8014FA4848FCBB4C1C226E4)]
这里还有一个error的
可以展示请求出错的页面
import "package:flutter/material.dart";
import 'package:project03/core/model/category_model.dart';
import 'package:project03/core/services/json_parse.dart';import "../../../core/extension/int_extension.dart";
import "home_catagory_item.dart";class HYHomeContent extends StatelessWidget {@overrideWidget build(BuildContext context) {return FutureBuilder<List<HYCategoryModel>>(future: JsonParse.getCategoryData(),builder: (ctx, snapshot) {if(!snapshot.hasData)
// 这个就是判断是否有返回数据 如果没有返回数据 我们就可以 显示我们的转圈图片return Center(child: CircularProgressIndicator());if( snapshot.error ) return Center(child: Text("请求失败"));
// 如果直接返回是一个dynamic的类型final categories = snapshot.data;return GridView.builder(padding: EdgeInsets.all(20.px),itemCount: categories.length,gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2,crossAxisSpacing: 20.px,mainAxisSpacing: 20.px,childAspectRatio: 1.5),itemBuilder: (ctx, index) {return HYHomeCategoryItem(categories[index]);});},);}
}
但是还有一个问题就是 这个 是写在 build里面如果这个页面经常刷新的话 那么这个请求是会经常执行
每次构建的时候他都会重新请求一次的
同时注意这里 因为整个的 build是会重新构建了 但是这个HomeContent因该不是会老是重新构建的
还有一个问题 就是 这个东西(FutureBuilder)还有一个局限性
我们的数据非常多的时候 比如我么能做上拉加载的时候 这个时候需要记录 我们现在的页数
我们就不能用这个FutureBuilder 来做了
因为这个时候我们 需要 一个变量来记录我们的页面 所以在上拉记载更多的时候我们就不能使用 这个FutrueBuilder
点击跳转详情页
展示的数据 我们使用的meals.json文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hpHcoDDr-1589428979480)(4EC78243478E481FB0326618A6E8ACCE)]
我们这种数据的获取方式有两种
- 一种是放在本地 用加载json文件类似的方式来加载
- 还有一种是 使用网络请求的方式来加载数据
但是这里我们已经使用json了 所以这里想使用网络请求的方式 来实现这个操作
这个就是网络请求的地址
- 这个是catagory上的地址
http://123.207.32.32:8001/api/category
- meals的地址
http://123.207.32.32:8001/api/meals
虽然这个数据量不是很大 但是 我们还是希望能 使用网络请求
现在看看 这个页面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZhnmC6Bg-1589428979481)(7A2437711A5543719F1D69F06E0C5531)]
这里有很多的数据需要 共享 我们点到某一个页面
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3QNG4fPX-1589428979483)(62A080754BF34D4E8B1F9C19C1D46004)]
那如果我们每次进到一个页面里面的时候 它都进行一次加载一次
我们只需要将它放在一个共同的地方 我们只需要根据这个页面的一个特别性质 然后展示你想展示的东西
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Zf3jJBmt-1589428979485)(384965B0B0CB4CC1B3FB2E777E4BBF56)]
所以我们因该把它放在一个共同的地方
Provider 然后把它放到ViewModel
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P4q920Jk-1589428979487)(DE4841F96D204F41B5F9055D1F3978E2)]
首先第一步我们发送网络请求
直接把我们之前的网络请求拿过来
import 'package:dio/dio.dart';
import 'config.dart';class HYHttpRequest {static final BaseOptions baseOptions = BaseOptions(baseUrl: HttpConfig.baseURL, connectTimeout: HttpConfig.timeout);static final Dio dio = Dio(baseOptions);static Future<T> request<T>(String url, {String method = "get",Map<String, dynamic> params,Interceptor inter}) async {// 1.创建单独配置final options = Options(method: method);// 全局拦截器// 创建默认的全局拦截器Interceptor dInter = InterceptorsWrapper(onRequest: (options) {print("请求拦截");return options;},onResponse: (response) {print("响应拦截");return response;},onError: (err) {print("错误拦截");return err;});List<Interceptor> inters = [dInter];// 请求单独拦截器if (inter != null) {inters.add(inter);}// 统一添加到拦截器中dio.interceptors.addAll(inters);// 2.发送网络请求try {Response response = await dio.request(url, queryParameters: params, options: options);return response.data;} on DioError catch(e) {return Future.error(e);}}
}
这里还没有安装这个包 所以pub.dev 看看有没有新版本 没有好吧
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7YFfxQUa-1589428979489)(2B3CC1C78CD448C0A6FDE539E4E40814)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rv8v6t21-1589428979492)(8BC48239B2C4435586BB11299E135E00)]
同时改变配置文件
class HttpConfig {static const String baseURL = "http://123.207.32.32:8001/api";static const int timeout = 10000;
}
然后封装对meal的请求的文件 这个地方和 vue那边一样 面向我们的 HttpRequest来进行封装
import 'package:project03/core/services/http_request.dart';class HYMealRequest {static void getMealData( ) async {
// 1. 发送网络请求final url = "/meal";final result = await HYHttpRequest.request(url);// 2. json转modelprint(result);}
}
我们现在先对这个网络请求做一个测试
直接放到 main函数里面测试
import "package:flutter/material.dart";
import 'package:project03/core/router/router.dart';
import 'package:project03/core/services/meal_request.dart';
import 'package:project03/ui/shared/app_theme.dart';
import 'package:project03/ui/shared/size_fit.dart';void main() {HYMealRequest.getMealData();runApp(MyApp());
}
...
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jdfAKyyL-1589428979494)(898756615E9F4FEC82A84365F8AF27FA)]
这里还是能拿到这个类型的 这个拿到的是一个对象所以就是 map然后里面有一个meal里面是数组 装了很多项东西
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TMmDGQde-1589428979496)(A66ED994242F41B5947972A369ABAAD5)]
我们就需要去拿十项数据中转化成一个一个的模型
我们是讲过 如将将这中json转换成模型
之前给过很多的方案 比如之前的这个网站
当数据比较多的时候 那个第一个网站转换的数据是有一点问题的
当你的数据比较复杂的时候这个转换就会有很多的问题(我第二个介绍的网站 没有这个问题)
但是如果你仔细去看它的错误的话 这些错误也不是不能解决
它这里生成了一个类 叫List 但是你怎么能叫 List呢
因为我们的这个List里面有有一个List 本来 dart的核心里面就有一个类叫做List
它主要也就是这里有问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nPTmSZef-1589428979499)(8E14142A532949ED824C59CC0FA81482)]
所以这里我们就可以把它改了 然后之后所有用到它的地方全部改了 就可以了
但是现在这种情况一个一个去改也会很麻烦
所以这里我们去 另外的一个网站 它会更好一点
https://app.quicktype.io/ 这个就是之前建议的网站 现在我们来使用
而且这个网站不仅可以转换 dart还可以转化 swift 转化 它就能基本没有错误的转化 很复杂的代码‘’
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IMUJaK4m-1589428979500)(8B96DBEDECA345E3966D8DE56579CCB7)]
这个可以转化非常复杂的代码
然后这里我们就有模型了
// To parse this JSON data, do
//
// final hyMealModel = hyMealModelFromJson(jsonString);import 'dart:convert';
HYMealModel hyMealModelFromJson(String str) => HYMealModel.fromJson(json.decode(str));String hyMealModelToJson(HYMealModel data) => json.encode(data.toJson());class HYMealModel {String id;List<String> categories;String title;int affordability;int complexity;String imageUrl;int duration;List<String> ingredients;List<String> steps;bool isGlutenFree;bool isVegan;bool isVegetarian;bool isLactoseFree;HYMealModel({this.id,this.categories,this.title,this.affordability,this.complexity,this.imageUrl,this.duration,this.ingredients,this.steps,this.isGlutenFree,this.isVegan,this.isVegetarian,this.isLactoseFree,});factory HYMealModel.fromJson(Map<String, dynamic> json) => HYMealModel(id: json["id"],categories: List<String>.from(json["categories"].map((x) => x)),title: json["title"],affordability: json["affordability"],complexity: json["complexity"],imageUrl: json["imageUrl"],duration: json["duration"],ingredients: List<String>.from(json["ingredients"].map((x) => x)),steps: List<String>.from(json["steps"].map((x) => x)),isGlutenFree: json["isGlutenFree"],isVegan: json["isVegan"],isVegetarian: json["isVegetarian"],isLactoseFree: json["isLactoseFree"],);Map<String, dynamic> toJson() => {"id": id,"categories": List<dynamic>.from(categories.map((x) => x)),"title": title,"affordability": affordability,"complexity": complexity,"imageUrl": imageUrl,"duration": duration,"ingredients": List<dynamic>.from(ingredients.map((x) => x)),"steps": List<dynamic>.from(steps.map((x) => x)),"isGlutenFree": isGlutenFree,"isVegan": isVegan,"isVegetarian": isVegetarian,"isLactoseFree": isLactoseFree,};
}
然后我们将 请求的数据转化成模型 打印
import 'package:project03/core/model/meal_model.dart';
import 'package:project03/core/services/http_request.dart';class HYMealRequest {static Future<List<HYMealModel>> getMealData( ) async {
// 1. 发送网络请求final url = "/meal";final result = await HYHttpRequest.request(url);// 2. json转modelfinal mealArray = result["meal"];List<HYMealModel> meals = [];for(var json in mealArray) {meals.add(HYMealModel.fromJson(json));}return meals;}
}
然后main中打印来看看
void main() {HYMealRequest.getMealData().then((res) {print(res);});runApp(MyApp());
确实可以看到数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c2PMVwhF-1589428979502)(DDF278A80F78458CB44AEE9B2319F5D7)]
使用Provider来共享数据
- 1.创建ViewModel并进行初始化
然后我们希望能将这个数据放到 ViewModel 中来做一个共享
所以这个东西还是非常重要的 如何将数据 放到 ViewModel
我们一般用 Provider来实现 这个将数据放到 ViewModel中 来做数据共享
可以使用第三方的 但是第三方 有可能就不做数据维护了 你不能保证这个东西
这个是官方的 除非 flutter没了 不然它是不会不维护的 如果flutter不存在那你找第三放的东西 当然也是不行的
虽然这个东西是官方的 但是它也还是 需要下载的
我们找到 pub.dev
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DFoaTXYP-1589428979504)(C9947EC1EA6F474381BA30CA5C2C5B51)]
但是这里 flutter package get 的时候报错了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ckA3ZkHm-1589428979506)(7473499EC9FB4CF49634A1BEBE8D52BC)]
这里说的是 flutter SDK的版本是1.12 但是这个 4.1.1的版本需要的是 1.15的实验版本 所以 我们 这里用原来的 4.0.4 就没有报错了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZJI3n5FS-1589428979509)(637231E7CD0E4F8DA26CFE4608336716)]
我们现在 就可以使用provider共享的数据
共享的数据因该放在哪里呢
我们 之前学习过 Provider的东西有 这几个是比较重要的
- Provider
- ViewModel因为这里是用的 MVVM的模型 所以用这个名字来做为共享数据的模型的名称 因该是比较好的
- Provider
- Consumer 还有在某些情况性能更高的 Selector
我们首先在 viewmodel 文件夹里面 添加一个新文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IFrVXawc-1589428979511)(04DB03DA2ED74D14B65D88FC1C982713)]
我们这个文件结构就比较好 我们找文件的时候就可以 比较快的找到 对应的文件
所以我们在些项目的时候 结构就可以用这个文件结构
import 'package:flutter/cupertino.dart';
import 'package:project03/core/model/meal_model.dart';
import 'package:project03/core/services/meal_request.dart';class MealViewModel extends ChangeNotifier {
// 这里不初始化的就是nullList<HYMealModel> _meals = [];// 一旦你创建出对象 你就发送网络请求MealViewModel() {HYMealRequest.getMealData().then((res) {_meals = res;});}
}
我们希望它能尽早的初始化
所以我们把 这个请求直接就放在 这个 构造方法里面
同时 对这个List赋值的时候要 通知所有依赖的 文件 对这个 东西进行刷新
MealViewModel() {HYMealRequest.getMealData().then((res) {_meals = res;notifyListeners();});}
但是我们这里 是一个私有的变量 别人是访问不了这个值的
所以按我们的做法 写一个 meals 的get
但是我们是不希望使用的时候来直接修改这个 变量的 所以不写set
import 'package:flutter/cupertino.dart';
import 'package:project03/core/model/meal_model.dart';
import 'package:project03/core/services/meal_request.dart';class MealViewModel extends ChangeNotifier {
// 这里不初始化的就是nullList<HYMealModel> _meals = [];List<HYMealModel> get meals {return _meals;}// 一旦你创建出对象 你就发送网络请求MealViewModel() {HYMealRequest.getMealData().then((res) {_meals = res;notifyListeners();});}
}
- 1.创建ViewModel并进行初始化
- 2.使用Provider
在 根目录的Main.dart中使用这个东西
这里为什么是一个create 传过来一个函数 因为这里 是对数据做一个懒加载
void main() {
// Provider -> ViewModel / Provider / Consumer(Selector)runApp(ChangeNotifierProvider(create: (ctx) => HYMealViewModel(),child: MyApp(),));
}
所以这里 首页不会影响加载速度的
- 1.创建ViewModel并进行初始化
- 2.使用Provider
- 3.使用共享的数据
现在我们就可以使用 这个共享的数据
什么地方会使用这个共享的数据呢
显然我们会在 详情页面 取出这个数据并展示它
这里就要谈到这个category和 meals这两个数据的关系了
meal里面的东西 meals里面
我们的每一个meal对象里面都有一个属性 category 这个东西代表 它的分类
然后再category里面id就是对应的分类的编号
category.json
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ibfVLCsA-1589428979516)(F776F57095504EBF8A709A421F63E5BA)]
meals
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pfhtsh0T-1589428979517)(00D120D908794B6A82FA805C60B6FEC7)]
meal的每一个元素就是一个 食物的做法
那我们怎么做一个区分呢
我们点击的跳转的时候 我们可以传 过来对应点击的 category的id然后对 meal里面的东西做 过滤
然后再把这个列表在这里展示
所以下面我们要 做要给跳转然后把category id传过来
那我们首先就要先建立一个新的页面
import "package:flutter/material.dart";class HYMealScreen extends StatelessWidget {@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("TODO"),),body: Center(child: Text("meal List"),),);}
}
然后就是路由的跳转 我们一般都会使用 命名路由 所以
配置路由
import "package:flutter/material.dart";class HYMealScreen extends StatelessWidget {static const String routeName = "/meal";@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("TODO"),),body: Center(child: Text("meal List"),),);}
}
配置命名路由
import 'package:flutter/material.dart';
import 'package:project03/ui/pages/main/main.dart';
import 'package:project03/ui/pages/meal/meal.dart';class HYRouter {static final String initialRoute = HYMainScreen.routeName;static final Map<String, WidgetBuilder> routes ={HYMainScreen.routeName: (ctx) => HYMainScreen(),HYMealScreen.routeName: (ctx) => HYMealScreen(),};// 自己扩展static final RouteFactory generateRoute = (settings) {return null;};static final RouteFactory unknownRoute = (setting) {};
}
然后我们的跳转因该是在哪里使用的呢
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fPXTnpFE-1589428979519)(62A530720A674CFF8CEC931F4FBFE8DE)]
home_category_iem里面来做手势然后跳转
Container本身是没有onTag的所以这里给他 一个 GestureDetector
因为这个category对象本来就是 分类的意思 它里面是有个 属性id的 所以我们只要把这个东西传过去就可以了
至于为甚不只传 id因为这个是面向的对象的变成 所以用这个方式来做
import 'package:flutter/material.dart';
import 'package:project03/core/model/category_model.dart';
import 'package:project03/ui/pages/meal/meal.dart';import "../../../core/extension/int_extension.dart";class HYHomeCategoryItem extends StatelessWidget {final HYCategoryModel _category;HYHomeCategoryItem(this._category);@overrideWidget build(BuildContext context) {return GestureDetector(child: Container(decoration: BoxDecoration(color: _category.cColor,borderRadius: BorderRadius.circular(12.px),gradient: LinearGradient(colors: [_category.cColor.withOpacity(.5),_category.cColor])),alignment: Alignment.center,child: Text(_category.title,style: Theme.of(context).textTheme.display2.copyWith(fontWeight: FontWeight.bold,))),onTap: () {print(_category);Navigator.of(context).pushNamed(HYMealScreen.routeName, arguments: _category);},);}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VbpJhiuu-1589428979523)(99220253076B4E48B2474D2CDF2A5CCC)]
然后我们就可以在meal里面获取参数
ui/meal/meal.dart
import "package:flutter/material.dart";
import 'package:project03/core/model/category_model.dart';class HYMealScreen extends StatelessWidget {static const String routeName = "/meal";@overrideWidget build(BuildContext context) {
// 获取数据final category = ModalRoute.of(context).settings.arguments as HYCategoryModel;return Scaffold(appBar: AppBar(title: Text(category.title),),body: Center(child: Text("meal List"),),);}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3dyUqL6X-1589428979528)(539A959F612F448692DC68FBF7B43E30)]
同时我们这里 也是没有 发送网络请求的
以为这个东西它是一个懒加载 只有在用到的时候才会加载这个数据 如果加载 至少 因该会有请求拦截 的打印
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tFSJ0irr-1589428979530)(AABE73F1709B44008BE797B75FC68BA8)]
这页面已经跳转过来了
meal页面的展示
然后我们就是要这 meal页面展示这个页面了
这个页面需要的展示 内容
我们已经拿到了 category id 但是meal里面的数据还没有拿到 所以
还需要用Consumer 或者用Selector来展示
我们用一个新的文件来展示这个 Content 当然我们要用一个新的文件
import "package:flutter/material.dart";class HYMealContent extends StatelessWidget {@overrideWidget build(BuildContext context) {return Container();}
}
但是同时我们还需要将这个 category放进来 当然你也可以再获取一次
final category = ModalRoute.of(context).settings.arguments as HYCategoryModel;
但是为什么我们再首页 也能拿到这个东西
就是因为这个东西是向上找的 因为这个ModalRoute.of 它永远是找的顶层的 这个路由
当你跳转的时候 路由它是以栈的方式来管理的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zAUKGZ9U-1589428979536)(8522CBCDF7B448ECA6106ED6E42C4994)]
而它再找的时候 它永远是找的顶层的这个路由
所以我们可以直接在 这个里面这样那 因为只要你还在这个路由里面你就可以拿到 这个值 和 你在哪里是一样的
import "package:flutter/material.dart";import "package:project03/core/model/category_model.dart";
import 'package:project03/core/model/meal_model.dart';
import 'package:provider/provider.dart';class HYMealContent extends StatelessWidget {@overrideWidget build(BuildContext context) {final category = ModalRoute.of(context).settings.arguments as HYCategoryModel;return Consumer<List<HYMealModel>>(builder: (ctx, mealVM, child) {},);}
}
这样我们就可以获得这个 ViewModel的对象
但是注意啊我们这里 不是获取到 <List>
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ViWvQ8Gt-1589428979546)(C2D435AD4B5346F48B0A4725655876FA)]
我们是拿到的是ViewModel对象 然后展示一下
import "package:flutter/material.dart";import "package:project03/core/model/category_model.dart";
import 'package:project03/core/model/meal_model.dart';
import 'package:project03/core/viewmodel/meal_view_model.dart';
import 'package:provider/provider.dart';class HYMealContent extends StatelessWidget {@overrideWidget build(BuildContext context) {final category = ModalRoute.of(context).settings.arguments as HYCategoryModel;return Consumer<HYMealViewModel>(builder: (ctx, mealVM, child) {return Text("${mealVM.meals.length}");},);}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2zNOjRql-1589428979551)(2FCD4278277D4E3C872A1B20B560CD9A)]
说明我们确实可以拿到这个数据
但是这十个数据 都要展示吗
并不是 我们需要根据这个category来对这个meals做一个过滤
我们可以用 where 来实现 对数组的过滤的操作 像js的高阶函数filter
这里的where就等价于 filter当它的返回值为 true的时候 就将这个值添加到 新建立的数组里面
import "package:flutter/material.dart";import "package:project03/core/model/category_model.dart";
import 'package:project03/core/model/meal_model.dart';
import 'package:project03/core/viewmodel/meal_view_model.dart';
import 'package:provider/provider.dart';class HYMealContent extends StatelessWidget {@overrideWidget build(BuildContext context) {final category = ModalRoute.of(context).settings.arguments as HYCategoryModel;return Consumer<HYMealViewModel>(builder: (ctx, mealVM, child) {final meals = mealVM.meals.where((meal) => meal.categories.contains(category.id)).toList();return ListView.builder(itemCount: meals.length,itemBuilder: (ctx, index) {return ListTile(title: Text(meals[index].title));});},);}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iaj81VbX-1589428979554)(67EC8299DC55430199A50965E4C2A42E)]
说明我们这里这样拿的话是可以的
但是我们是不希望使用Consumer的
- 1.因为只要我们使用了Consumer 一旦我们里面的数据发生改变 里面的东西都是要重新构建的
- 2.Selector里面 本来就有对数据进行过滤的代码 来给它做一个过滤
在这里用这个Selector真的是非常的好用
这个Selector两个泛型 意思是 将前面的转化成后面的类型 之前因为没有必要转化 所以这两个泛型都是一样的
这样的话就省了很多的转化的功夫
Selector<HYMealViewModel, List<HYMealModel>>
它有三个必传的参数
return Selector<HYMealViewModel, List<HYMealModel>>(selector: ,shouldRebuild: ,builder: ,);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zsamHequ-1589428979561)(DFEC272255F64DF0B29B09BC0BE1A892)]
在 selector里面 传 context 和 第一个类型的变量 然后
return Selector<HYMealViewModel, List<HYMealModel>>(selector: (ctx, mealVM) => mealVM.meals.where((meal) => meal.categories.contains(category.id)).toList(),shouldRebuild: ,builder: ,);
同样我们用where来过滤这样就少了很多步骤
这样我们就把数据做了一个转化
这个shouldRebuild 是用来是否重新创建 builder
shouldRebuild: (prev, next) => true,
这个东西会把 前面的变量和后面的参数 来对比 如果返回 true就是一定刷新页面
如果返回是false就是一定不刷新 判断一下就可以提升性能
那我们这里是需要让它从新执行 还是不 重新执行 呢 这个看情况
那么我们要不要重新刷新就要看看 列表里面的数据是否相同 来判断 是否重新刷新
如果两个里面的数据 相等就重新刷星 不相等就重新刷新
这里就有一个问题 我们现在有两个List 判断它里面的东西
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KuRuxMuM-1589428979563)(1CC66B2ADBA74B9AAAFD8290984E6F4E)]
- 1.可以用遍历 思路比较简单 代码
- 2.api有一个专门可以比较 两个List是否相同
这个东西必须要导入这个东西
import "package:collection/collection.dart";
return Selector<HYMealViewModel, List<HYMealModel>>(selector: (ctx, mealVM) => mealVM.meals.where((meal) => meal.categories.contains(category.id)).toList(),shouldRebuild: (prev, next) => ListEquality().equals(prev, next),builder: ,);
这样我们就可以比较他们是否相等了
当他们不相等的时候发生刷新
return Selector<HYMealViewModel, List<HYMealModel>>(selector: (ctx, mealVM) => mealVM.meals.where((meal) => meal.categories.contains(category.id)).toList(),shouldRebuild: (prev, next) => !ListEquality().equals(prev, next),builder: ,);
builder就是一样的 不过我们就是直接取到的mealVM的值
import "package:flutter/material.dart";import "package:project03/core/model/category_model.dart";
import 'package:project03/core/model/meal_model.dart';
import 'package:project03/core/viewmodel/meal_view_model.dart';
import 'package:provider/provider.dart';
import "package:collection/collection.dart";class HYMealContent extends StatelessWidget {@overrideWidget build(BuildContext context) {final category = ModalRoute.of(context).settings.arguments as HYCategoryModel;return Selector<HYMealViewModel, List<HYMealModel>>(selector: (ctx, mealVM) => mealVM.meals.where((meal) => meal.categories.contains(category.id)).toList(),shouldRebuild: (prev, next) => !ListEquality().equals(prev, next),builder: (ctx, meals, child) {return ListView.builder(itemCount: meals.length,itemBuilder: (ctx, index) {return ListTile(title: Text(meals[index].title),);});},);}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1xg9v1xx-1589428979566)(831660AA57CF41D69CBAE0ADF82C04B5)]
但是这里我们是希望是这个效果的 所以我们肯定就不能使用ListTile的组件来完成这个 Widget
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FCz8gYLi-1589428979574)(D8E7568842554669BE159334D31D6BC2)]
所以这里我们就要将这个东西单独的封装成一个Widget
同时我们将他封装成Widget很多的其他的地方都可以使用这个
然后我们在Widget中创建这个widgets
widgets/meal_item
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ieHG6Wi1-1589428979576)(602C50E639EE458C861F46B3B6C4008E)]
虽然我们创建的是StatelessWidget 但是它需要收藏的功能
这个一会再说
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SVjoYldV-1589428979582)(7FC32959112C48EC85439954546C529C)]
现在我们回到meal_content 里面
ui->page->meal->meal.dart
return Selector<HYMealViewModel, List<HYMealModel>>(selector: (ctx, mealVM) => mealVM.meals.where((meal) => meal.categories.contains(category.id)).toList(),shouldRebuild: (prev, next) => !ListEquality().equals(prev, next),builder: (ctx, meals, child) {return ListView.builder(itemCount: meals.length,itemBuilder: (ctx, index) {return HYMealItem(meals[index]);});},);
然后我们就考虑这个东西要怎么才能将 像老师的那个app一样展示的一样
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RSdxn8Kp-1589428979584)(7FC32959112C48EC85439954546C529C)]
我们这里用的是一个 Card来展示的 整个最外面的东西就是一个Card
首先它是先垂直方向的布局 所以我们是
Card(child: Column(children: [StackRow(children: )]))
所以最后这个代码因该这样传
这些样式的代码 只需要记住一个大概 然后倒是时候就去看源码
import "package:flutter/material.dart";
import 'package:project03/core/model/meal_model.dart';import "../../core/extension/int_extension.dart";class HYMealItem extends StatelessWidget {final HYMealModel _meal;HYMealItem(this._meal);@overrideWidget build(BuildContext context) {return Card(
// 它这里是有外边距的所以我们这里设置marginmargin: EdgeInsets.all(10.px),
// 它这里的阴影是这样的 这个值越大它明显elevation: 5,
// 我们这里边框是用这个BoxBorder来设置的
// 但是如果你想做圆角的话我们最好使用 * [RoundedRectangleBorder] 矩形圆角
// 这里不是传的值而是传的一个类 它的类型是一个抽象父类 所以我们要找它的实现类 这里因该这样传shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.px)),);}
}
现在我们 已经把最外面的这个大的Card已经设计完了
我们来设计里面的 之前已经说过了它的结构 所以这里就直接写
同样全部写在里面不太好 而这里最好要内聚一点因为其他的地方 不需要使用这个东西
所以将它作为方法返回
import "package:flutter/material.dart";
import 'package:project03/core/model/meal_model.dart';import "../../core/extension/int_extension.dart";class HYMealItem extends StatelessWidget {final HYMealModel _meal;HYMealItem(this._meal);@overrideWidget build(BuildContext context) {return Card(
// 它这里是有外边距的所以我们这里设置marginmargin: EdgeInsets.all(10.px),
// 它这里的阴影是这样的 这个值越大它明显elevation: 5,
// 我们这里边框是用这个BoxBorder来设置的
// 但是如果你想做圆角的话我们最好使用 * [RoundedRectangleBorder] 矩形圆角
// 这里不是传的值而是传的一个类 它的类型是一个抽象父类 所以我们要找它的实现类 这里因该这样传shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12.px)),child: Column(children: <Widget>[buildBasicInfo(),],),);}Widget buildBasicInfo() {return Stack()}Widget buildOperationInfo() {}}
BasicWidget的创建
Widget buildBasicInfo() {return Stack(children: <Widget>[
// 然后这张图是非常的大的 所以不能直接放在上面Image.network(_meal.imageUrl, width: double.infinity, height: 250.px, fit: BoxFit.cover,)],);}
但是有一个问题就是图片是没有圆角的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IfSON8p8-1589428979588)(233734DE9BA2436BBA2F3FB9C30C50D5)]
而且它的上面是没有 圆角的下面是矩形的样子
这里我们就可以使用 裁剪来实现这个目标
同样之前我们 做豆瓣的那个项目是 使用类似的这个 来裁减出 圆形的图片的
我们这里最好 不要用cirular因为这个东西是四个边都裁剪
Widget buildBasicInfo() {return Stack(children: <Widget>[
// 然后这张图是非常的大的 所以不能直接放在上面ClipRRect(
// 但是这里我们不用 circular 因为这个东西是四个边都裁剪
// 所以这里又是一个新的apiborderRadius: BorderRadius.only(topLeft: Radius.circular(8.px),topRight: Radius.circular(8.px),),child: Image.network(_meal.imageUrl, width: double.infinity, height: 250.px, fit: BoxFit.cover,))],);}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xdJg0aO9-1589428979593)(78752EBA94544056923067CA2F271E05)]
那么我们现在来接着做
还有一个展示基本信息的
return Stack(children: <Widget>[
// 然后这张图是非常的大的 所以不能直接放在上面ClipRRect(
// 但是这里我们不用 circular 因为这个东西是四个边都裁剪
// 所以这里又是一个新的apiborderRadius: BorderRadius.only(topLeft: Radius.circular(cardRadius),topRight: Radius.circular(cardRadius),),child: Image.network(_meal.imageUrl, width: double.infinity, height: 250.px, fit: BoxFit.cover,)),Positioned(right: 10.px,bottom: 10.px,child: Container(width: 300.px,padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),decoration: BoxDecoration(
// 我们这里需要半透明 所以这里就用 black54borderRadius: BorderRadius.circular(6.px),color: Colors.black54),
// 这里要使用context还得把这个改了
// 但是这里我们不想使用原来的 黑色child: Text(_meal.title, style: Theme.of(context).textTheme.display3.copyWith(color: Colors.white)),),)],);}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y99J7Ya7-1589428979596)(541B9F14BCDF40BD9B739D22630416F7)]
这样我们就能看到这个东西了
然后就是下面的这个
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aG5mk6lM-1589428979601)(E1A82BAA922C428188D5F8002B102A05)]
它的就是一个Row加三个这个 东西
但是这三个东西 长的差不多 如果你想项目做的大的话 这个小结构可能会在很多地方使用
所以可以给他封装一个Widget
widgets->HYOperationItem
import "package:flutter/material.dart";class HYOperationItem extends StatelessWidget {
// 这个传的东西不一定是icon但是我们可以用这个 icon左边变量final Widget _icon;final String _title;HYOperationItem(this._icon, this._title);@overrideWidget build(BuildContext context) {return Row(children: <Widget>[_icon,
// 这里默认情况最小就是 11所以我们也不要用Theme.of 去取样式了Text(_title)],);}
}
使用这个 widgets -> meal_item.dart
Widget buildOperationInfo() {return Row(children: <Widget>[
// 这个时间是HYOperationItem(Icon(Icons.schedule), "${_meal.duration} 分钟"),HYOperationItem(Icon(Icons.restaurant), "${_meal.complexity} 分钟"),HYOperationItem(Icon(Icons.favorite), "未收藏"),],);}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r1YcB6rb-1589428979606)(7621DCF009004B05A840B6E5BACDD1FE)]
这里难看有几个原因 其中一个就是挨的太近所以 还有一个就是排布难看
Widget buildOperationInfo() {return Padding(
// 这里不能直接使用 const EdgeInsets.all(16.px) 因为如果用const创建对象的话 我们这里 就必须是常量 但是px已经是计算以后的结果 所以不能使用padding: EdgeInsets.all(16.px),child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround,children: <Widget>[
// 这个时间是HYOperationItem(Icon(Icons.schedule), "${_meal.duration} 分钟"),HYOperationItem(Icon(Icons.restaurant), "${_meal.complexity} 分钟"),HYOperationItem(Icon(Icons.favorite), "未收藏"),],),);}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HWYghHcF-1589428979609)(724686FF5E2D437D82CC9FD2650774C0)]
还有一个问题提就是它们 组件内部太近 加一个SizedBox就可以了
import "package:flutter/material.dart";import "../../core/extension/int_extension.dart";class HYOperationItem extends StatelessWidget {
// 这个传的东西不一定是icon但是我们可以用这个 icon左边变量final Widget _icon;final String _title;HYOperationItem(this._icon, this._title);@overrideWidget build(BuildContext context) {return Row(children: <Widget>[_icon,
// 这里默认情况最小就是 11所以我们也不要用Theme.of 去取样式了SizedBox(width: 3.px),Text(_title),],);}
}
当然我们可以在前面搞一个 Switch来判断 然后生成对应的 Widget
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-um2Mq2wy-1589428979612)(16FEF5FE48E74D968E46CAC572ACFE01)]
但是这里 写的就有两个问题
- 一个是 代码量比较大
- 还有一个 如果其他的地方也要使用这个值的话 那就也要这样转化 代码复用率低
所以我们直接取改模型
我们到meal_model里面去搞一个新的变量
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8mTOzFnF-1589428979620)(0CC71A219D034FC18BF24FD056234130)]
初始化它肯定就是在fromJson里面进行初始化的
你当然可以在里面进行 if 或者 switch
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MaE0CkvM-1589428979625)(098A7791D6F4459B894C7B18ACDCDA5F)]
但是 这里我们可以不用这个来做这个判断
但是我们不想这样做
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xMLt0PDC-1589428979632)(606FAA000B6F4B80B8CB08B1E81322EE)]
我们可以去模型的最前面生成这样一个数组 以便后面使用
但是fromJson初始化也是使用的构造方法进行初始化的 所以我们需要给构造方法也添加参数
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zJ34eep6-1589428979634)(D8089E94B74F4D258B81B4F9C3D52C37)]
然后我们可以去初始化了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s8rs5Gay-1589428979644)(C333604ABF554D53BE4A029E47266B75)]
但是这里有一个问题 因为在这里 complexity还没有初始化 所以我们是没有办法使用 complexity 来使用这个值 所以
虽然不能这样 但是我们可以直接去 json里面取 因为json里面是有数据的
complexStr: complex[json["complexity"]],
当然这里我们是根据服务器返回的值是 123这样的数字才能这样设置这个东西
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oJ6brxpm-1589428979648)(A644F83DA6B04DB6BDE0CBF0FFD1ED15)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MiiBlGkh-1589428979650)(CCE4B8CBBE874B84BCF64714EC38C72B)]
还有一个问题就是我们的这个
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4Nt1dYbh-1589428979652)(845188A413D1482B8282956079042F2E)]
这里的complexs有一个警告这个是说你的拼写有问题所以 它在这里 报错
因为只有拼写是complex 的单词 并没有拼写是 complexs的单词 这里如果你确定你的拼写没有错的时候 你就可以
alt + enter 来 取消这个提示 保存这个单词到项目字典里
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tLQdI0AW-1589428979658)(AC0F01C4A08D4228AFBBAB75842036D2)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xEiZlxfU-1589428979664)(03B58BBCBD514E188CB9803A6D7EAD75)]
more
// To parse this JSON data, do
//
// final hyMealModel = hyMealModelFromJson(jsonString);import 'dart:convert';
HYMealModel hyMealModelFromJson(String str) => HYMealModel.fromJson(json.decode(str));String hyMealModelToJson(HYMealModel data) => json.encode(data.toJson());List<String> complexs = ["简单", "普通", "困难"];class HYMealModel {String id;List<String> categories;String title;int affordability;int complexity;String complexStr;String imageUrl;int duration;List<String> ingredients;List<String> steps;bool isGlutenFree;bool isVegan;bool isVegetarian;bool isLactoseFree;HYMealModel({this.id,this.categories,this.title,this.affordability,this.complexity,this.complexStr,this.imageUrl,this.duration,this.ingredients,this.steps,this.isGlutenFree,this.isVegan,this.isVegetarian,this.isLactoseFree,});factory HYMealModel.fromJson(Map<String, dynamic> json) => HYMealModel(id: json["id"],categories: List<String>.from(json["categories"].map((x) => x)),title: json["title"],affordability: json["affordability"],complexity: json["complexity"],complexStr: complexs[json["complexity"]],imageUrl: json["imageUrl"],duration: json["duration"],ingredients: List<String>.from(json["ingredients"].map((x) => x)),steps: List<String>.from(json["steps"].map((x) => x)),isGlutenFree: json["isGlutenFree"],isVegan: json["isVegan"],isVegetarian: json["isVegetarian"],isLactoseFree: json["isLactoseFree"],);
然后使用
import "package:flutter/material.dart";
import 'package:project03/core/model/meal_model.dart';
import 'package:project03/ui/widgets/operation_item.dart';import "../../core/extension/int_extension.dart";class HYMealItem extends StatelessWidget {final HYMealModel _meal;final double cardRadius = 8.px;HYMealItem(this._meal);@overrideWidget build(BuildContext context) {return Card(
// 它这里是有外边距的所以我们这里设置marginmargin: EdgeInsets.all(10.px),
// 它这里的阴影是这样的 这个值越大它明显elevation: 5,
// 我们这里边框是用这个BoxBorder来设置的
// 但是如果你想做圆角的话我们最好使用 * [RoundedRectangleBorder] 矩形圆角
// 这里不是传的值而是传的一个类 它的类型是一个抽象父类 所以我们要找它的实现类 这里因该这样传shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(cardRadius)),child: Column(children: <Widget>[buildBasicInfo(context),buildOperationInfo()],),);}Widget buildBasicInfo(BuildContext context) {return Stack(children: <Widget>[
// 然后这张图是非常的大的 所以不能直接放在上面ClipRRect(
// 但是这里我们不用 circular 因为这个东西是四个边都裁剪
// 所以这里又是一个新的apiborderRadius: BorderRadius.only(topLeft: Radius.circular(cardRadius),topRight: Radius.circular(cardRadius),),child: Image.network(_meal.imageUrl, width: double.infinity, height: 250.px, fit: BoxFit.cover,)),Positioned(right: 10.px,bottom: 10.px,child: Container(width: 300.px,padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),decoration: BoxDecoration(
// 我们这里需要半透明 所以这里就用 black54borderRadius: BorderRadius.circular(6.px),color: Colors.black54),
// 这里要使用context还得把这个改了
// 但是这里我们不想使用原来的 黑色child: Text(_meal.title, style: Theme.of(context).textTheme.display3.copyWith(color: Colors.white)),),)],);}Widget buildOperationInfo() {return Padding(
// 这里不能直接使用 const EdgeInsets.all(16.px) 因为如果用const创建对象的话 我们这里 就必须是常量 但是px已经是计算以后的结果 所以不能使用padding: EdgeInsets.all(16.px),child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround,children: <Widget>[
// 这个时间是HYOperationItem(Icon(Icons.schedule), "${_meal.duration} 分钟"),HYOperationItem(Icon(Icons.restaurant), "${_meal.complexStr}"),HYOperationItem(Icon(Icons.favorite), "未收藏"),],),);}}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NScGmXaY-1589428979667)(5553438D03004B2385988BF3D081F9F6)]
还有注意 我们如果是 hot-reload 数据没有加载 这里就需要hot-restart
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cm0duoOB-1589428979670)(D9F5213163AD48E8B0AEFB27890BD1FB)]
就会这样 显示
这里就做一个页面的跳转
首先肯定是 先建立一个 文件
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lZH4bCDT-1589428979674)(D080B03C622B490BBA446289C2687CC5)]
新建页面配置路由
import "package:flutter/material.dart";class HYDetailScreen extends StatelessWidget {static final String routeName = "/detail";@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("TODO"),),body: Center(child: Text("TODO"),),);}
}
import 'package:flutter/material.dart';
import 'package:project03/ui/pages/detail/detail.dart';
import 'package:project03/ui/pages/main/main.dart';
import 'package:project03/ui/pages/meal/meal.dart';class HYRouter {static final String initialRoute = HYMainScreen.routeName;static final Map<String, WidgetBuilder> routes ={HYMainScreen.routeName: (ctx) => HYMainScreen(),HYMealScreen.routeName: (ctx) => HYMealScreen(),HYDetailScreen.routeName: (ctx) => HYDetailScreen()};// 自己扩展static final RouteFactory generateRoute = (settings) {return null;};static final RouteFactory unknownRoute = (setting) {};
}
- 监听点击 这个Card同样是没有onTap所以 同样需要嵌套一个 GestureDetector
import "package:flutter/material.dart";
import 'package:project03/core/model/meal_model.dart';
import 'package:project03/ui/pages/detail/detail.dart';
import 'package:project03/ui/widgets/operation_item.dart';import "../../core/extension/int_extension.dart";class HYMealItem extends StatelessWidget {final HYMealModel _meal;final double cardRadius = 8.px;HYMealItem(this._meal);@overrideWidget build(BuildContext context) {return GestureDetector(child: Card(
// 它这里是有外边距的所以我们这里设置marginmargin: EdgeInsets.all(10.px),
// 它这里的阴影是这样的 这个值越大它明显elevation: 5,
// 我们这里边框是用这个BoxBorder来设置的
// 但是如果你想做圆角的话我们最好使用 * [RoundedRectangleBorder] 矩形圆角
// 这里不是传的值而是传的一个类 它的类型是一个抽象父类 所以我们要找它的实现类 这里因该这样传shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(cardRadius)),child: Column(children: <Widget>[buildBasicInfo(context),buildOperationInfo()],),),onTap: () {Navigator.of(context).pushNamed(HYDetailScreen.routeName, arguments: _meal);},);}Widget buildBasicInfo(BuildContext context) {return Stack(children: <Widget>[
// 然后这张图是非常的大的 所以不能直接放在上面ClipRRect(
// 但是这里我们不用 circular 因为这个东西是四个边都裁剪
// 所以这里又是一个新的apiborderRadius: BorderRadius.only(topLeft: Radius.circular(cardRadius),topRight: Radius.circular(cardRadius),),child: Image.network(_meal.imageUrl, width: double.infinity, height: 250.px, fit: BoxFit.cover,)),Positioned(right: 10.px,bottom: 10.px,child: Container(width: 300.px,padding: EdgeInsets.symmetric(horizontal: 10, vertical: 5),decoration: BoxDecoration(
// 我们这里需要半透明 所以这里就用 black54borderRadius: BorderRadius.circular(6.px),color: Colors.black54),
// 这里要使用context还得把这个改了
// 但是这里我们不想使用原来的 黑色child: Text(_meal.title, style: Theme.of(context).textTheme.display3.copyWith(color: Colors.white)),),)],);}Widget buildOperationInfo() {return Padding(
// 这里不能直接使用 const EdgeInsets.all(16.px) 因为如果用const创建对象的话 我们这里 就必须是常量 但是px已经是计算以后的结果 所以不能使用padding: EdgeInsets.all(16.px),child: Row(mainAxisAlignment: MainAxisAlignment.spaceAround,children: <Widget>[
// 这个时间是HYOperationItem(Icon(Icons.schedule), "${_meal.duration} 分钟"),HYOperationItem(Icon(Icons.restaurant), "${_meal.complexStr}"),HYOperationItem(Icon(Icons.favorite), "未收藏"),],),);}}
然后 获得参数
import "package:flutter/material.dart";
import 'package:project03/core/model/meal_model.dart';class HYDetailScreen extends StatelessWidget {static final String routeName = "/detail";@overrideWidget build(BuildContext context) {final meal = ModalRoute.of(context).settings.arguments as HYMealModel;return Scaffold(appBar: AppBar(title: Text(meal.title),),body: Center(child: Text(meal.title),),);}
}
注意这里我们改了路由所以也要hot-restart
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P7nFNtGu-1589428979676)(203CAE255A944BB89CCA5B9A3B7C5322)]
flutter 项目实践2相关推荐
- 干货:在Flutter项目下安卓flavor打包配置实践
????????关注后回复 "进群" ,拉你进程序员交流群???????? 作者丨狐友技术团队 来源丨搜狐技术产品(ID:sohu-tech) 本文字数:3894 字 预计阅读时间 ...
- 干货: 在 Flutter 项目下安卓 flavor 打包配置实践 | 开发者说·DTalk
本文原作者: 狐友技术团队,原文发布于搜狐技术产品: https://mp.weixin.qq.com/s/uOMxhc8xnFPmi7j_Z5JqVg 1.前言 Flutter 是 Google ...
- Flutter 项目开发指导 从基础入门到精通使用目录
Flutter 从入门 到精通系列文章 本文章为 Flutter 开发中的经验积累分享.教程分享.开发笔记分享目录,持续维护中. 题记 -- 执剑天涯,从你的点滴积累开始,所及之处,必精益求精. Fl ...
- dio设置自定义post请求_基于dio库封装flutter项目的标准网络框架
网络框架是每个应用的基石,封装一个好的网络框架不仅是项目的一个好的开始,并且直接影响到随后项目的稳定性和可扩展性.在移动开发的各个端都有非常赞的网络请求基础框架,比如Android的okhttp库.s ...
- Kotlin高仿微信-项目实践58篇
Kotlin高仿微信项目实践主要包含5大模块: 1.Web服务器 2.Kotlin客户端 3.Xmpp即时通讯服务器 4.视频通话服务器 5.腾讯云服务器 另外也有Flutter版本高仿微信功能,Fl ...
- 美团外卖Flutter动态化实践
此文转载自:https://my.oschina.net/meituantech/blog/4325381 LiteOS Studio图形化调测能力,物联网打工人必备!>>> 一.前 ...
- Flutter项目实操---资讯、发布动弹
资讯: 继续接着上一次https://www.cnblogs.com/webor2006/p/13186045.html的功能往下编写,这一篇算是这个操练小项目的最后一篇笔记了,基本上经过这么将近一年 ...
- Flutter 沙龙回顾 | 跨平台技术趋势及字节跳动 Flutter 架构实践
11 月 23 日,字节跳动技术沙龙 | Flutter 技术专场 在北京后山艺术空间圆满结束.我们邀请到字节跳动移动平台部 Flutter 架构师袁辉辉,Google Flutter 团队工程师 J ...
- 万字长文,请耐心看完!美团外卖Flutter动态化实践,5w+人已阅
作者:尚先 杨超 松涛 原文链接:https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651752194&idx=1& ...
最新文章
- 1102示波器使用方法_使用示波器进行故障诊断的方法(1):常见传感器波形分析-汽车用品行业...
- Javascript中的类实现
- springframework包下的RequestContextHolder类和ServletRequestAttributes类的源码和使用
- auth复习和BBS项目的登录(1)
- Apk打包-签名过程
- 受欢迎的五个开源可视化工具——你的选择是?
- myeclipse jdk tomcat mysql配置_JDK,TOMCAT,myeclipse,mysql安装以及配置
- PHP中的Array类型其实是Hashtable
- 注意啦,Struts 2.1.6跟sitemesh-2.4.1不兼容
- Android 中AlarmManager升级4.2
- 学习笔记:unity自带寻路(导航)系统:Nav Mesh导航网格
- rms 公式 有效值_为什麼均方根值(RMS)比平均值表达好一些?
- “5G+”发展论坛暨“金帽子”年度盛典圆满结束,共同探讨5G背景下网安技术发展和前沿趋势
- 华硕路由器流量管理QoS设置
- 计算机无法识别出硬件,电脑检测不到硬盘,电脑硬件故障检测工具
- XCTF 攻防世界Web题目 mfw
- python爬虫豆瓣top250_Python 爬取豆瓣TOP250实战
- Redis应用之限制访问频率
- 嵩山少林寺网站向全世界公布了千年武功秘籍
- c语言设计垃圾分类答题游戏,小程序案例源码001~垃圾分类+答题小程序效果演示...
热门文章
- Nginx 的配置和访问控制的理论实验操作详情
- P1027 [NOIP2001 提高组] Car 的旅行路线 (图 最短路)
- http post 415错误
- websocket-php
- 2023小红书年度生活趋势报告
- R Error: BiocParallel errors 1 remote errors, element index: 1 506 unevaluated and other errors解决办法
- line-height 和 height 区别
- 怎么卸载mysql????如何清理干净?
- HIVE启动的时候, The server time zone value ‘EDT‘ is unrecognized or represents more than one time zone.
- 遗传算法求解TSP问题(python版)