本片文章来自与我自己的有道云笔记 要看图片请点击链接

文档: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相关推荐

  1. 干货:在Flutter项目下安卓flavor打包配置实践

    ????????关注后回复 "进群" ,拉你进程序员交流群???????? 作者丨狐友技术团队 来源丨搜狐技术产品(ID:sohu-tech) 本文字数:3894 字 预计阅读时间 ...

  2. 干货: 在 Flutter 项目下安卓 flavor 打包配置实践 | 开发者说·DTalk

    本文原作者: 狐友技术团队,原文发布于搜狐技术产品:  https://mp.weixin.qq.com/s/uOMxhc8xnFPmi7j_Z5JqVg 1.前言 Flutter 是 Google ...

  3. Flutter 项目开发指导 从基础入门到精通使用目录

    Flutter 从入门 到精通系列文章 本文章为 Flutter 开发中的经验积累分享.教程分享.开发笔记分享目录,持续维护中. 题记 -- 执剑天涯,从你的点滴积累开始,所及之处,必精益求精. Fl ...

  4. dio设置自定义post请求_基于dio库封装flutter项目的标准网络框架

    网络框架是每个应用的基石,封装一个好的网络框架不仅是项目的一个好的开始,并且直接影响到随后项目的稳定性和可扩展性.在移动开发的各个端都有非常赞的网络请求基础框架,比如Android的okhttp库.s ...

  5. Kotlin高仿微信-项目实践58篇

    Kotlin高仿微信项目实践主要包含5大模块: 1.Web服务器 2.Kotlin客户端 3.Xmpp即时通讯服务器 4.视频通话服务器 5.腾讯云服务器 另外也有Flutter版本高仿微信功能,Fl ...

  6. 美团外卖Flutter动态化实践

    此文转载自:https://my.oschina.net/meituantech/blog/4325381 LiteOS Studio图形化调测能力,物联网打工人必备!>>> 一.前 ...

  7. Flutter项目实操---资讯、发布动弹

    资讯: 继续接着上一次https://www.cnblogs.com/webor2006/p/13186045.html的功能往下编写,这一篇算是这个操练小项目的最后一篇笔记了,基本上经过这么将近一年 ...

  8. Flutter 沙龙回顾 | 跨平台技术趋势及字节跳动 Flutter 架构实践

    11 月 23 日,字节跳动技术沙龙 | Flutter 技术专场 在北京后山艺术空间圆满结束.我们邀请到字节跳动移动平台部 Flutter 架构师袁辉辉,Google Flutter 团队工程师 J ...

  9. 万字长文,请耐心看完!美团外卖Flutter动态化实践,5w+人已阅

    作者:尚先 杨超 松涛 原文链接:https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651752194&idx=1& ...

最新文章

  1. 1102示波器使用方法_使用示波器进行故障诊断的方法(1):常见传感器波形分析-汽车用品行业...
  2. Javascript中的类实现
  3. springframework包下的RequestContextHolder类和ServletRequestAttributes类的源码和使用
  4. auth复习和BBS项目的登录(1)
  5. Apk打包-签名过程
  6. 受欢迎的五个开源可视化工具——你的选择是?
  7. myeclipse jdk tomcat mysql配置_JDK,TOMCAT,myeclipse,mysql安装以及配置
  8. PHP中的Array类型其实是Hashtable
  9. 注意啦,Struts 2.1.6跟sitemesh-2.4.1不兼容
  10. Android 中AlarmManager升级4.2
  11. 学习笔记:unity自带寻路(导航)系统:Nav Mesh导航网格
  12. rms 公式 有效值_为什麼均方根值(RMS)比平均值表达好一些?
  13. “5G+”发展论坛暨“金帽子”年度盛典圆满结束,共同探讨5G背景下网安技术发展和前沿趋势
  14. 华硕路由器流量管理QoS设置
  15. 计算机无法识别出硬件,电脑检测不到硬盘,电脑硬件故障检测工具
  16. XCTF 攻防世界Web题目 mfw
  17. python爬虫豆瓣top250_Python 爬取豆瓣TOP250实战
  18. Redis应用之限制访问频率
  19. 嵩山少林寺网站向全世界公布了千年武功秘籍
  20. c语言设计垃圾分类答题游戏,小程序案例源码001~垃圾分类+答题小程序效果演示...

热门文章

  1. Nginx 的配置和访问控制的理论实验操作详情
  2. P1027 [NOIP2001 提高组] Car 的旅行路线 (图 最短路)
  3. http post 415错误
  4. websocket-php
  5. 2023小红书年度生活趋势报告
  6. R Error: BiocParallel errors 1 remote errors, element index: 1 506 unevaluated and other errors解决办法
  7. line-height 和 height 区别
  8. 怎么卸载mysql????如何清理干净?
  9. HIVE启动的时候, The server time zone value ‘EDT‘ is unrecognized or represents more than one time zone.
  10. 遗传算法求解TSP问题(python版)