这篇文章来自于我自己的有道云笔记 看图片请去那里
文档:Day 4_6 项目实践3.md
链接:http://note.youdao.com/noteshare?id=65039f0f3bb93543e8d42dde9cc946b4&sub=30683DC8A1F946F883CE9947B0045685
项目实践3
如果现在我们是点击某一个
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aX8TPLCQ-1589760752439)(7DEA32B474214A7687C4260EAD602DBE)]
这个页面 就会跳转到 详情页
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o6v584h3-1589760752442)(6C4F1E6EC03840A4B35269B309801274)]
现在话我们就要把这个 详情页面做一下
现在看看我们项目中的东西没有做
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n0BYiAge-1589760752444)(16B3EE96011A4D59B261E4CFFE5C4B20)]
还有一个过滤的内容没有做
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-llDBjyh1-1589760752447)(EA7BD19C6F8F43ABBE297B0ADE58DB9D)]
点击了之后 过滤分类页面中的项
搜藏
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eFeITeDr-1589760752449)(DBB0B0137BF549CB946C825795658891)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0SN1jROJ-1589760752451)(8B7166EB23274C8D8A175FC601526770)]
也就说我们现在的页面还有 三个页面
- 详情页
- 收藏
- 过滤
我们之前也是创建了详情页的 同时也是将他放到了 路由中的
也是再页面中拿到了展示的meal的数据的
import "package:flutter/material.dart";
import 'package:project03/core/model/meal_model.dart';
class HYDetailScreen extends StatelessWidget {
static final String routeName = "/detail";
@override
Widget 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),
),
);
}
}
我们按照之前的习惯 页面的展示内容放在另外的一个 文件中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eIRwhHzK-1589760752453)(6705F56C8D78460E936842C6AD11465D)]
然后这个meal同时要在 detail和 detail_content 中使用的
之前讲过这个参数可以可以直接在build方法里面通过context 来获取的
import "package:flutter/material.dart";
import 'package:project03/core/model/meal_model.dart';
class HYDetailContent extends StatelessWidget {
final HYMealModel _meal;
HYDetailContent(this._meal);
@override
Widget build(BuildContext context) {
final meal = ModalRoute.of(context).settings.arguments as HYMealModel;
return Container();
}
}
但是我们这里的 meal 这个参数因该是不只是在build方法里面使用 所以我们这里最好将建议使用
使用构造方法传参的方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1kLtbgQi-1589760752454)(E84FA27EB9E5414D8F782CE316835CFF)]
这里 需要滚动
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w12HHmuw-1589760752455)(3E56AF9A80C44D3B92D3E5BA52B33976)]
当然这里可以用 ListView 滚动的
但是这里我们需要用 Column
但是这个Column有一个问题就是一旦 超出了屏幕它就会报错
这里我们可以让我们的Column作为 SingleChildScrollView的子组件
import "package:flutter/material.dart";
import 'package:project03/core/model/meal_model.dart';
class HYDetailContent extends StatelessWidget {
final HYMealModel _meal;
HYDetailContent(this._meal);
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
],
),
);
}
}
我们这样我们就可以滚动了
然后就是将 这些Widget创建出来
横幅的Widget
- 横幅就是一个图片 但是我们可以给它套一个Container 让它可以有更多的空间
// 1. 横幅图片
Widget buildBannerImage() {
return Container(
child: Image.network(_meal.imageUrl)
);
}
我们这里发现这个 标题是差不多的 所以这里我们要么搞一个 Widget要么搞一个公共的方法
这样我们就可以返回一个title了
// 公共的方法
Widget buildMakeSteps() {
}
flutter 布局经典问题
同样我们也需要展示这个步骤
// 2. 制作材料
Widget buildMakeMaterial() {
return Container(
child: ListView.builder(
itemCount: _meal.ingredients.length,
itemBuilder: (ctx, index) {
return Card(
child: Text(_meal.ingredients[index]),
);
}
),
);
}
这里有一个问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zR7480sV-1589760752457)(2F855233D65C46938956CA5A7C84084D)]
如果在Column里面嵌套一个 ListView就会出现一个非常常见的问题
一旦我们在Column中放一个ListView
ListView它很特别 它会在垂直方向上尽可能大的占据空间能占据多大的空间就占据多大的空间
这个东西非常的特别
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-G5HWEM7N-1589760752458)(967D113D9D5545E8AD85B8792AC6EF26)]
但是这个List View 就是占据这么大的空间
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Twbcsyy3-1589760752459)(CC400D26ADE64AD7B259310D87AC1A5C)]
如果我们在界面里面设置了Container然后在里面设置了高度300s
然后这个时候我们往Container里面 放一个ListView 那么我们的这个ListView会占据多高的高度呢
我们的LIstView是不是会占据一个尽可能高的高度
这个ListVIew就会尽可能大的占据空间 但是我们这里占据的高度设置了300这个
最后这个ListView的高度就是300 所以这个ListView就会尽可能大的占据空间
然后我们就将 ListView放到这个Column中 但是它需要它的子Widget需要有一个明确的高度
然后它就会根据这个高度来排放这个 之后Column在摆放的时候才知道怎么摆放
比如我们刚刚设置的横幅 其实也是这样的 虽然我们没有直接设置它的高度 但是我们一旦将图片加载出来以后 它就是有一个高度的Widget
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lWXOb7a7-1589760752461)(51421B3EB34743A39714A6663C17BFB9)]
所以这里Column里面放一个ListView
这个时候就有一个问题
- ListView需要占据尽可能多的占据高度 但是不可能因为还需要放其他的东西
- Column这里又需要ListView这里来给一个高度
这个问题出现最多的地方就是这个
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4nIHWPF8-1589760752462)(83447E17F65748DA962F62F588022BB4)]
同样如果你在ListView里面放一个ListView也会出现这个问题
- 出现场景: 父Widget 需要子Widget 给一个高度 但是子Widget又希望占据尽可能多的高度 这个时候就会出现这个hasSize的报错
- 所以 之后遇到这个子Widget没有一个确定的高度 这个时候父Widget就会不知道怎么布局
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aOlONtWv-1589760752463)(2284E9FB1C394FA9839CE65F4DD0884F)]
其实你如果去分析源码的话 我们是可以看到 它在哪里报的这个错的 它其实就是要给断言 会报这个错误
我们现在已经知道这个东西的产生的原因
ListView会想要尽可能的高但是 Container又想要知道这个子Widget高度
// 2. 制作材料
Widget buildMakeMaterial() {
return Container(
child: ListView.builder(
itemCount: _meal.ingredients.length,
itemBuilder: (ctx, index) {
return Card(
child: Text(_meal.ingredients[index]),
);
}
),
);
}
当然我们可以给它一个高度 这样我们就知道这个东西怎么摆放了
// 2. 制作材料
Widget buildMakeMaterial() {
return Container(
height: 300,
child: ListView.builder(
itemCount: _meal.ingredients.length,
itemBuilder: (ctx, index) {
return Card(
child: Text(_meal.ingredients[index]),
);
}
),
);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DBsImJpg-1589760752465)(6816B1F318054068BC314504B6CD9BD5)]
但是这样 很有可能这个东西就放不下的
同时这个 ListView也是可以在这个里面进行滚动的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7KOu6la0-1589760752466)(B90F8F9A8785433188DC388AB37AD4BB)]
如果我我们的内容比较多 那它就会在里面滚动了 这个是我们不想要的 局部滚动
- 所以如果在我们的应用里面想要用 局部滚动的话我们就可以用这个方法来做
所以我们不能把这个数字写死
但是去掉又要报错
- 那我们这里需要怎么做呢
我们希望它有多少内容它就有多高 我们这里的话 因该怎么样才能让我们的这个高度等于这个高度呢
我们可以用ListView.builder的shrinkWrap属性 如果这个值取false那就不会包裹内容还是和平常一样
但是如果这个shrinkWrap这个属性取做 true 这个shrinkWrap 这个属性值 可以注意到就是是否包裹的意思
// 2. 制作材料
Widget buildMakeMaterial() {
return Container(
color: Colors.orange,
child: ListView.builder(
shrinkWrap: true,
itemCount: _meal.ingredients.length,
itemBuilder: (ctx, index) {
return Card(
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Text(_meal.ingredients[index]),
),
);
}
),
);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7tDU6Wl2-1589760752467)(08ECE96AF89A4F11A122932058628509)]
但是我们拖它还是看的到东西 所以我们还得再设置一个属性physics 它的传的参数是一个抽象类 所以我们来找它的实现类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KhhGxJ7R-1589760752469)(64220F30D8674CE59EAB9B2135369C3F)]
我们在这里设置一个
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Su2nb3d-1589760752470)(0674E1B5294849199139A54BF02E75F9)]
这样的话它就会不会滚动了
// 2. 制作材料
Widget buildMakeMaterial() {
return Container(
color: Colors.orange,
child: ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: _meal.ingredients.length,
itemBuilder: (ctx, index) {
return Card(
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Text(_meal.ingredients[index]),
),
);
}
),
);
}
这样就完全没有这个 滚动的感觉了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wuII2xZI-1589760752471)(80F2840A97AB4ED695B9D98844004D11)]
还有一个问题
这里会发现这个Column它会自动加上一些内边距 但是我们并不想要这个东西 它会自动加上一些内边距
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eIJJl6IM-1589760752472)(2EFFB7EF0FA249A5BCAE1C7FDB2E38B0)]
我们可以设置它的内边距 为0 这个和 EdgaInsets.all(0) 是一样的
// 2. 制作材料
Widget buildMakeMaterial() {
return Container(
color: Colors.orange,
child: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: NeverScrollableScrollPhysics(),
itemCount: _meal.ingredients.length,
itemBuilder: (ctx, index) {
return Card(
child: Padding(
padding: const EdgeInsets.all(30.0),
child: Text(_meal.ingredients[index]),
),
);
}
),
);
}
同时修改一下样式
因为太贴近墙边了 所以我们这里给它加一个宽度
// 2. 制作材料
Widget buildMakeMaterial() {
return Container(
color: Colors.white,
width: 300,
child: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: NeverScrollableScrollPhysics(),
itemCount: _meal.ingredients.length,
itemBuilder: (ctx, index) {
return Card(
color: Colors.amber,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 5.px, horizontal: 10.px),
child: Text(_meal.ingredients[index]),
),
);
}
),
);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tku86pS4-1589760752474)(8C0230D0DD8C47E2BC6D84757CEB68CC)]
但是这个并没有根据设备变化 所以这里我们可以 获取到页面的宽度然后在做处理
// 2. 制作材料
Widget buildMakeMaterial(BuildContext context) {
return Container(
color: Colors.white,
width: MediaQuery.of(context).size.width - 30.px,
child: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: NeverScrollableScrollPhysics(),
itemCount: _meal.ingredients.length,
itemBuilder: (ctx, index) {
return Card(
color: Colors.amber,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 5.px, horizontal: 10.px),
child: Text(_meal.ingredients[index]),
),
);
}
),
);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B4nyWukh-1589760752475)(F95E0DFB6948478B946675D7D885FCE7)]
更改样式
我们的color和decoration不能同时存在
// 2. 制作材料
Widget buildMakeMaterial(BuildContext context) {
return Container(
padding: EdgeInsets.all(8.px),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(6.px),
),
width: MediaQuery.of(context).size.width - 30.px,
child: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: NeverScrollableScrollPhysics(),
itemCount: _meal.ingredients.length,
itemBuilder: (ctx, index) {
return Card(
color: Colors.amber,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 5.px, horizontal: 10.px),
child: Text(_meal.ingredients[index]),
),
);
}
),
);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PYySTI0r-1589760752476)(9C10D0582FA0406495A7E3846288EBF0)]
更细节的调整就后面再来做
现在我们做出的来的就和 样本差不多了
制作步骤
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: <Widget>[
buildBannerImage(),
buildMakeTitle("制作材料", context),
buildMakeMaterial(context),
buildMakeTitle("制作步骤", context),
buildMakeStep(),
],
),
);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qiXXmEXu-1589760752478)(06EB5E6A00C741F18136C0EDD62AC8C7)]
- 制作步骤
然后这里我们继续做这个 制作步骤
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9iOJR8Rg-1589760752479)(43438C29DA004F4B854A5C2F82E5FBE7)]
同样这个 也是一个Container然后里面放ListView就可以了
而且我们不难发现 这两个东西是有很多的相似的地方
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uDJUcAjA-1589760752480)(A368BDFF24104AECB1B1451BF5F4248F)]
我们可以发现最外面的东西几乎是一样的 我们没有必要写两遍
我们就来抽取代码 两个办法 Widget 和 做成方法
抽取代码
Widget buildMakeContent(Widget child, BuildContext context) {
return Container(
padding: EdgeInsets.all(8.px),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8.px),
),
width: MediaQuery.of(context).size.width - 30.px,
child: child
);
}
但是我们这里最好和 官方一样使用命名的参数
Widget buildMakeContent({Widget child, BuildContext context}) {
return Container(
padding: EdgeInsets.all(8.px),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8.px),
),
width: MediaQuery.of(context).size.width - 30.px,
child: child
);
}
使用
return buildMakeContent(
child: ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: NeverScrollableScrollPhysics(),
itemCount: _meal.ingredients.length,
itemBuilder: (ctx, index) {
return Card(
color: Colors.amber,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 5.px, horizontal: 10.px),
child: Text(_meal.ingredients[index]),
),
);
}
),
context: context
);
}
这样就重构完了
我们在下面就可以直接 使用这个MakeContent
来构造这个Container
// 3. 制作步骤
Widget buildMakeStep(BuildContext context) {
return buildMakeContent(
context: context,
child: ListView.builder(
itemBuilder: ,
)
);
}
但是有一个问题就是我们需要用这个 线条来分割这个 每一项元素
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N8Ythqf5-1589760752481)(DF244AC68AF44E6EA550829AA602140D)]
所以这里我们就不用builder了我们用ListView.seprated
// 3. 制作步骤
Widget buildMakeStep(BuildContext context) {
return buildMakeContent(
context: context,
child: ListView.separated(
itemCount: _meal.steps.length,
itemBuilder: (ctx, index) {
return ListTile(
leading: Text("${index + 1}"),
title: Text(_meal.steps[index]),
);
},
separatorBuilder: (ctx, index) {
// 这个Divider就是分割线
return Divider();
},
)
);
}
这样我们就能产出分割线
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PTFVCu4K-1589760752483)(EA37DF8093BD44F2A4FF3360FB73E6F9)]
但是是我们这里样做的时候他又报这个错了
所以我们同样需要设置这个的shrinkWrap的属性
同样对他设置physics padding
// 3. 制作步骤
Widget buildMakeStep(BuildContext context) {
return buildMakeContent(
context: context,
child: ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: _meal.steps.length,
itemBuilder: (ctx, index) {
return ListTile(
leading: Text("#${index + 1}"),
title: Text(_meal.steps[index]),
);
},
separatorBuilder: (ctx, index) {
// 这个Divider就是分割线
return Divider();
},
)
);
}
这些东西 我们发发现它是一些重复的代码
当然我们可以用一个东西来继承这个ListView 然后来设置这样的一个 但是这里我们就不设置这个东西了
这里的话为我们的这个东西就搞定了
- 制作圆角
之前说过我们如果要做圆角图片 专门讲过
Day3_18 制作一个豆瓣的主页 1300 多行的地方有专门讲 这个圆角图片怎么做的地方这里也差不多
但是这里不是用的那里的方法 但是因该都可以 我们用 CircleAvatar
// 3. 制作步骤
Widget buildMakeStep(BuildContext context) {
return buildMakeContent(
context: context,
child: ListView.separated(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
itemCount: _meal.steps.length,
itemBuilder: (ctx, index) {
return ListTile(
leading: CircleAvatar(
child: Text("#${index + 1}"),
),
title: Text(_meal.steps[index]),
);
},
separatorBuilder: (ctx, index) {
// 这个Divider就是分割线
return Divider();
},
)
);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lhZctp7k-1589760752484)(D01A0B6D36E84CAFAB5249A204F8CD19)]
flutter里面还有很多这中Widget 我们可以到 Google上面去搜索 stackoverflow的那些就会比较好
这里我们就把这个页面搞定了
当时我们这里还有一个东西没有做
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EEGEGq1v-1589760752485)(C10F702CED044BB0A0D21F367F982934)]
就是右下角的这个 按钮
很明显这个东西 它是 floatActionButton的东西
import "package:flutter/material.dart";
import 'package:project03/core/model/meal_model.dart';
import 'package:project03/ui/pages/detail/detail_content.dart';
class HYDetailScreen extends StatelessWidget {
static final String routeName = "/detail";
@override
Widget build(BuildContext context) {
final meal = ModalRoute.of(context).settings.arguments as HYMealModel;
return Scaffold(
appBar: AppBar(
title: Text(meal.title),
),
body: HYDetailContent(meal),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.favorite_border),
),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6trQcg0Q-1589760752486)(B86844A7E9DF43E490AC6AF21FC67851)]
这个颜色是来自我们之前设置全局的主题
全局的这个主题里面有一个primarySwitch
所以我们为了设置这个颜色 我们可以设置accentColor
// 可以在这里把它封装成一个方法 也可以封装成一个属性
static final ThemeData norTheme = ThemeData(
primarySwatch: Colors.pink, // primaryColor accentColor 如果不一样再单独设置
canvasColor: Color.fromRGBO(255, 254, 222, 1),
accentColor: Colors.amber,
textTheme: TextTheme(
body1: TextStyle(fontSize: bodyFontSize),
display1: TextStyle(fontSize: smallFontSize, color: Colors.black87),
display2: TextStyle(fontSize: normalFontSize, color: Colors.black87),
display3: TextStyle(fontSize: largeFontSize, color: Colors.black87),
)
);
还有我们这里 也是用的Colors.embar
所以这个但是这个和 我们之前设置的ListView颜色重复了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Em3folr6-1589760752487)(09D9D08616284FC3B00F1A0DB7249CE0)]
我们这里ListView不因该直接用这个Colors.embar来直接写死所以
通过Theme.of(“context”)的方式最好
return Card(
color: Theme.of(context).accentColor,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 5.px, horizontal: 10.px),
child: Text(_meal.ingredients[index]),
),
);
同时我们也需要做一个点击
同时我们 这个喜欢的状态因该是要在其他的地方也是要使用的 所以
avator 这个因该还是比较简单的
- 是否可以把这个 ListView换成 Column这样不就没有这些事情了吗
最好不好 因为这个 ListView其实还有 语义的作用
同样还有一个问题就 Column 这个也是尽量占据尽可能多的地方
如果你要用Column这个东西来摆这个东西的话 就会同样出现 父Widget问子类高度 但是子类Widget尽可能占据高度的问题
我们可以用这个mainAxisSize 来设置高度
这样它也不会触发这个 hasSize的报错
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-knmG2Yjk-1589760752488)(77961D4FCE8C4C72B3882D3031DCCDB0)]
也可以看这个链接
https://blog.csdn.net/jsp13270124/article/details/106139465
收藏的效果
这个收藏因该是在很多的地方共享的 在不同的页面中使用
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aHCQsPHI-1589760752490)(EC291F239A3A4F5F98E43576A9368614)]
这个这里这个东西有两种做法
- 我们再搞一个Privider 我们搞一个数组 一旦我们收藏我们就会 将meal添加到数组里面
就是再搞一个ViewModel来处理
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UKJX4Ddy-1589760752491)(23E72714F63A445D9BA7611A4532468F)]
- 还有一种方法 就是
给食物添加一个属性 isFavor属性
默认它的值 是false 一旦我们点击了某一个食物 它就搞成 true
setState(() {})
不用ViewModel来通知的话我们只有用这个
如果你采取的是 第二种方法 那我们就需要将它变成 fulWidget这样不太方便
- 所以我们最终采取第一种方案
创建 HYFavorViewModel
import 'package:flutter/cupertino.dart';
import 'package:project03/core/model/meal_model.dart';
class HYFavorViewModel extends ChangeNotifier {
List<HYMealModel> _favorMeals = [];
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fEVDXJHi-1589760752493)(A865AC2430394E1FAAD41C62D2C2BD47)]
我们就可以在这里添加一些操作相关的方法
get 添加 删除
core->viewmodel->meal_view_model
import 'package:flutter/cupertino.dart';
import 'package:project03/core/model/meal_model.dart';
class HYFavorViewModel extends ChangeNotifier {
List<HYMealModel> _favorMeals = [];
List<HYMealModel> get favorMeals {
return _favorMeals;
}
void addMeal(HYMealModel meal) {
_favorMeals.add(meal);
// 然后这里是要做一个通知的
notifyListeners();
}
void removeMeal(HYMealModel meal) {
_favorMeals.remove(meal);
notifyListeners();
}
}
然后我们就要去 添加点击事件
floatingActionButton: FloatingActionButton(
child: Icon(Icons.favorite_border),
onPressed: () {
},
),
然后就有 一个问题 就是我们的这个favor的状态因该是要受这个东西控制的
所以我们的这个图片的参数还不能写死了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9q53JZb5-1589760752495)(B32FEC1051104423A2A0ECDA563F6FF4)]
floatingActionButton: Consumer<HYFavorViewModel> (
// 1. 判断是否是收藏状
builder: (ctx, favorVM, child) {
return FloatingActionButton(
child: Icon(Icons.favorite_border),
onPressed: () {
},
);
}
)
然后我们就要 根据这个List里面是否含有这个meal来判断来展示不同的 icon
detail->detail.dart
floatingActionButton: Consumer<HYFavorViewModel> (
// 1. 判断是否是收藏状
builder: (ctx, favorVM, child) {
final isFavor = favorVM.favorMeals.contains(meal);
return FloatingActionButton(
child: Icon(Icons.favorite_border),
onPressed: () {
},
);
}
)
但是我们发现这个 判断可能很多的地方都要用 所以 我们可以封装一下这个方法
我们可以去viewmodel里面添加一个方法
import 'package:flutter/cupertino.dart';
import 'package:project03/core/model/meal_model.dart';
class HYFavorViewModel extends ChangeNotifier {
List<HYMealModel> _favorMeals = [];
List<HYMealModel> get favorMeals {
return _favorMeals;
}
void addMeal(HYMealModel meal) {
_favorMeals.add(meal);
// 然后这里是要做一个通知的
notifyListeners();
}
void removeMeal(HYMealModel meal) {
_favorMeals.remove(meal);
notifyListeners();
}
bool isFavor(HYMealModel meal) {
return _favorMeals.contains(meal);
}
}
然后使用这个 方法来做判断
floatingActionButton: Consumer<HYFavorViewModel> (
// 1. 判断是否是收藏状
builder: (ctx, favorVM, child) {
final iconData = favorVM.isFavor(meal) ? Icons.favorite : Icons.favorite_border;
return FloatingActionButton(
child: Icon(iconData),
onPressed: () {
},
);
}
)
运行一下 点击的时候报错了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2NPlv627-1589760752497)(0A300A54D80F4E89B8E148F33DA7263E)]
它的意思就是我们没有吧这个HYfavorViewModel挂到Marterial上面去
我们刚刚漏掉了添加这个 Provider的步骤
改成 MultiProvder就可以了
main.dart
import 'package:provider/provider.dart';
void main() {
// Provider -> ViewModel / Provider / Consumer(Selector)
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(
create: (ctx) => HYMealViewModel(),
),
ChangeNotifierProvider(
create: (ctx) => HYFavorViewModel(),
)
],
child: MyApp(),
)
);
}
这样就没有问题了
处理一个问题
我们发现一个问题 就是我们进入详情页的时候 它的位置太靠左 不对头
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mKMJs4gT-1589760752499)(6DB616C40464458287A1DDAAC641BC7D)]
交叉轴 对其方式
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aDNNw8oC-1589760752502)(656A8791073B40B69A2DD4FC8B073AB9)]
会发现它过于靠左 然后图片出现后它就回复正常了
显然这个问题 就是图片没有加载出来的
这个Widget的宽度是取决于子Widget的最大宽度
而这个最大的宽度就是 图片 所以
如果图片没有加载出来 最宽的说就是 下面的这个Contaienr
现在我们知道原因的话 就好处理
我们来到这个 Column 我们可以给他宽度吗
不行 所以我们可以给 里面的子Widget加宽度
detail->detail-content.dart
// 1. 横幅图片
Widget buildBannerImage() {
return Container(
width: double.infinity,
child: Image.network(_meal.imageUrl)
);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ElTPfmF-1589760752504)(576311AB814A4609AC578381E8782A12)]
这样它就居中了 这就没有这个问题了
然后添加点击事件
import "package:flutter/material.dart";
import 'package:project03/core/model/meal_model.dart';
import 'package:project03/core/viewmodel/favor_view_model.dart';
import 'package:project03/ui/pages/detail/detail_content.dart';
import 'package:provider/provider.dart';
class HYDetailScreen extends StatelessWidget {
static final String routeName = "/detail";
@override
Widget build(BuildContext context) {
final meal = ModalRoute.of(context).settings.arguments as HYMealModel;
return Scaffold(
appBar: AppBar(
title: Text(meal.title),
),
body: HYDetailContent(meal),
floatingActionButton: Consumer<HYFavorViewModel> (
// 1. 判断是否是收藏状
builder: (ctx, favorVM, child) {
final iconData = favorVM.isFavor(meal) ? Icons.favorite : Icons.favorite_border;
final iconColor = favorVM.isFavor(meal) ? Colors.red : Colors.black;
return FloatingActionButton(
child: Icon(iconData, color: iconColor),
onPressed: () {
favorVM.addMeal(meal);
},
);
}
)
);
}
}
但是我们还要移出 所以这里可以做一个简单的判断
floatingActionButton: Consumer<HYFavorViewModel> (
// 1. 判断是否是收藏状
builder: (ctx, favorVM, child) {
final iconData = favorVM.isFavor(meal) ? Icons.favorite : Icons.favorite_border;
final iconColor = favorVM.isFavor(meal) ? Colors.red : Colors.black;
return FloatingActionButton(
child: Icon(iconData, color: iconColor),
onPressed: () {
if( favorVM.isFavor(meal) ) {
favorVM.addMeal(meal);
} else {
favorVM.removeMeal(meal);
}
},
);
}
)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CrikOIbY-1589760752506)(3FE2B84AD4D94B9281E491986F812861)]
然后这里 我们又发现这个代码可能会再其他的地方也会使用 所以我们又把它封装起来
view_model -> HYFavorViewModel.dart
import 'package:flutter/cupertino.dart';
import 'package:project03/core/model/meal_model.dart';
class HYFavorViewModel extends ChangeNotifier {
List<HYMealModel> _favorMeals = [];
List<HYMealModel> get favorMeals {
return _favorMeals;
}
void addMeal(HYMealModel meal) {
_favorMeals.add(meal);
// 然后这里是要做一个通知的
notifyListeners();
}
void removeMeal(HYMealModel meal) {
_favorMeals.remove(meal);
notifyListeners();
}
bool isFavor(HYMealModel meal) {
return _favorMeals.contains(meal);
}
void handleMeal(HYMealModel meal) {
if (isFavor(meal)) {
removeMeal(meal);
} else {
addMeal(meal);
}
}
}
虽然这里 我们将 addMeal和 removeMeal的操作 放在handleMeal里面完成了 但是我们最好不要删除这个 removeMeal 和 addMeal方法
使用
detail -> detail.dart
floatingActionButton: Consumer<HYFavorViewModel> (
// 1. 判断是否是收藏状
builder: (ctx, favorVM, child) {
final iconData = favorVM.isFavor(meal) ? Icons.favorite : Icons.favorite_border;
final iconColor = favorVM.isFavor(meal) ? Colors.red : Colors.black;
return FloatingActionButton(
child: Icon(iconData, color: iconColor),
onPressed: () {
favorVM.handleMeal(meal);
},
);
}
)
这里我们就搞定了这个问题
如果你觉得这个floaActionButton代码有点多的话 我们可以将它封装一下
我们新建一个文件来封装一下
detail -> detail_floating_button.dart
import "package:flutter/material.dart";
import 'package:project03/core/model/meal_model.dart';
import 'package:project03/core/viewmodel/favor_view_model.dart';
import 'package:provider/provider.dart';
class HYDetailFloatingButton extends StatelessWidget {
final HYMealModel _meals;
HYDetailFloatingButton(this._meals);
@override
Widget build(BuildContext context) {
return Consumer<HYFavorViewModel> (
// 1. 判断是否是收藏状
builder: (ctx, favorVM, child) {
final iconData = favorVM.isFavor(_meals) ? Icons.favorite : Icons.favorite_border;
final iconColor = favorVM.isFavor(_meals) ? Colors.red : Colors.black;
return FloatingActionButton(
child: Icon(iconData, color: iconColor),
onPressed: () {
favorVM.handleMeal(_meals);
},
);
}
);
}
}
然后我们的这个detail的代码就比较简洁了
detail -> detail.dart
import "package:flutter/material.dart";
import 'package:project03/core/model/meal_model.dart';
import 'package:project03/core/viewmodel/favor_view_model.dart';
import 'package:project03/ui/pages/detail/detail_content.dart';
import 'package:project03/ui/pages/detail/detail_floating_button.dart';
import 'package:provider/provider.dart';
class HYDetailScreen extends StatelessWidget {
static final String routeName = "/detail";
@override
Widget build(BuildContext context) {
final meal = ModalRoute.of(context).settings.arguments as HYMealModel;
return Scaffold(
appBar: AppBar(
title: Text(meal.title),
),
body: HYDetailContent(meal),
floatingActionButton: HYDetailFloatingButton(meal)
);
}
}
然后我们再来做一下这个 分类的页面来做一下这个收藏
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k8qxA0qc-1589760752508)(E4B7F940155F4CF7A9EF7F9D75F34AD4)]
然后我们当时是把这个Widget来一个公共的Widget里面使用的
来到这个里面
widgets -> meal_item
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-PJ6GGSTT-1589760752509)(1FA7C9C8EF9B49B7B970A1484FE2AA11)]
这里还有一个问题就 我们想让这个 文字的颜色也变化
这个东西已经被封装了所以我们要到里面去看看
import "package:flutter/material.dart";
import "../../core/extension/int_extension.dart";
class HYOperationItem extends StatelessWidget {
// 这个传的东西不一定是icon但是我们可以用这个 icon左边变量
final Widget _icon;
final String _title;
final Color _textColor;
HYOperationItem(this._icon, this._title, {this._textColor = Colors.black});
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
_icon,
// 这里默认情况最小就是 11所以我们也不要用Theme.of 去取样式了
SizedBox(width: 3.px),
Text(_title),
],
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4PkQw9lV-1589760752510)(637BB3CC803B471E86A059D8B2CCB6CC)]
但是这里报错了 因为这里报错 因为你看这个报错 命名可选参数不能以 _开头
官方也是这样它们也是没有用 _开头
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BuwFflqZ-1589760752511)(8E5B64C2595C46CCBA1AA6695C6A3458)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JglQCcsL-1589760752512)(EA8642A76E9C4FE582397AEA0E4DFCF4)]
如果你想将一个东西搞成可选参数我们 就不能用_定义
这样就没问题了
widgets-> operation_items.dart
import "package:flutter/material.dart";
import "../../core/extension/int_extension.dart";
class HYOperationItem extends StatelessWidget {
// 这个传的东西不一定是icon但是我们可以用这个 icon左边变量
final Widget _icon;
final String _title;
final Color textColor;
HYOperationItem(this._icon, this._title, {this.textColor = Colors.black});
@override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
_icon,
// 这里默认情况最小就是 11所以我们也不要用Theme.of 去取样式了
SizedBox(width: 3.px),
Text(_title, style: TextStyle(color: textColor)),
],
);
}
}
widgets -> meal_item.dart
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WeNG9naU-1589760752513)(4A592E412F694741A07143AD29C9F86B)]
同时这个东西还需要监听它的点击
同样这个东西非常简单
Widget buildFavorItem(HYMealModel meal) {
return Consumer<HYFavorViewModel> (
builder: (ctx, favorVM, child) {
// 1, 判断是否是处理收藏状态的
final iconData = favorVM.isFavor(meal) ? Icons.favorite : Icons.favorite_border;
final favorColor = favorVM.isFavor(meal) ? Colors.red : Colors.black;
final title = favorVM.isFavor(meal) ? "收藏" : "未收藏";
return GestureDetector(
child: HYOperationItem(
Icon(
iconData, color: favorColor),
title,
textColor: favorColor,
),
onTap: () {
favorVM.handleMeal(meal);
}
);
}
);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-v1KPGCQP-1589760752515)(50F6F6FF7C0A4744B6A678ABEF571A81)]
但是这里有一个问题 如果这个文本长度不一样 它就会 影响整个的排布的
因为我们的这个Row里面排布的时候 我们用的是mainAxisAlignment的主轴的对齐方式的时候 我们使用的是
ainAxisAlignment: MainAxisAlignment.spaceAround
所以当子widget变化的时候 重构的时候它会去重新对其
这里我们可以设置它的宽度 或者是 将字体的宽度长度设置为一定
同样我们也可以设置它的宽度
widgets -> operation_item.dart
import "package:flutter/material.dart";
import "../../core/extension/int_extension.dart";
class HYOperationItem extends StatelessWidget {
// 这个传的东西不一定是icon但是我们可以用这个 icon左边变量
final Widget _icon;
final String _title;
final Color textColor;
HYOperationItem(this._icon, this._title, {this.textColor = Colors.black});
@override
Widget build(BuildContext context) {
return Container(
width: 80.px,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_icon,
// 这里默认情况最小就是 11所以我们也不要用Theme.of 去取样式了
SizedBox(width: 3.px),
Text(_title, style: TextStyle(color: textColor)),
],
),
);
}
}
然后还有一个问题 就是我们这个容易点到外面去 我们可以给这个包裹一个 padding让它更容易被点击到
我们这里可以将这个Gesture只会将有内容的部分加入点击的事件
- 这里我们可以给他加一个背景
还有一个办法
import "package:flutter/material.dart";
import "../../core/extension/int_extension.dart";
class HYOperationItem extends StatelessWidget {
// 这个传的东西不一定是icon但是我们可以用这个 icon左边变量
final Widget _icon;
final String _title;
final Color textColor;
HYOperationItem(this._icon, this._title, {this.textColor = Colors.black});
@override
Widget build(BuildContext context) {
return Container(
width: 80.px,
padding: EdgeInsets.symmetric(vertical: 12.px),
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_icon,
// 这里默认情况最小就是 11所以我们也不要用Theme.of 去取样式了
SizedBox(width: 3.px),
Text(_title, style: TextStyle(color: textColor)),
],
),
);
}
}
还有一种办法
- 往GestureDetector里面添加 属性
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y4D2PUy2-1589760752516)(614732C77A3646228BFDE2D24E975C4F)]
使用GestureDetector包裹Container,发现在Container内容为空的区域点击时,捕捉不到onTap点击事件。
解决方案:在GestureDetector里面添加属性:behavior: HitTestBehavior.opaque,即可:
widgets -> meal_item.dart
return GestureDetector(
behavior: HitTestBehavior.opaque,
child: HYOperationItem(
Icon(
iconData, color: favorColor),
title,
textColor: favorColor,
),
onTap: () {
favorVM.handleMeal(meal);
}
);
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tU31KVJL-1589760752517)(3760BAADE6664BDD8931AD77EED65356)]
同样可以完成相应的操作
这样就可以方便用户做一个点击
收藏页面的制作
我们已经有 FavorMeal的list了所以
我们现在就需要将这些页面做一个展示
新建文件 favor_content.dart
我们用使用provider里面的东西 所以consumer
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-b6EgVsZc-1589760752518)(414CDFE04A92425BB62A16F5A5037841)]
我们发现这个收藏和 我们的平常的页面是一样的
import "package:flutter/material.dart";
import 'package:project03/core/viewmodel/favor_view_model.dart';
import 'package:project03/ui/widgets/meal_item.dart';
import 'package:provider/provider.dart';
class HYFavorContent extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<HYFavorViewModel>(
builder: (ctx, favorVM, child) {
return ListView.builder(
itemCount: favorVM.favorMeals.length,
itemBuilder: (itemCtx, index) {
return HYMealItem(favorVM.favorMeals[index]);
}
);
},
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-08bKmgat-1589760752520)(3013DBEDE29740EC9C56829266D061BC)]
这样收藏 就可以看到
但是当没有数据的时候我们希望能 显示一个没有东西的提示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f6YqlW7c-1589760752521)(FBFC350EDC1D4541905F1BC6BACC0D70)]
当然还有一种方案就是 再meal_model里面设置一个bool一旦为true就是收藏的食品
这个东西 如果是购物车这些是需要 送到服务器的
服务器也是需要知道的
我们这个如果你不希望发生刷新的话 就可以用Selector
抽屉效果
这个东西其实是再flutter里面实现起来非常的简单的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Tw0nV7wm-1589760752522)(561983ABDBE940CA86527F3D1F4C734D)]
这个东西是加在首页里面的 我们到首去加这个东西
我们来到 home -> home.dart
只需要给它加一个Drawer就可以出现这个抽屉的效果
import "package:flutter/material.dart";
import 'package:project03/ui/pages/home/home_content.dart';
class HYHomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("美食广场")
),
body: HYHomeContent(),
drawer: Drawer(),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mljNADuX-1589760752523)(EE5912EB440B4969BFD21AF38B6F3BB8)]
我们的flutter很好做这个抽屉的效果
但是它默认情况它有一定的宽度
但是这里 它是没有一个属性可以直接调整它的宽度的
我们只能给它添加一个 Container来嵌套它 然后来设置它的宽度
import "package:flutter/material.dart";
import 'package:project03/ui/pages/home/home_content.dart';
import "../../../core/extension/int_extension.dart";
class HYHomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("美食广场")
),
body: HYHomeContent(),
drawer: Container(
width: 200.px,
child: Drawer()
),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jOfaYutd-1589760752524)(3C7C7D8603954CD0B59B460BA3BF70ED)]
这样我们就可以做一些抽屉效果
但是如果我们这里 想要做一些图标 但是我们的这个默认的图标怎么改呢
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dRMQkAxP-1589760752525)(8E3EABADDE744022BE29FDF9C6F28DA7)]
之前我们因该是讲过这个东西怎么改
leading
import "package:flutter/material.dart";
import 'package:project03/ui/pages/home/home_content.dart';
import "../../../core/extension/int_extension.dart";
class HYHomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("美食广场"),
leading: Icon(Icons.build),
),
body: HYHomeContent(),
drawer: Container(
width: 250.px,
child: Drawer()
),
);
}
}
但是这里有一个问题就是 如果我们把图标改了那这个东西 Drawer它就不弹出了
那这个东西怎么做呢
flutter 东西是很多的 所以我们希望如果 后面自己遇到问题的话 能自己做一个搜索
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-205KKtqR-1589760752527)(732600E61C0742B5A7C6CFD4EFA3AE40)]
那我们怎做呢
我们去google搜索 这个 flutter drawer icon
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YTRx68R8-1589760752528)(95E63261F4894099A7EFD7BD00681C18)]
这样它的第一条就是 How can I change Drawer icon
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VqfTvadM-1589760752529)(043A97094D1C43DE9219762BA1091101)]
问题 搜索的答案 下面就有搜索的答案
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BDMkvx8r-1589760752530)(6288D5B212444AC3B8D303AE7ADCF7B2)]
所以我们这里最好搞一个IconButton
所以这里我们也这样
import "package:flutter/material.dart";
import 'package:project03/ui/pages/home/home_content.dart';
import "../../../core/extension/int_extension.dart";
class HYHomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("美食广场"),
leading: IconButton(
icon: Icon(Icons.build),
onPressed: () {
},
),
),
body: HYHomeContent(),
drawer: Container(
width: 250.px,
child: Drawer()
),
);
}
}
答案里面还解释为什是这样的 我们这里监听它的点击事件 然后将这个Drawer弹出
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E75rfPF9-1589760752532)(75677F7CDC444DE69B27A7FBC7E025C5)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s56GRrf4-1589760752533)(3591023303304286AE8D7671729606BD)]
但是这里还是不可以的
这是为什呢
因为我们拿到的不是这个 Scaffold
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2hCxX6XP-1589760752534)(460D26FFE5534E8783150709B22CF163)]
因为这个context里面是没有这个Scaffold 因为这个 Scaffold是在build方法里面的
回答也指出了这个问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sXxHhMbt-1589760752536)(45A3606EADB34FA6966A481C4C0CBE56)]
但是这里 我们 使用这个Builder来 创建这个leading它就可以获得这个build方法里面创建的 Scaffold
我们之前也用了很多的builder 比如 FutureBuilder 还有LayoutBuilder 但是这里我们使用 Builder
我们这里拿到的就是这个Scaffold了
因为原因很简单 因为 这个Builder开始调用它的builder的时候一定是在Scaffold已经创建好了的基础上的
然后你在这里调用这个 就看可以了
但是你原来的东西不能丢 那个Drawer属性
import "package:flutter/material.dart";
import 'package:project03/ui/pages/home/home_content.dart';
import "../../../core/extension/int_extension.dart";
class HYHomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("美食广场"),
leading: Builder(
builder: (BuildContext context) {
return IconButton(
icon: Icon(Icons.build),
onPressed: () {
// 它这里的目的是为了拿到这个 Scaffold 然后调用它的一个openDrawer
// 这样来做的
Scaffold.of(context).openDrawer();
},
);
}
),
),
body: HYHomeContent(),
drawer: Drawer(),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WCwj83GU-1589760752537)(4DC153E1C9E245478DEB3866AF37C089)]
这样就可以把这个东西弹出了
然后我们把这个过滤做一下了
我们要做drawer最好也把这个东西做一个封装
当然我们也可以把这个appBar也对它做一个封装
封装 drawer
import "package:flutter/material.dart";
import "../../../core/extension/int_extension.dart";
class HYHomeDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 250.px,
child: Drawer()
);
}
}
appBar封装是有一点问题的
因为这个 appBar要求的参数是这个这个类型 我们的stl是不行的
import "package:flutter/material.dart";
class HYHomeAppBar extends StatelessWidget {
@override
Widget build(BuildContext context) {
return AppBar(
title: Text("美食广场"),
leading: Builder(
builder: (BuildContext context) {
return IconButton(
icon: Icon(Icons.build),
onPressed: () {
// 它这里的目的是为了拿到这个 Scaffold 然后调用它的一个openDrawer
// 这样来做的
Scaffold.of(context).openDrawer();
},
);
}
),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0RaUspxG-1589760752539)(3395ACC44CCB4771B62C7B2535DD8476)]
所以你封装的时候还不能这样做了
我们让他继承自 AppBar 但是我们这样就要重写构造器了
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A7SYrwPr-1589760752540)(EAA5706B3E0D4E7798975CC0008FC33D)]
然后让这个build的参数放到这个 super里面
home -> home_app_bar.dart
import "package:flutter/material.dart";
class HYHomeAppBar extends AppBar {
HYHomeAppBar(BuildContext context):super(
title: Text("美食广场"),
leading: Builder(
builder: (BuildContext context) {
return IconButton(
icon: Icon(Icons.build),
onPressed: () {
// 它这里的目的是为了拿到这个 Scaffold 然后调用它的一个openDrawer
// 这样来做的
Scaffold.of(context).openDrawer();
},
);
}
),
);
}
然后在 home -> home.dart中使用
import "package:flutter/material.dart";
import 'package:project03/ui/pages/home/home_app_bar.dart';
import 'package:project03/ui/pages/home/home_content.dart';
import 'package:project03/ui/pages/home/home_drawer.dart';
import "../../../core/extension/int_extension.dart";
class HYHomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: HYHomeAppBar(context),
body: HYHomeContent(),
drawer: HYHomeDrawer(),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7jjEuuCf-1589760752542)(A05BD1E1DCED4F4FBF7895B010705C42)]
同样可以正常使用
然后我们来写一下这个页面
home -> home_drawer.dart
import "package:flutter/material.dart";
import "../../../core/extension/int_extension.dart";
class HYHomeDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 250.px,
child: Drawer(
child: ,
)
);
}
}
child就是放在里面的东西
这里我范了一个错 我们需要将 这个Container放在某个一容器里面使用
如果不放在某一个容器里面 它就会占据整个Drawer
import "package:flutter/material.dart";
import "../../../core/extension/int_extension.dart";
class HYHomeDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 250.px,
child: Drawer(
child: buildHeaderView(context),
)
);
}
Widget buildHeaderView(BuildContext context) {
return Container(
width: double.infinity,
height: 150.px,
color: Colors.orange,
child: Text("开始动手", style: Theme.of(context).textTheme.display3),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ozj3EmkT-1589760752544)(A12A6A9CF24D420F9D8751A74DE66F23)]
所以我们这里需要将他改一下 嵌套一个Column
import "package:flutter/material.dart";
import "../../../core/extension/int_extension.dart";
class HYHomeDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 250.px,
child: Drawer(
child: Column(
children: <Widget>[
buildHeaderView(context),
],
),
)
);
}
Widget buildHeaderView(BuildContext context) {
return Container(
width: double.infinity,
height: 150.px,
color: Colors.orange,
child: Text("开始动手", style: Theme.of(context).textTheme.display3),
);
}
}
这样我们就可以 定义它的高度
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yYvJ8BRS-1589760752546)(38F7465373BC4333810C6021A78399BF)]
然后我们要调整一下这个位置
Widget buildHeaderView(BuildContext context) {
return Container(
width: double.infinity,
height: 150.px,
color: Colors.orange,
alignment: Alignment(
0, .5
),
child: Text("开始动手", style: Theme.of(context).textTheme.display3),
);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Gk8snVZf-1589760752547)(F6AC360863824CDDBADF8AD6391D6D4E)]
这个字体是有点小的
我们可以给字体再添加一个更大的字体
在app_theme.dart里面再添加一个
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TiPnNE7n-1589760752549)(48286373BAD542999509327B3C9C4191)]
改了主题以后要 热重启
Widget buildHeaderView(BuildContext context) {
return Container(
width: double.infinity,
height: 150.px,
color: Colors.orange,
alignment: Alignment(
0, .5
),
child: Text("开始动手", style: Theme.of(context).textTheme.display4),
);
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7Lff199u-1589760752551)(0AA865703E6E49CF8A412D7DF38100F5)]
但是我们发现这个字体有点小
给他加粗体
Widget buildHeaderView(BuildContext context) {
return Container(
width: double.infinity,
height: 150.px,
color: Colors.orange,
alignment: Alignment(
0, .5
),
child: Text("开始动手", style: Theme.of(context).textTheme.display4.copyWith(fontWeight: FontWeight.bold)),
);
}
同样可以给他加在主题上
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SWiu9tdq-1589760752553)(51B146CE609F45288DB653D232F2E149)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MjDDV0bT-1589760752554)(1133192479F04799A6160D18916A0BC3)]
一样的
然后就是再往下做
Widget buildListTile() {
return ;
}
我们知道这个ListTile是在 ListView里面使用的 但是这个ListTile也是可以再其他地方使用
它就是一个简单的Widget
import "package:flutter/material.dart";
import "../../../core/extension/int_extension.dart";
class HYHomeDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 250.px,
child: Drawer(
child: Column(
children: <Widget>[
buildHeaderView(context),
buildListTile(Icon(Icons.restaurant), "进餐"),
buildListTile(Icon(Icons.settings), "过滤")
],
),
)
);
}
Widget buildHeaderView(BuildContext context) {
return Container(
width: double.infinity,
height: 150.px,
color: Colors.orange,
alignment: Alignment(
0, .5
),
child: Text("开始动手", style: Theme.of(context).textTheme.display4),
);
}
Widget buildListTile(Widget icon, String title) {
return ListTile(
leading: icon,
title: Text(title),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-49eVmgLT-1589760752556)(43D47ACF02F2455AB45CE655D902D44A)]
传context然后将字体样式改一下
import "package:flutter/material.dart";
import "../../../core/extension/int_extension.dart";
class HYHomeDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 250.px,
child: Drawer(
child: Column(
children: <Widget>[
buildHeaderView(context),
buildListTile(context, Icon(Icons.restaurant), "进餐"),
buildListTile(context, Icon(Icons.settings), "过滤")
],
),
)
);
}
Widget buildHeaderView(BuildContext context) {
return Container(
width: double.infinity,
height: 150.px,
color: Colors.orange,
alignment: Alignment(
0, .5
),
child: Text("开始动手", style: Theme.of(context).textTheme.display4),
);
}
Widget buildListTile(BuildContext context, Widget icon, String title) {
return ListTile(
leading: icon,
title: Text(title, style: Theme.of(context).textTheme.display2,),
);
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ttn5YY4-1589760752558)(9B987E2850714056AEB2492CB8FD1820)]
然后我们希望这个 ListTile和 上面的这个东西有一定的间距 所以我们最好 加一个SizedBox当然也可以给HeaderView这个东西加一个margin
然后就是这个要做一个跳转
ListTile这个东西本来就是可以监听这个事件的
Widget buildListTile(BuildContext context, Widget icon, String title) {
return ListTile(
leading: icon,
title: Text(title, style: Theme.of(context).textTheme.display2,),
onTap: () {
},
);
}
我们可以将这个操作当成参数 传过来然后回调
这个抽屉的弹出并不是用 Scaffold来做操作的 它使用路由的弹出来将这个东西弹出栈顶来实现的
import "package:flutter/material.dart";
import "../../../core/extension/int_extension.dart";
class HYHomeDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
width: 250.px,
child: Drawer(
child: Column(
children: <Widget>[
buildHeaderView(context),
buildListTile(context, Icon(Icons.restaurant), "进餐", () {
Navigator.of(context).pop();
}),
buildListTile(context, Icon(Icons.settings), "过滤", () {
})
],
),
)
);
}
Widget buildHeaderView(BuildContext context) {
return Container(
width: double.infinity,
height: 150.px,
color: Colors.orange,
margin: EdgeInsets.only(bottom: 20.px),
alignment: Alignment(
0, .5
),
child: Text("开始动手", style: Theme.of(context).textTheme.display4),
);
}
Widget buildListTile(BuildContext context, Widget icon, String title, Function handle) {
return ListTile(
leading: icon,
title: Text(title, style: Theme.of(context).textTheme.display2,),
onTap: () {
handle();
},
);
}
}
还有一个东西就是过滤了
但是这个东西 设置到 过滤数据 所以这个东西放到下面的来说
但是这里并没有遮挡这个tabbar
- 如何给这个首页添加一个蒙板
- 可以再首页的地方不直接返回这个 页面而是返回一个 Stack 然后 第一个Container 然后第二个就是Scaffold 来做这个效果
- 界面的这种需求 因该有很多的方法可以做的