Flutter学习笔记(coderwhy)

万物皆是Widget

一般缩进2个空格

文字居中 Widget Center()

MaterialApp使用Meterial风格

Scaffold脚手架 帮助我们快速的搭建页面
传appBar body

刚开始分不清那里写, 那里写;
一条语句结束之后写的是;
属性直接用,

ctrl + R 启动

Widget:
StatefulWidget有状态的Widget: 在运行过程中有一些状态(data)需要改变
StatelessWidget 内容是确定没有状态(data)的改变

先用StatelessWidget

build方法
build方法什么时候被执行?

重构
一行代码, 箭头函数

  1. 最终形成的是Widget树
  2. 组件化开发的思想 跟VUE、React很像
    VUE、React: 样式和结构进行分离
    代码规范, 划分清晰

Center居中
Row相对于Center是居中的
Row的内容还要居中

CheckBox
@required: 必须的, 注解告诉某些可选命名参数是必须的, Flutter中才有
命名可选参数对顺序没有要求, 可忽略顺序

点击的时候发生改变
var flag = true;
本身就是错误的代码
This class immutable不可变的类
@immutable关键字, 告诉当前这个类一旦创建后定义的所有的东西都是不可变的使用final
StatelessWidget: 无状态的Widget
StatefullWidget:

flag: 状态
android/iOS 命令式变成, 不说状态, 属性、数据
VUE... 声明式变成 管好状态
flutter开发中, 所有的Widget都不能定义状态
StatefulWidget是Widget也不能定义状态 -> 创建一个单独的类, 这个类负责管理状态
抽象方法必须实现

没有命名冲突, this可以省略
setState(){};回调函数中改变状态, 监听改变, 刷新界面
React虚拟DOM
当前的State里面继承过来的 本质是this.state()

20.3.2

Flutter借鉴了很多React的思想
构造方法传入很多的参数
StatelessWidget案例
StatefulWidget案例
垂直排布Column
所有的数据暂时都是写死的
child, 单个
children, 多个
展示的数据是不一样的 -> 需要传过来一些参数
Widget中定义的所有成员变量应该是final的
显示网络图片, 异步加载, 有专门的IO线程 Image.network(imageURL)
下面显示一个黄条, Flutter里面的重点
黄条: 在Fultter里面, 内容超出了屏幕的可显示区域,
而你又没有设置父Widget滚动, 就会显示
安全区域 relayoutBoundary布局边界
Flutter中的布局跟iOS、Android不一样
解决办法, 超出后变成可滚动的Widget
ListView, 传入children
变量放在不同的地方, 不同的含义, build执行的时候就会创建变量

希望有间距
Flutter, 中间插入组件, 有很多种办法
SizedBox(height: 8), 相当于8个像素, 像素适配rpx, 相当于iOS中的点像素

边框
给Column包裹一个Container
decoration 装饰
BoxDecoration boder
Border.all(
width,
color
)

内容都是自动居中显示
不想让居中显示,
文本 textAlignLTextAlign.right 不起作用
不是因为文本, 包裹内容, 只有那么大
Column, 主轴上面垂直排布,
交叉轴 crossAxisAlignment
flex布局
Column、Row、Flex
Column垂直方向就是一个主轴
Row水平方向是主轴
Flex决定主轴和交叉轴

StatefulWidget做个案例
计数器案例, 有状态的改变 -> StatefulWidget
抽象方法createState返回StatefulWidget
做开头, 在其他库不能访问
Widget是不加下划线
, 暴露给别人使用
State是加下划线_: 状态这个类只是给Widget使用, 自己使用

为什么Flutter在设计的时候StatefulWidget的build方法放在State中

  1. build出来的Widget是需要以来State中的变量(状态/数据)
  2. 在Flutter的运行过程中, Widget是不断地销毁和创建的,
    当我们自己的状态改变时, 并不希望重新创建一个新的State

Widget相当于描述信息

child, children
只能放一个元素, 放数组多个元素

抽取方法, 返回Widget

Cloumn占据的是屏幕整个高度, 设置主轴居中

_counter+=放的位置,
看setState的实现
提前两三行或延后两三行代码执行

可以管理自己的状态

全局变量, 整个应用程序全部使用StatelessWidget来构建
不合理, 全局状态变得非常庞大, 耦合度太大
属于自己的状态, 自己管理

StatefulWidget使用父Widget传来的数据
this.widget
普通情况下this都可以省略, 有冲突的时候不可以省略

生命周期

hook回调, 钩子函数, 某一刻调用这个函数
生命周期的函数对于开发者意义是什么?

  1. 初始化一些数据, 变量, 状态
  2. 发送网络请求, 要知道widget什么时候创建完了创建网络请求
  3. 进行一些事件的监听, controller添加监听事件
  4. 管理内存: 一些定时器、controller手动进行销毁

Flutter里面只需要监听Widget的生命周期

StatelessWidget的生命周期

StatelessWidget只需要监听构造函数、build方法被调用, 很少监听
调用了2次, AndroidStudio的bug?
VSCode运行只会调一次

StatefulWidget的生命周期

开发中用的最多的就是StatefulWidget的生命周期
不是一个类, 快捷stful
生成两个类

  1. Stateful Widget Widget.createState()
  2. State object
  • constructor ...-> initState ...-> build ...-> dispose

init的super必须调用
初始化
不调会报警告, 注解限制 @mustCallSuper

setState做一个标记, 重新显示
小部件销毁和重建的过程

didChangeDependence

点击底部加号, 添加HYHomeContent, 让父Widget结构发生改变

final指向的引用不能改变, 里面是可以增加数据的

生命周期复杂版

dirty sate markNeedsBuild()
setSate中dirty false改成true build执行

20.3.4 三

  1. MaterialApp home可以是StatefulWidget

  2. StatelessWidget改成StatefulWidget

  1. 将build出来的widget抽取到一个单独的Widget中
  1. JHHomeContent打印了很多的生命周期函数
    官网文档
    api.flutter.dev/flutter/widgets/State-class.html
    didUpdateWidget 8:10-8:22 网络不好

  2. 在Android Studio里面运行Flutter项目多打印
    VSCode中不会执行两次

二. Flutter的编程范式

  1. 命令式编程:一步一步给计算机命令, 告诉它我们想做什么事情
    一步一步设置frame
  2. 声明式编程: 描述目标应该长什么样子
    依赖一些框架: Vue React Flutter
    配置信息
    Column告诉框架垂直排布
    特殊的地方写好数据

前端的编程范式

jquery、原生JS 命令式编程
VueJS 声明式编程
vue、react、angular 声明式编程
2009年开始, 声明式编程就开始流行起来
SwiftUI, 苹果开发模式转向声明式编程
声明式编程更简单, 逻辑更清晰
慢慢来体会

基础的Widget

1.1 文本Widget

Text() 不能设置宽度
不居中是因为没有不存在宽度
maxLines: 2, 最大行数
overflow: TextOverflow.ellipsis, 最后显示...
textScaleFactor: 1.5, 缩放1.5倍
TextStyle

1.2 富文本显示

Text Widget -> RenderWidget 不是最终渲染的Widget
RichText widgets require a Directionality widget ancestor.
最终渲染的是build出来的东西
RichText extends MultiChildRenderObjectWidget
开发中: 富文本

Text.rich(
    TextSpan(

    )
)

咸鱼boost_flutter框架

2.1按钮的基础

RaisedButton凸起的Button
必传参数和@required: 必传参数不穿就会报错(编译不通过)
@required编译可以通过, 但是会报警告
onPressed: () {}, 不传是disabled
两种办法改变颜色

FlatButton, 扁平的按钮

OutlineButton, 边框按钮

FloatingActionButton, 悬浮的按钮, 一直浮着

既有图标又有文本的按钮, 自定义Button
FlatButton(
child: Row(
children:
)
)
FlatButton为什么占据整行, Row占据整行
mainAxisSize: MainAxisSize.min,
Column主轴, 交叉轴
圆角: ShapeBorder 抽象类
RoundedRectangleBorder(
borderRadius
)

MaterialButton
Button也不是一个直接渲染的Widget

三. 图片Widget

Image.network/asset
ImageProvider 抽象类, 看看有哪些实现类
AssetImage
NetworkImage
抽象类在什么情况下实例化? 有工厂方法的时候, 工厂方法里面去调用另一个子类的东西
width: 200, 设置图片宽度
height: 200, 设置成200, 看起来不是200, 填充模式
fit: BoxFit.fill,
fitWidth 宽度一定, 高度自适应
fitHeight 高度一定, 宽度自适应
alignment: Alignment.centerLeft, 对齐
Alignment(x, y) 左上角(-1, -1), 右下角(1, 1) 中心(0, 0)
(0, -2), 跑出去了
color: 颜色混入
fit 和 repeat一起使用, 重复填充

加载本地图片
image: AssetImage("本地图片地址"),

  1. 在Flutter项目中创建一个文件夹, 存储图片
  2. 在pubspec.yaml进行配置
    assets:前的空格要删除
    要执行Packages get
  3. 使用图片

assets文件夹
iamges文件夹
fonts
iOS图片类型怎么区分?
@1x放在外面
2.0x文件夹
3.0x文件夹

44行 注释打开, 空格删除
assets:
- assets/images/juren.jpeg
- assets/2.0x/juren.jpeg
- assets/3.0x/juren.jpeg
执行 flutter packeges get 依赖重新安装一下
没有配置2x图片就删除依赖

  • assets/images/ 通用, 加载里面所有的资源

20.3.6 五

Flutter三层树结构
Element
知识内容

  1. Button-Image-TextField
    Button: 小知识点的补充
    Button的小间距, 没有完全放在左上角
    默认有间距, 属性 MaterialTapTargetSize 默认48 垂直方向
    设置成紧缩, shrinkWrap
    Column中心对齐 文本1和2占据的空间不一样
    默认情况下Button上下有一定的间距

没有内容的时候Button有尺寸, Button如何变小
文档看默认值
Flat buttons have a minimum size of 88 36
继承自 MaterialButton build方法
上下文包裹Button
ButtonTheme(
minWidth: 30,//最小宽度
height: 30
)

去除Button的内边距

Image补充2个知识点

  1. 占位图的问题
    FadeInImage默认淡入淡出效果
    milliseconds传0会报错, 传1

  2. 图片缓存问题
    flutter默认会对图片进行缓存
    图片和缩放都一致的时候, 直接使用之前的图片
    最多缓存1000张图片, 最大100M
    iOS中内存占用内存过大, iPhone直接杀死App

Icon的补充
IconData
Icon字体图标和图片图标

  1. 字体图标矢量图(放大的时候不会失真) size: 300
  2. 字体可以设置颜色
  3. 图标很多时, 占据的空间会更小
    自己来创建IconData, 0xe91d 传进来的是16进制数字
    Text("0xe91d"), 不能显示图标?
  • 0xe91d -> unicode编码 \ue91d
  • 设置对应的字体 fontFamily

TextField
Dart中所有类继承自Object
Material风格

点击登录获取输入框里的东西
SizedBox调整间距
怎么设置按钮的宽度和高度? 没有width和height
Container包裹按钮, 按钮子元素填充Container

  1. 获取用户名和密码
    声明式编程, 很少专门搞一个引用 => Controller
    全局变量里面 usernameTextEditController
    TextField的controller属性
    边框颜色 Theme()包裹, child中设置TextField data:

Color white 颜色常量
Color()xff ff ff ff)
Color.fromRGBO()
Colors.red[100], 为什么有[], => 运算符冲在 ColorSwatch中

FlatButton设置颜色没用

叶子Widget
所有的Widget最终形成一个树
LeafRenderObject

2. 布局Widget

官方文档, 布局组件特别多
Flutter很少用Rect设置位置

Align

Center的源码 常量构造方法 传给的所有属性都传给了父类Align, 没有做任何的改变
为什么搞个Align就居中了?
Align是占据屏幕整个区域的
单子组件
alignment: Alignment(0, 0)中心点 左上角-1, -1, 支持小数
Container包裹Align, Align设置widthFactory没反应
widthFactory child的size倍数

Center相当于不设置AlignAlignment的值

Padding

Dart2.0+ const可以省略
文字行高的概念
EdgeInsets.all()全部加内边距
EdgeInsets.symmetric(vertical: 5)
EdgeInsets.only(bottom: 10)

Container

child是多大Container就是多大
Flutter的布局方式, 最终形成的是一个树结构
RenderWidget(父Widget) -> RenderObject
constraints: 约束 BoxConstraints(minWidth: maxWidth ...) 传递给子组件
子组件根据父组件给的约束调整自己的大小, 之后报告给父组件调整大小, 超出会出现黄色警告
不同的子组件有不同的表现
alignment
padding设置边距
margin: EdgeInsets.all(10) 外边距
transform: Matrix4.rotationZ(50) 旋转 工厂构造方法

decoration: BoxDecoration() 跟color属性冲突, 只能提供一个
提供一个color的时候本质是在创建decoration
BoxDecoration(
color: ,
border: Border.all(
width:
color
),
borderRadius: BorderRadius.circular(50),//设置圆角
boxShadow: [
BoxShadow(color: Colors.blue, offset: Offset(10, 10), spreadRadius),
BoxShadow(color: Colors.red, offset: Offset(10, 10), spreadRadius)
]
)

Container是很多组件的大杂烩

如果没有设置alignment,

20.3.9 一

多子布局组件

2.1 Flex组件

Flex Widget
Row和Column继承自Flex
Flex: CSS Flex布局
必传参数direction
direction: Axis.horizontal: Row
direction: Axis.vertical: Column
开发中直接使用Flex比较少
多直接使用Column、Row

主轴和交叉轴的概念

主轴: Main Axis
交叉轴: Cross Axis
剩余的间距平均分给Row的子组件
以前用浮动float来做 -> Flex布局

mainAxisAlignment: 6个值
start(默认): 沿着主轴的开始位置, 紧挨着排每一个组件
end: 沿着主轴的结束位置, 挨着摆放组件
center: 主轴的中心点对齐
spaceBetween: 将左右两面的间距为0, 其他组件之间平分间距
spaceAround: 左右两边的间距, 是其他组件之间的间距的一半
spaceEvenly: 所有的间距平分空间

Row的特点: 水平方向尽可能占据比较大的空间, 垂直方向包裹内容
水平方向包裹内容
-> 设置mainAxisSize = min; (默认max)
设置后就没有剩余空间

CrossAxisAlignment:
start: 交叉轴起始位置对齐
end: 交叉轴的结束位置对齐
center(默认值): 中心点对齐

baseline: 基线对齐, (必须有文本的时候才起效果)
文字的排版, 文字的行距, 文字排版有很多的线, 顶线/底线, 之间是行高
line-height: 行高高于文字的高度text-height
(line-height - text-height) / 2
vertical-align:
文字下沉, x最底部的线是基线, 不是底线 alphabetic ideographic

stretch: 先让Row占据交叉轴尽可能大的空间, 将所有的子组件交叉轴的高度, 拉伸到最大
用Container包裹一个Row限制最大拉伸高度

Column
verticalDirection VerticalDirection.up, 从下到上排
垂直方向上很少用基线对齐

textDirection: TextDirection.rtl,阅读方向改成从右往左, 这个属性用的少

需求: 剩余的宽度全部分给第一个红色的组件

  1. Expanded
    包裹一个Flexible, 里面的属性flex
    fit: FlexFit.tight, 默认loose
    多个Flexible, 会预留一些空间
    比例的问题, 包裹的组件的宽度一致, 等分
    flex: 1, 只和flex有关系, 跟宽度没有关系

Expanded(用的更多) -> Flexible(fit: FlexFit.tight)
超出区域出现黄色条状警告
用Expanded包裹, 避免警告

2.3 Stack组件

默认情况下, 不会出现组件之间的重叠
Stack默认的大小是包裹内容
alignment: AlignmentDirectional.center,// 从什么位置开始排布所有的子Widget
fit: StackFit.expand, // 默认loose 很少用 将子组件拉伸到尽可能大
overflow: Overflow.visible, 超出部分如何处理 超出可显示
Positioned 布局Widget

hasSize错误

20.3.11 三

点击按钮, Icon变成红色
StatelessWidget -> StatefulWidget
变量做记录, 真实开发中用模型来记录防止影响其他数据
setState() {

}
让界面刷新
能用StatelessWidget尽量使用StatelessWidget

滚动的Widget

  1. ListView 列表
  2. GridView 网格, 九宫格
  3. sliver 分片 本质上是Sliver
  4. 滚动的监听

先不讲JSON的解析

ListView组件

  • 默认构造方法: ListView() children比较少的时候使用, 一次创建
  • ListView.builder() 即将展示的时候创建
  • ListView.separated()
  • ListView.custom() 需要传入代理

List.generate(100, generator()) List的构造函数
Flutter提供的 ListTile
总高度超过界面高度, 自动滚动
Flutter实现滚动, 需要包裹ListView
水平滚动需要确定Item宽度 itemExtent 范围(高度/宽度)
reverse: true, 反转

ListView.builder()
itemBuilder: () {
}

ListView.separated()
separatorBuilder: () {}
Divider 分割线
一般传入回调函数的时候, 都是等到需要的时候自动调用回调函数

GridView

  • GridView()
  • GridView.count()
  • GridView.extent()
  • GridView.builder()

gridDelegate: ,必传参数
SliverGridDelegateWithFixedCrossAxisCount
固定的个数, 宽度根据不同的屏幕改变
Random().nextInt(256) 随机数
高度怎么确定?
childAspectRatio: ,宽高比
crossAxisSpacing: 8, 交叉轴间距
mainAxisSpacing: 8, 主轴间距
两边缘间距, 包裹一个Padding
padding: EdgeInsets.symmetric(horizontal:8)

传入宽度, 不同的屏幕放的个数不同,
SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 100, 最大宽度, 414 100放5个

)
Item

count()

sliver

学习方法
ListView, GridView -> buildSliver抽象方法, 子类实现
extends BoxScrollView 不是Widget就是一个普通的类
extends ScrollView
extends
看build方法 返回了一个Scrollable
继承关系理清楚
本质是sliver

CustomScrollView()
slivers
dart/math包 随机数
安全区域 刘海SafeArea包裹
Sliver的安全区域 SliverSafeArea() 默认情况安全区域内显示, 可以滚动到刘海

内边距出现了覆盖, 不想要这个效果
不用Padding -> SliverPadding
SliverAppBar

滚动的监听

两种方式可以监听:
controller:
1. 可以设置默认值offset
2. 监听滚动, 也可以监听滚动的位置
NotificationListener
1. 开始滚动和结束滚动
ScrollController

作业:
Body的Widget抽取了, 监听的东西

20.3.13

异步
网络请求
越来越流行的异步处理方式
JS、Dart 单线程+事件循环
一个应用程序大部分时间都是处于空闲的状态, 并不是无限制的在和用户进行交互
非阻塞式调用
操作系统 文件操作

  • 阻塞式调用 非阻塞式调用
    并不会等待结果, 直接返回, 不阻塞当前线程
    维护了"事件循环", 将需要处理的一系列事件, 放到一个Event queue中

对Future认识

发送一个网络请求
import 'dart:io';
sleep();

Future(){
//耗时操作的代码
return "结果";
}
抽象类看工厂构造方法
联合类型

Future的then函数
没有指定类型的时候是dynamic
then后面的回调函数什么时候被执行?
需要在Future(函数)有结果, 财智星下面的回调函数
Future源码里面是各种各样的回调

  1. 只要有返回结果, 那么就执行Future对应的then的回调(Promise - resolve)
  2. 如果没有结果返回(有错误信息), 需要在Future回调中抛出一个异常(Promise - reject)
    throw Exception;
    抛出异常, 没有处理异常, 程序报错
    catchError((err){})
    代码执行完成
    whenComplete((){})

链式调用
写开不可以, 程序崩溃

Future的链式调用

中间发生了异常
链式调用解决"回调地狱"

Future的其它API

Future.value('哈哈哈').then()(res) {
print(res);
};
Future.error('错误信息')

延迟执行
delayed()

关键字 await、async

同步的代码格式去实现异步的调用过程

解决两个问题:

  1. await必须在async函数中才能使用
  2. async函数返回的结果必须是一个Future

语法糖, 做了一个包裹

sleep操作, 即使是Future也会做一个等待

拿到第一次的结果, 做参数拿到第二次结果
模拟链式调用 -> async、await

Dart的异步补充

import 'rootBundle'
多核CPU的利用
一般前端不要处理太多非常复杂的数据, 交给后端处理
后端处理逻辑只要写一次各端都能用

Dart开启另一个线程
Isolate(隔离)的概念
Root Isolate
每个Isolate都有自己的Event Loop 和Queue

创建Isolate

Isolate.spawn(calc, 100);
把函数传进去 函数的参数
在一个新的线程中执行

Isolate之间的通信

双向通信

  1. 创建管道
  2. 创建Isolate
  3. 监听管道

Dart主要是单线程的,
Flutter是单线程的吗? 至少是4个线程
UI Runner
GPU Runner
IO Runner
Platform Runner

网络请求

pub.dev搜索dio库
所有规范的第三方库, 都有一个主文件, 做导入

  1. 创建dio对象
  2. 发送网络请求

真实开发中

  1. 参数 - 拦截器 (对网络请求进行封装)
  2. 在开发中只要用到第三方库, 建议大家都进行一层封装

考虑到换库
20个Widget都需要换?
iOS 网络请求ASI库不更新 -> AFN
HttpTools依赖第三方库

dart文件一般用_进行分割
类名用驼峰
命名可选参数, 默认get
Flutter的习惯, 不直接搞常量, 创建一个类, 里面放常量
生产环境/开发环境

全局拦截器
dio.interceptprs.addAll

dependencies:
dev_dependencies: 开发时依赖, 不会打包进去

20.3.16 一

豆瓣电影列表界面

  1. 对前面内容的回顾
  2. 对一些Widget, 其他知识点补充
  3. 代码的结构如何进行组织

异步compute函数执行报错
原因: compute接收的回调函数要是全局的, 不是对象的方法
没有对象, 不能执行对象方法

启动文件main.dart配置
Edit Config...

豆瓣两个难点, 先做封装

  1. 评分五颗星显示
  2. Flutter边框不支持虚线
    抽成独立的组件, 以后也可以复用

1. 评分Widget的封装

具备通用性的特点
评分控制星数
星的颜色
星的大小

状态有可能发生变化, 用StatefulWidget

如何封装的呢?
绝对定位
Stack, 元素可以重叠
搞2个Widget横向重叠 => Row
按比例裁剪

需要传过来的参数
分数(必传) rating
满分(可以给默认值) maxRating
展示多少个星(可默认) count
星的大小(可默认) size
未选中颜色(可默认) unselectedColor
选中颜色 selectedColor

颜色16进制 Color(0xffbbbbbb)
默认值const

List.generate(widget.count, )
buildUnselectedStar()

  1. 创建stars

  2. 构建满填充的star
    满的星
    向下取整 floor
    向上取整 ceiling

  3. 构建部分填充star
    ClipRect()
    自定义子类
    抽象方法, 子类必须实现
    当成一个矩形裁剪
    getClip()

要不要重新裁剪, 宽度不一致在重新裁剪
shouldReclip()

如果传入图片使用传入图片, 没有传入使用默认图片 ??

完成功能和复用性

创建dart文件用下划线命名
star_rating.dart

虚线的封装

实现目标
提供定制
确定虚线的方向
虚线的宽度
虚线的高度
多少个虚线, 密度
虚线的颜色

只是负责展示的 => StatelessWidget

DashedLine
final Axis axis; 必须传或给个默认值
final double dashWidth;
final double dashHeight;
final int count;
final Color color;

SizeBox来做

用不上的用_替代

默认情况下SizeBox没有color属性
写个child: DecoratedBox
decoration: BoxDecoration(color: color)
)

TabBar实现说明

分析:
home属性直接改不合适
Scaffold: body -> IndexedStack
切换就是一个状态改变 -> StatefulWidget

重启重新打包时会重新打包资源

代码抽取

  1. 抽成一个函数
    Widget buildBottomItem() {}

  2. 抽成一个类
    items有具体的要求
    class JHBottomBarItem extends BottomNavigationBarItem {

}
创建一个bottom_bar_item.dart文件
文字不见的原因? 超过4个的时候, 有一个属性控制文本隐藏

initialize_items.dart文件
单独的文件里做管理

引用方式
绝对路径 package:
相对路径

home.dart是StatelessWidget
home_content.dart
应该是StatefulWidget

ui文件夹

core文件夹
网络
工具

文本选中时的颜色用的是主题的颜色
ThemeData()

BottomNavigationBar中
选中和未选中的字体大小不一样
main.dart中
unselectedFontSize 默认12
selectedFontSize 默认14
设置成一致

默认水波纹高亮效果
MaterialApp()
highlightColor:Colors.transparent

20.3.18 三
豆瓣列表实现
网络请求JSON转Model

ListView.builder创建LiveView
itemBuilder: (ctx, index) {} 类型可以省略

网络请求的封装, 封装精细点, 每个对应的页面发送网络请求
直接在页面里面使用工具类?
每个里面都封装一个模块
好处: 在模块里面完成转化过程, 更加面向对象
home_request.dart 首页的请求都在这个文件里面封装
config.dart里面放请求HOST地址
请求了多次

  1. 构建URL

  2. 发送网络请求获取结果 await 配合 async使用
    subjects数组存储请求结果
    JS里面直接用subjects数组了
    面向对象的开发, 转成模型对象

  3. 将Map数据转成Model
    手动进行转化, 看需要的参数, 属性定义模型
    优点: 完全可以控, 获取自己需要的, 继承关系一目了然
    缺点: 麻烦, 可能出错
    暂时使用手动转换

报错dynamic转化

电影No.1排序, rank 加上的字段

快捷键: 直接生成ToString方法 cmd + n

ListTile换成自定义的Widget
信息特别多封装成Widget movieItem.dart
只有在首页使用, 把封装好的widget放在home文件夹中
在另外的界面也会用到, 封装到widgets文件夹里面

状态不会变化, 数据展示是静态的数据, => StatelessWidget

只是在首页中使用, 命名 JHHomeMovieItem

封装分析
Widget树结构
先包裹一个Container, 设置背景颜色方便, 设置内边距方便, 扩展性更强
垂直排布 => Column
Container
Container
Row
Container
Widget -> 渲染时是RenderWidget 性能好
xib存在的问题该需求, 嵌套, 布局改起来麻烦
各有利弊
Flutter写熟练之后, 写布局是更简单的
Auto Layout手写起来更难
CSS写布局最好写, 布局和结构完全分离
Flutter布局和结构写在一起
前端朝着声明式发展
await请求出错抛出异常catch中拿到异常
目前没有比较好用的JSON解析库

调试先用占位的Text()
简单的优化
交叉轴对齐方式设置
padding做间距
底部间距 Container加边框
decoration
写很多布局, 代码量多, 嵌套层级深 => 函数封装

  1. 头部的布局 buildHeader()
    文本边缘有内边距
    有背景颜色, 有圆角 color和decoration不能共存
    BoxDecoration(
    color: Colors.fromARGB
    borderRadius:
    )

  2. 内容的布局
    buildContent()
    水平排布Row
    确定的宽度, 中间适应宽度 => Expanded来做
    东西有点多, 再划分一下

2.1内容图片
Widget buildContentImage()
图片默认按照比例显示
给图片圆角 => 很多种方式设置圆角, 比较简单的 => ClipR(Radius)Rect
忘记了多去看源码多去查文档, 学习方法

2.2内容信息
Widget buildContentInfo() {}
Row默认交叉轴垂直对齐 => 顶部对齐

嵌套多, 结果清晰 => 代码划分
buildContentInfoTitle()
playDate, json转模型, 自己看数据

Row之后的小问题, 不能换行, 只能在一行显示
解决办法 => 换成Text.rich

buildContentInfoRate()
Column交叉轴居中对齐 => 改对齐方式成start
SizedBox(width: 6)横向间距

buildContentInfoDesc()

  1. 是一段文本, 字符串拼接 空格分隔
    join自己查源码
    超出边界, 要显示两行
    报错原因, 整个东西是个Row, 没有固定宽度
    没有宽度的情况下是内容的宽度
    Expanded包裹Column

2.3内容的虚线
buildContentLine()包裹一个Container, 给个固定的高度
child: JHDashedLine
传好方向

2.4内容的想看
buildContentWish()

  1. 底部的布局
    buildFooter()
    背景颜色里面有个文本
    Container

上拉下拉监听滚动

toList转换

item默认自适应高度
动态控制item里面的widget的显示与隐藏
if判断, 添加

json转模型官方

一个网站转换, 自动生成.dart文件
复杂的数据结构会漏掉一下东西, 没有导演这个类
单独找到, 单独转换

编辑器插件AS FlutterJsonBeanFactory
也有问题 int 1; int 2;
没有网页好用

20.3.20

临时加一节课
Flutter的Widget-Element-RenderObject
很多都是理论, 面试也会问

BuildContext
Widget频繁的创建会不会影响性能?
StatefulWidget两个类 Widget、State的关系
Key可选参数, 有什么样的作用?

上节课的疑惑, 豆瓣补充

豆瓣, 电影名前的图标文字对的不是很齐
WidgetSpan 放上Icon TextSpan TextSpan
三个没有居中对齐, 怎么做对齐
三个东西全用WidgetSpan包裹 alignment属性
也可能和CSS文字下沉类似

电影名字太长, 三个东西没有放在一行
一个WidgetSpan本身就是一个整体, 要么同一行显示, 要么换一行
奇淫技巧, 每个文字单独拿出来, 每个文字转成WidgetSpan 编码runes,在一个数组中
每一个编码转成WidgetSpan 最后toList(), 转成列表
...展开, 类似ES6
dart2.3.0开始不再支持这种写法, 还可以这样写
开发中建议使用其他方法

底部itemBar第一次点击出现闪烁, 再点击就不闪了
gapless
如何解决问题?

  1. 尝试真机会不会闪烁(也会闪烁)
  2. 尝试图片换成图标会不会闪烁(图标不会闪烁)
    => 用了图片, 图片的特点, 加载的时候会稍微慢点, 图片读到内存, 渲染
    => 点到图片的属性里, 关于图片加载的, => gaplessPlayback默认false
    false时: 图片删掉 显示空白 加载上图片
    true时: 中间没有空白间隔, 一直显示原来的图片,

代码调试的时候, 打印代码行数
打印所在文件、所在行
自己封装一个小工具log.dart 正则表达式去匹配文件及所在行数, 调用栈
jhLog StackTrace
OC file 当前文件
定义debug和release模式

虚线高度和其他如何一致?
Row包裹一层Expanded, 中间的child: IntrinsicHeight(Row)

Flutter的渲染流程

树结构
Flutter Engine要把WidgetTree渲染成最终的界面
并不直接渲染WidgetTree, 这个树结构特别不稳定
动不动就会重新调用build方法, 引擎直接解析不合适, 耗性能
最终渲染的不是WidgetTree

=> 还有一个树结构 RenderObjectTree 渲染对象
不是所有的WidgetTree都会转化成RenderTree
并不是所有的Widget都会变成RenderObject
Text StatelessWidget build方法 看
不是一个最终渲染的Widget

渲染layout - paint -
Widget 没有 layout - paint

Element Tree
React的虚拟Dom概念
Flutter借鉴了
inspiration from React😂

JS生成的HTML代码 转成真实的Dom 非常消耗性能的过程 代价昂贵
改一点代码, 每次都要操控Dom
React -> 虚拟DOM 对代码结构生成的虚拟DOM
difference修改只需要修改的东西
patch -> 真实DOM反应
最小的开销生成DOM

HTML -> 虚拟DOM -> 真实DOM

Widget Tree -> Element Tree -> Render Tree
key是否相同
最小的开销更新RenderObject

理论 -> 源码
看看在哪里创建

=> 多去查文档

组件Widget:
Text
Container() -> StatelessWidget -> Widget

渲染的Widget: 生成RenderObject
Padding extends SingleChildRenderObjectWidget
-> RenderObjectWidget -> Widget

RenderObjectWidget里面有个核心方法
createRenderObject 抽象方法
让子类实现

Element Tree在哪里创建?
只要是一个Widget, 里面都需要有个方法 createElement 子类或者父类实现
所有的Widget都会创建一个对应的Element对象, 不同Widget创建的Element对象不一样
StatelessElement
StatefulElement 有一个属性 state

  1. 自己写Widget
  2. 某些Widget中会创建renderObject
  3. 每一个Widget都会创建一个Element对象
    4.1 ComponentElement: Flutter引擎自己调用mount方法
    做了什么事情? firstBuild -> rebuild -> performRebuild
    -> 抽象类看实现 build -> _widget.build()创建Element时传进的build
    4.2 RenderObjectElement: mount方法 -> _widget.createRenderObject
    挂载
    4.3 StatefulElement: extends ComponentElement
    构造方法中 _state widget.createState()
    _state._widget = widget; 所以可以 => this.widget
    mount方法

简单的小总结:
Widget -> Element -> mount方法 ->
回头调用Widget的build(BuildContext context)方法,
renderElement -> RenderObject

-> 传的Context 就是Element

一定要看源码, 自己走一遍

Dart垃圾回收机制 V8引擎也是这样的机制
VSCode怎么开发的?
TypeScript写的 github上看 93.9%
解释器执行TS代码, -> JS 最终V8引擎执行JS代码

Widget的key

提一个需求:
界面上显示3条数据
点击按钮删除第一条数据
再次点击再删除一条
map返回的要toList()
随机的颜色
删除后颜色改了

StatefulWidget
删除后颜色没改
=> 复用
canUpdate方法
Element State 复用

diff算法
runtimeType
默认删除最后一个
如果绑定了Element key, 对比, 把不一样的删除

key的另一个作用
点击删除, 所有东西都重建
给随机的key, 强制刷新Element
每次都重新创建state
好好写一遍案例, 加深理解

key的分类

Key抽象类
LocalKey

GlobalKey
来到上一个层级JHHomePage, 增加一个按钮
点击按钮拿到JHHomeContent、State属性的值
静态变量 static 弊端 属于类
=> homepage中
final GlobalKey homeKey
创建Widget绑定key
本质里放了几个Map
也可以调方法
父调用子
组件之间的相互引用
evenbus
借鉴React事件总线

Flutter的设计理念很先进
命令式编程越来越少
声明式编程越来越多

20.3.23 一

状态管理是声明式编程里非常重要的一个概念
命令式编程 -> 声明式编程
UI = f( state )
数据、成员变量 -> 状态
build方法展示 -> UI界面
setState()

1.2 不同状态管理分类

1.2.1 短时状态 Ephemeral state

某些状态只需要在自己的Widget中使用
计数器counter
PageView
动画记录当前的进度
BottomNavigationBar中记录当前被选中的tab
缺点: Widget树中其他Widget不能访问

1.2.2 应用状态App state

开发中也有非常多的状态列甩开多个部分进行共享

  • 用户一个个性化选项
    根据选择过滤一部分东西

  • 用户的登录状态信息
    登录信息, token, 用户信息, 多个界面都进行展示

  • 电商应用的购物车

  • 新闻应用的一度消息或者未读消息

对状态进行统一的管理和应用

1.2.3如何选择不同的管理方式

并没有一个明确的规则, 可能会升级
怎么简单怎么来

共享状态管理

2.1 InheritedWidget

counter共享管理
官方提供两种方式

  1. InheritedWidget
  2. Provider 官方更推崇

抽象方法必须实现
JHCounterWidget
updateShouldNotify 方法
封装一个静态方法获取对象

特点
继承自 InheritedWidget
必须实现update...fy方法
定义一个共享状态
通过静态方法拿到对象

共享一个counter

找到共同的祖先, 祖先包裹一个Widget
传进来context
树结构往上去找, 找到最近的对象
静态方法返回的null(有错误!) -> 返回 context.dependOnInheritedWidgetOfExactType
作用: 沿着Element树, 去找到最近的JHCounterElement
从Element中取出Widget对象

Widget里的属性都是不能改变的 -> 重新创建

返回false数据改了, 不执行didChangeDependence方法
返回true, 执行didChangeDependence方法
如何选? 看第一次传入的counter和第二次传入的有没有改变

  1. 共享的数据
  2. 自定义构造方法
  3. 获取组件最近的当前InheritedWidget
  4. 要不要回调State中的didChangeDependencies

如何找的? 点到dependOnInheritedWidgetOfExact

结构理清楚, 写一下代码
并不是很复杂, 第一次用可能觉得麻烦点

2.2 Provider

官方已开始推荐的是Provide
相当于React中的Redux
官方提供的一个第三方的东西

添加依赖

Provider怎么用?
可能共享的数据有很多

一般在最顶层, MyApp
打开箭头函数
runApp(
ChangeNotifierProvider {
child: MyApp(),
}
);

使用步骤

  1. 创建自己需要共享的数据
  2. 在应用程序的顶层ChangeNotifierProvider
  3. 在其他位置使用共享的数据
    • Provider.of
    • Consumer
    • Selector

创建一个文件夹 viewmodel或store(商店, 仓库)
MVVM架构
在这个文件夹里创建需要共享的数据

快捷键: cmd + N, 快速生成getter、setter、toString、Constructor

第三步
of 泛型方法

底层依赖InheritedWidget, 做了很多优化

Provider.of更简洁,
当Provider中的数据发生改变时, 所在的Widget整个build都会被重新执行
开发中更多的使用Consumer(相对推荐)
当Provider中的数据发生改变是, 只会重新执行传入的builder

没有必要重新构建的进行优化

  1. child: Icon优化
    Consumer还有一个参数 child
    Icon移到这里来, 点击加号, Icon就不会重新构建了
    builder中的参数child就是Consumer的参数child

  2. FloatingActionButton也不需要重新构建
    Consumer -> Selector

Selector有2个作用

  1. 对原有的JHCounterViewModel进行一个转换
  2. shouldRebuild要不要重新构建, 返回false
    shouldRebuild: (prev, next) => false,

2.2.4 MultiProvider

方式一: 多个Provider之间嵌套
弊端, 不方便维护, 扩展性差

方式二: MultiProvider
搞一个initialize_providers.dart文件
统一维护

Consumer2
...
Consumer5
Consumer6
传多个共享数据

网络请求放在Provider里面
拿到请求后, 通知一下
类似于观察者模式

第一次写的时候会不习惯,
先看源码, 后看老师的代码

20.3.25 三

事件监听
路由导航

豆瓣在5s小屏上有问题
如何修复?
中间是可伸缩的, 看Row
Row一旦布局好, 还是有一个固定的宽度
五颗星的宽度是固定死的
三种思路来解决

  1. 手动更改子Widget的大小
    FittedBox做适配, 包裹Row, 尽可能占据宽度, 可以压缩
  2. FittedBox
    更好的解决方案, 不同屏幕, 不同尺寸 Container大小变化
    后期会开一节课讲, 自己定义一个单位RPX, 把屏幕分成多少分
  3. rpx/rem/vw 100*rpx

事件监听

原始指针事件(Pointer Events):
描述了屏幕上由触摸板、鼠标、指示笔等处罚的指针的位置和移动
手势识别(Gesture Detector): 在原始事件上的一种封装

2.1指针事件

代表的是人机界面交互的原始数据. 一共有四种指针事件
Pointer的原理是什么?
hit test, 确定与屏幕发生接触的位置上有哪些Widget
冒泡
不存在取消或停止
原始指针事件使用Listener来监听
按下去的时候来电话了、闹钟响了 onPointerCancel

监听复杂事件还是需要手势

2.2手势识别 Gesture

官方建议开发中尽可能使用Gesture, 而不是Pointer
长按手势长按多久? 官方文档没有写

补充个小的知识点
黄色的Container里面放一个小的红色的Container
直接写红色会撑满
tight约束
alignment: Alignment.center
监听红色的点击也不想监听黄色的点击, 不想做冒泡
默认点击事件不是每次都会传到黄色, 偶尔会传到外面
如何解决?
官方文档有时也会出错, 翻译的是老的
不要让两个存在嵌套关系
=> Stack

跨组件事件的传递

两个Widget, Widget层级多
回调函数传好几层不合适
=> EventBus, 事件总线
订阅者模式, 通过一个全局的对象来管理
全局事件的传递和监听
第三方event_bus

需求: ...

  1. 创建全局的EventBus对象
    开发中可以搞个全局的文件夹
  2. 发出事件
  3. 监听事件的类型
    开发中一般会搞个类

一般不销毁, 只要程序运行, 就有可能用到

Flutter路由导航

前端朝着路由发展

路由管理

路由的概念由来已久, 包括网络路由、后端路由, 到现在广为流行的前端路由
核心是一个路由映射表
如: 名字detail映射到DetailPage页面等
有了这个映射表之后, 我们就可以方便的根据名字来完成路由的转发
Flutter中, Route和Navigator
页面包装在路由里面, 包裹成一个Route对象
官方的说法: An abstraction for an entry managed by a Navigator

文件夹改命名

1.2 Route

Route抽象类用的最多的MaterialPageRoute

1.3 Navigator

管理所有的Route的Widget, 通过Stack栈结构来进行管理
不需要手动创建Navigator
MaterialApp默认是有插入Navigator, 需要的时候直接使用
创建一个详情页面
默认增加了一个返回按钮

  1. 普通的跳转方式
    push、pop
    传递过来一些参数, push, 构造器直接传递参数

详情页返回数据 pop
Future的返回值
点击顶部返回按钮如何也携带数据?

  1. 自己来决定返回按钮是什么样子

  2. WillPopScope包裹Scaffold
    // 当返回为true时, 可以自动返回(flutter帮助我们执行返回操作)
    // 当返回为false时, 自行写返回代码
    onWillPop: () {
    return
    }

除了普通的跳转方式还有命名路由跳转
开发中普通的跳转方式不常用
存在弊端, 商品类App,
首页 推荐页 分类页
点击都跳转到详情页

命名路由使用

基本跳转

routes:
首页也可以配进去, home可以不写
=> initialRoute: "/"

字符串常量, 很容易写错, => 定义成常量

  1. 把所有的常量放在一个文件夹里
    把路由的名称定义常量
    页面里static const String routeName = "/about"
    代码规范

参数传递
多个参数, 放到对象里面

跳转时需要动态参数
钩子函数 onGenerateRoute

onUnknownRoute

跳转到一个统一的错误页面
跳转到不存在的设置页面

代码规范
router文件夹
router.dart
class Router {
static final Map<String, >
}
两个好处,

  1. 使用的地方代码间距
  2. 方便增加路由映射

20.3.27

Flutter实现动画

Flutter有自己的渲染闭环

一. 动画API认识

1. Animation

抽象类
监听动画值的改变
监听动画状态的改变
value
status
addListener
removeListener
addStatusListener

2. AnimationController

继承自Animation
构造器里面的必传参数 vsync
同步信号, 屏幕刷新率, 下层渲染

AnimationController(vsync: this);
多继承, 混入 with SingleTickerProviderStateMixin

屏幕绘制, 退到后台、锁屏状态不需要绘制, 不收到同步信号不绘制

forward(): 向前执行动画
reverse(): 反转执行动画

1.3 CurvedAnimation

作用: 设置动画执行的速率(速度曲线)
官网有gif图示例

默认情况下, AnimationController动画生成的值所在区间是0.0到1.0

1.4 Tween: 设置动画执行的value范围

begin: 开始值
end: 结束值

文档注释对应类, 有警告

二. 动画案例练习

2.1 动画的基本使用

心跳动画, 可以反复执行
心的size变化

  1. 创建AnimationController, 做动画的基石
    this写在方法里才有用

每次值发生变化, 界面要执行刷新

  1. 设置Curve的值
    CurvedAnimation(parentL_controller, curve: Curves.elasticInOut)

  2. Tween
    Tween(begin: 50.0, end: 150).animation(animation);
    50要写成 50.0, 泛型 成了int类型, 出问题

监听动画值的改变
_controller.addListener
不需要重新构建的后期做优化

监听动画的状态改变
addStatusListener((status) {})

点击按钮动画暂停, 再次点击开始
优化改变方向

StatelessWidget做动画?
不可以做动画

最后记得要
dispose() {
}
销毁, 调用的目的不是让controller对象销毁, 回收操作

2.2 AnimatedWidget

动画的另一个知识点
之前做的存在两个问题

  1. 每次写动画, 都需要写一段代码
  2. setState => Build方法, 所有东西都会重新构建
    只是icon做了个动画
    优化方案:
  3. AnimatedWidget
    • 将需要执行动画的Widget放到一个车AnimatedWidget中的build方法里进行返回
      JHAnimatedWidget
      父类中的属性 listenable
      缺点:
    1. 每次都需要创建一个类
    2. 如果构建的Widget有子类, 那么子类依然会重复的build
  4. AnimateBuilder
    不重新构建

3.1 交织动画

集合了透明度变化、大小变化、颜色变化、旋转动画等
我们这里是通过多个Tween

文档Cookbook -> Animation ->

  1. 大小变化动画
  2. 颜色变化动画
  3. 透明度变化动画

transform: Matrix4.rotationZ(pi/4);
默认左上角作为坐标原点旋转, 希望以中心点旋转
并不是特别好做
alignment: Alignment.center

  1. 创建AnimationController

  2. 设置Curve的值

  3. Tween
    3.1 创建size的tween
    3.2 创建color的ColorTween
    3.3 创建opacity的tween
    3.4 创建radians的tween

build方法疯狂执行
-> AnimatedBuilder
有一些动画是不支持设置Curve的属性的, 设置会报错
curve: Curves.elasticInOut

知识补充:
转场动画
iOS中的Push
->Present 两种做法 Modal

  1. 自己自定义动画
  2. 比较简单的方式, Material
    fullscreenDialog: true;

渐变的方式弹出页面
-> Navigator.of(context).push(PageRouteBuilder(
pageBuilder: (ctx, animation1, animation2) {
//return JHModalPage(); 一下弹出
return FadeTransition(
opacity: animation1,
child:
);
}
))

3.2 Hero动画

会飞的动画
遇到的需求:
点击商品列表的一张图片, 跳到另一个页面大图展示
iOS中自定义转场动画
Flutter里面直接用Hero动画直接就可以做出来
这种跨页面共享的动画被称之为享元动画(Shared Element Transition)
在Flutter中,有一个专门的Widget可以来实现这种动画效果:Hero

图片网站:
https://picsum.photos/500/500?random=

普通的push效果 -> 动画效果
非常简单
绑定Hero
tag不能重复
改push效果 -> 渐变效果
-> PageRouteBuilder

Flutter中文网站
官方文档错误

20.3.30 一
initState里面不能使用ModalRoute.of()

  1. 官方说了不能使用
    还没有准备好InheritedWidget
    还没有放到映射里面

Timer.run()不会阻塞线程
加入到事件队列
代码的执行顺序问题
执行好了以后才能拿到

三四天的时间来做一个小项目练习
收藏状态管理
路由跳转
左侧弹出菜单
三个目标

  1. 对前面所学的东西进行练习, 屏幕适配
  2. 麻雀虽小五脏俱全, 对项目目录结构, 组件化思想划分, 可扩展性, 可维护性
  3. 其他相关的东西, 移动端, web测试版本, 图标设置, 启动图设置, 各种细节

先来讲解两个知识点

Flutter主题风格

一. Theme主题的使用

Theme氛围: 全局Theme, 局部Theme

MaterialApp中的
title: "Flutter Demo"
在哪里使用? => 查文档
安卓中使用的
On iOS this value cannot be used.

  1. 一旦设置了主题, 那么应用程序中的某些Widget, 就会直接使用主题的样式
    1.1 亮度, 枚举类型 dark
    brightness
    根据系统是否是暗黑模式, 写出两套样式

  2. primarySwatch传入的不是Color, 而是MaterialColor
    (包含了primaryColor和accentColor)
    primarySwatch
    可以设置很多东西的颜色. 官方没有总结
    自己总结, StackOverFlow中, 官方人员Issue
    靠经验

开关跟iOS的样式不一样
CupertinoSwitch
iOS中显示的就是绿色
activeColor

MaterialColor
父类的引用指向一个子类的对象
颜色划分为不同的等级
重写了一个操作符
Color operator [](T index) => _swatch[index];

  1. primaryColor: 单独设置导航和TabBar的颜色

  2. accentColor: 单独设置FloatingActionButton/Switch

  3. 某些Widget主题, Button的主题
    buttonTheme: ButtonThemeData(
    height: 25,
    minWidth: 10,
    buttonColor: Colors.yellow
    )

  4. Card的主题
    cardTheme: CardTheme(
    color: Colors.green,
    elevation: 10, //统一设置阴影
    )

  5. Text的主题
    默认字体大小
    textTheme: TextTheme(
    body1: TextStyle(fontSize: 16),
    body2: TextStyle(fontSize: 20),
    )
    Display4
    Display3
    可以通过Theme.of(context).textTheme

实际开发中方便管理

文档
Cookbook
Widgets Catalog

API查类

页面跳转, 继承过来了主题设置
body1 设置了红色
好几个颜色都发生了变化
不管包裹了几层, 都生效

局部主题将全局主题覆盖
一般情况下不创建新的data, 所有的东西先拷贝过来, 设置了后会覆盖
data: Theme.of(context).copyWith(
primaryColor: Colors.purple
)

floatingActionButton单独包裹一个Theme
也是改不掉
= > 查资料 flutterchina.club
最早的时候官方文档是有错误的
为什么改不掉?
Don't know why this is but accept that it is :)

暗黑模式的适配

Flutter开发如何适配暗黑模式?
最简单的适配
theme: ThemeData(
primarySwatch: Colors.yellow,
textTheme: TextTheme(
boyd1: TextStyle(fontSize: 20, color: Colors.red)
)
)
darkTheme: ThemeData(
primarySwatch: Colors.grey,
textTheme: TextTheme(
boyd1: TextStyle(fontSize: 20, color: Colors.blue)
)
)
直接写两套
开发中封装, 抽取, 搞一个文件夹
JHAppTheme
字体大小抽成常量
static const double smallFontSize = 16;
static const double normalFontSize = 20;
static const double largeFontSize = 24;

屏幕适配

一个适配方案
封装一个工具类

Flutter中的单位
iphone6 dpr * 2

MyApp的build方法中拿到屏幕宽高
//1. 手机的无力分辨率
window.physicalSize.width;

//2. 手机屏幕的大小(逻辑分辨率)
final width = MediaQuery.of(context).size.width;
直接报错, 为什么?
context还没有初始化
JHHomePage中就可以拿到
MaterialApp还没有初始化完成
of方法里面是怎么拿的

怎么启动Debug? 点击小虫子

就是想在报错的地方拿到宽高
如何操作?

//2. 获取dpr, 直接通过window拿
final dpr = window.devicePixelRatio;

//3. 宽度和高度
final width = physicalWidth / dpr;
final height = physicalHeight / dpr;

//4. 状态栏高度
final statusHeight = window.padding.top / dpr;

抽取封装, shared文件夹
size_fit.dart
static静态好处是, 不需要创建实例

适配方案

前端里面的屏幕适配经验

  1. rem:
    给根标签(HTML标签)设置一个字体的大小
    但是不同的屏幕要动态设置不同的字体大小
    其他所有的单位都是用rem单位
    html font-size: 20 div font-size: ->

  2. vw、wh
    将屏幕分成100等份, 一个1vw相当于是1%的大小;
    其他所有的单位都是用vw或wh单位

  3. rpx
    rpx是小程序中的适配方案, 它将750px作为设计稿, 1rpx = 屏幕宽度/750
    其它所有的单位都是用rpx单位
    小程序以iphone6作为标准 750
    原理?
    不管是什么屏幕, 统一分成750份
    在iPhone5上:1rpx = 320/750 = 0.4266 ≈ 0.42px
    在iPhone6上:1rpx = 375/750 = 0.5px
    在iPhone6plus上:1rpx = 414/750 = 0.552px

小程序中所有的单位都要写成2倍
封装的通用点
setRpx(400)、setPx(200)
想要封装的扩展性更高, 以6+为设计稿
传一个标准的尺寸
standardSize()

第三方库: flutter_screenutil

20.4.1 三

升级XCode
iOS -> Flutter -> App.framework文件夹删除重新运行
升级后的问题, 别人也会遇到

屏幕适配工具类
dart2.6之后的extension语法
更简单的来做适配
Swift的语法extension
对现有类来做一个扩展

class Person {

}

extension PersonE1 on Person {

}

系统的类来做扩展
String/int
jh_split(" ")
this, 谁调用我, 就代表谁

对屏幕适配现有的工具类进行扩展
=> 200.px()
对int/double进行扩展

考虑到项目开发中对很多东西进行扩展
项目目录建立extension文件夹
int_extension.dart
double_extension.dart
string_extension.dart

double_extension.dart中
import "..size_fit.dart"
extension DoubleFit on double {
double px() {
return JHSizeFit.setRpx(this);
}
}
get写法更简单 20.0.px

dart中没有隐式转换
int不会自动转成double类型
this.toDouble()
Swift中也可以用这种方式做个适配
Java在不断更新, 语言相对还是比较落后

再来说一个知识点
豆瓣解构的方法, 扩展, 当时翻译错了, 有问题
从2.3.0版本后才开始支持这个语法
...movie.title.runes.map()
报警告的原因, pubspec.yaml
environment:
sdk: ">=2.1.0 < 3.0.0"
改成2.3.0开始支持
警告就消失了
extension直到2.6.0开始才支持
改成2.6.0

新的项目
固定的事情
一. 新建项目
推荐通过终端来新建, 不会生成一些乱七八糟的东西
flutter create favorcate

二. 对项目进行配置
Flutter移动端 => App
都需要配置一些特殊的信息

  • appid App在手机里的唯一标示
  • 应用名称, 默认显示项目名字
  • app icon
  • launcher启动图

针对Android和iOS还不一样的

Android中:

AS中, android文件夹

  1. appid修改 app文件夹 -> build.gradle
    -> defaultConfig -> applicationId "com.coderjh.favorcate"
    报错不用管

  2. main文件夹 AndroidMainifest.xml -> android: lable="美食广场"

  3. App图标 根据文件夹名字找对应的图标 icon="@mipmap/ic_launcher"

  4. 启动图 android/app/src/main/res/drawable/launch_background.xml
    规律: 根据不同的分辨率, 来到对应的文件夹加载图片, 没有就到其他分辨率中找

iOS中的配置

不建议在AS中修改
建议在XCode中修改更加方便

  1. Bundle Identity
    XCode打开, 点到文件夹不用到workspace

info.plist文件

  1. Bundle name 美食广场

  2. AppIcon
    网上工具生成图标

  3. iOS启动图, 早期LaunchImage, iOS13开始LaunchScreen.storyboard

开始做项目了
三. 项目结构目录划分

  • pages
  • widgets
  • services/network
  • router
  • viewmodel
  • model
  • theme
  • shared
    开发过程中其他的文件夹
    constants
    utils
    ...
    另外一层考虑, 有一些目录结构会深一点, 结构更清晰
    core
    ui
    AS建包
    建议来到文件夹里, 创建好文件夹

四. 主题相关
//
canvasColor:

五. 路由配置
类型推导, Stirng可以省略
static const String routeName = "/";

lib/main非常的简洁
app.dart可以抽可以不抽, 尽量不想让lib文件尽可能简单

六.
希望在最开始就把项目划分的更精细点
单一职责原则 initialize_items.dart

一般先搞成StatelessWidget
有结构的时候都用Scaffold

再讲一个东西
如果想做一个首页
JSON解析, 不从服务器请求
也是一个异步的
category.json
建立assets文件夹

home.dart抽取
home_content.dart

services文件夹中
class JsonParse {
静态方法 getCategoryDate() 获取分类数据
1. 加载json文件

    final jsonString = rootBundle.loadString("assets/json/category.json");

    导入convert
    2.将jsonString转成Map/List
    json.decode(jsonString);

    3. 将Map中的内容转成一个个对象
    final resultList = result["category"];
    快速建模型的网站
    javiercbk.github.io/json_to_dart/
}

导入屏幕适配工具类extension文件夹

对JHSizeFit进行初始化

颜色不一样, 还有渐变效果
不是随机的
颜色来自json文件
字符串 -> Color对象

  1. 截取计算对应的255数字
  2. Color(16进制数字)
    1. 将color字符串转成十六进制的数字
      final colorInt = int.parse(color, radix: 16)
    2. 将透明度加进去
      |或运算符
      cColor = Color(colorInt | 0xFF000000);

没有生效, 重新跑一下, hot restart

20.4.3 五

项目架构搭建, 首页搭建
展示内容, 读取JSON文件

加个类前缀做区分

首页代码改进
抽到一个Widget里面, 减少嵌套层级
有时候快捷键抽取有时并不是特别好
依赖的东西太多了
一些东西会作为参数放在里面
依赖数组, 会把整个数组传给我们
home_category_item.dart

冗余代码
StatefulWidget 定义变量 加载数据
新的知识点
继承StatelessWidget
异步加载数据, 根据异步加载的数据进行展示
=> FutureBuilder
未来构建的东西, 网络请求, 有数据的时候构建对应界面
两个必传参数
future: 发送的异步请求
builder: (ctx, snapshot) {
return
}
没有数据展示加载中, 加载完了之后再展示
=> snapshot.hasData
snapshot.data 泛型类, 上面就写好具体类型
请求失败有error
if (snapshot.error) return Center(child: Text("请求失败"));

局限性
不是所有场景都适用
Container/padding
上拉加载更多
传过来参数 page: 1
page的值不断地变化

一些场景下只能用StatefulWidget

下一个页面,
加载json文件
meal.json
服务器请求

123.207.32.32:9001/api/meal

数据在多个地方进行共享
加载多次数据没必要
把数据加载一次, 放在一个共同的地方共享
过滤出来你想要的数据
=> Provider => ViewModel
思路: 服务器 -> 网络请求 -> 数据共享

之前封装的网络请求工具类
依赖dio
meal_request.dart文件
静态方法 static

  1. 发送网络请求
  2. json转modal
    网站转换, 数据特别复杂的时候转换的有问题

女装商城数据, 电商
123.207.32.32:8000/api/h3/detail?iid=1lwwv82
使用网站转换, 报一大堆错误,
生成了一个类 List, DartCore里面有List

给另一个网站, 更有效
各种语言的模型都可以转换
app.quicktype.io
这次的模型没有报错, 可以直接用
还生成了两个全局函数模型json互转

拿到的数据放到对应的ViewModel中
对数据做一个共享
=> Provider官方维护, 添加相关依赖

Provider -> ViewModel/Provider/Consumer(Selector)
MVVM
viewmodel文件夹
meal_view_model.dart文件
class JHMealViewModel extends ChangeNotifier

Provider
ChangeNotifierProvier(
create:
child: MyApp()
)
懒加载, 不影响首屏加载数据

数据间的关系Category和Meal
模型传递到下一个页面
category: id -> meals 过滤

监听点击, 首页Item点击

懒加载, 没有打印"请求拦截"

导入引用
结构清晰

ModalRoute.of(context), 拿到的是栈顶层的路由传递的参数
拿到的是同一个, 重新拿或传过来都行
Consumer拿到的是ViewModel
高阶函数做锅炉类似filter
where((meal) => meal.categories.contains(category.id))
拿到的meals不是数组类型, 是一个抽象类
转成toList
不想用Consumer

  1. 只要是数据发生改变, 必然重新构建
  2. 现在的过滤, 直接的过滤

换成Selector
Selector<JHMealViewModel(原始数据), List<JHMealModel>>
要写两个泛型
必传参数
selector: 前面是上下文ctx, 用来做过滤 A传进来, 做各种转化, 最后返回S
(mealVM) => mealVM.meals.where((meal) => )
箭头函数可以写开

shouldRebuild: (prev, next) =>
包含的数据不同, 才进行刷新
两个List
List<JHMealModel> prev = ["abc", "cba", ];
List<JHMealModel> next = [""];

  1. 遍历, 把其中一个遍历一下
    => 查了一下API, 专门比较两个列表
    -> import '.../collection.dart';
    不同的时候需要重新刷新 !ListEquality().equals(prev, next),
    builder: (ctx, meals, child) {
    return ListView.builder(
    itemCount:meals.length,
    itemBuilder: (ctx, index) {
    return ListTile();
    }
    );
    }

=>封装一个Widget
meal_item.dart
暂时没有数据改变 => StatelessWidget

Card的效果比较好看
Column
Stack 存在层叠
Row
先分清结构再写布局
Card有个margin: EdgeInsets.all(10.px),
阴影elevation: 5,
很多东西不需要记, 经常点到源码看继承关系

图片没有圆角, 问题,
稍微复杂一点的东西, 图片下面没有圆角, 上面有圆角
=> 图片进行裁剪
又是一个新的API, 单独裁剪某些圆角
BorderRadius.only(
topLeft: Radius.circular(12.px),

)

Positioned()绝对定位
会在多个地方用到的, 单独封装一个Widget
operation_item.dart
class JHOperationItem extends StalessWidget{}
显示简单和复杂
complexity: 0, 数据中的复杂程度 0, 1, 2
搞一个字符串类型, 创建模型的时候, 直接赋值过去一个值
一个简单的方法
List<String> complexs = ["简单", ...]; 0,1,2
不是从0开始就别这样干
确定没有问题报警告, moreAction, 保存单词到字典里面

再稍微讲一会
当点击Card时, 跳转到一个详情页面
detail.dart
Card默认没有点击事件, 监听点击, 手势
一般放到最后
改路由的东西需要HotRestart

20.4.6 一

今天内容还是比较多的
项目里还有什么东西没有做?
菜谱详情页面
过滤页面, 过滤掉一些食物
右滑出的菜单页面
收藏显示

菜谱详情页面
两个地方都会使用
局部变量传递来传递去

图片
食材
制作步骤
超过了屏幕的高度, 滚动
方案非常多, 滚动的东西不是相同的

Column超过屏幕高度, 报错超过安全区域
如何解决?SingleChildScrollView
Column作为子widget
知识点补充

先把代码划分清晰, 用Text占位, 之后进行填充

  1. 横幅图片 Widget buildBannerImage() {}
    Image包裹一个Container

  2. 制作材料 Widget buildMakeMaterial() {}
    小标题, 内容
    两个标题长得差不多
    两种处理方式: 单独封装一个Widget/抽成一个公共的方法
    Widget buildMakeTitle() {}
    上下有一定的间距, 包裹Container
    通过Theme去拿, 需要把上下文传到方法里
    设置垂直方向的padding
    希望是粗体 -> copyWith()
    分析: 搞一个ListView来做
    Column嵌套ListView会出现一个常见的问题

有一个边框
Container里面放一个ListView
用Card做出来的东西比较好看

重点!!!
跑一下就报错
Flutter中非常经典的一个错误 hasSize
错误产生的原因
把所有Widget放在一个Column里面, 最大占据控件, 屏幕的高度
SingleChildScrollView只是让内容可以滚动而已
Column需要有一个固定的高度
放了一个ListView后出问题, ListView很特别, 滚动方向上尽可能大的占据空间, 自己也不知道占据多少空间
Container的高度是300
里面放个ListView, 尽可能占据大空间 => 只能占据300
Column的特点, 需要所有子Widget有个明确的高度,
有了明确的高度才能知道如何摆放, mainAxisAligment
产生了矛盾, ListView希望尽可能多的占据高度, Column希望ListView给一个固定的高度
Column里面放了一个ListView/ListView中放一个ListView
hasSize: 子Widget不能有一个明确的高度, 父Widget希望子Widget有个明确的高度

给Container一个明确的高度300, 会产生另一个问题
ListView的高度可能会超过300, ListView可以在Container里面滚动, 没有超过也可以滚动
出现局部滚动的效果
现在不要局部滚动
高度不能写死, 内容多高, 撑多高

如何解决?
让ListView有一个固定的高度, 不是尽量多的占据高度
=> 一个属性shrinkWrap: 范围内进行包裹, 默认false
单独拖动的时候可以进行滚动
=> 一个属性physics: NeverScrollableScrollPhysics(),设置不能滚动
每一个cell本来就存在的内边距设置为0

距离边缘太近, 给Container一个固定的宽度或者
媒体查询 MediaQuery 左右两边各有15

  1. 制作步骤 Widget buildMakeSteps() {}
    Container里面搞个ListView
    和上面制作材料相似的地方
    Widget buildMakeContent(BuildContext context, Widget child) {}
    间的的重构
    共享一个外面的Container, 传入child
    比较好看 => 写成可选参数,

圆形的步骤编号
圆角文字, 类似于圆角图片
leading: CircleAvatar(
child: Text()
)
背景默认情况下用导航栏颜色
也可以单独设置 backgroundColor: Orange
很多好用的Widget
学会去搜索, 最好用英文用Google搜索StackOverFlow

底部空间去除 EdgeInsets.zero

和制作材料是重复代码
搞个Widget继承ListView, 有些重复的东西写死

收藏按钮
FloatingActionButton
改了主题, 需要重新跑一下
监听点击
布局需要多练

不适合用Column, 尽可能大, 循环创建

Flutter的定位
做一个成套的东西, 最开始不想调用原生
原来的App是用原生开发的全线转向Flutter成本高, 模块开发
混合开发
Flutter的第三方插件在越来越丰富
开源出去的机会
Web端如果也发展的比较好的话, 很看好
混合开发会讲, 桥梁, 原理, 知道为什么要这样写
开源的, 读源码, 里面有很多东西

收藏的功能
共享收藏过哪些东西
多个地方使用共享
状态的共享
有几种做法

  1. 单独搞一个Provider, 记录着所有的收藏, 继续收藏继续添加
    相当于在搞一个ViewModel
  2. 给每一个食物添加一个属性, isFavor属性, 默认false
    点击了某个食物, 设置为true, 刷新界面setState
    考虑一个问题, 采用第二个方案, 需要改StatelessWidget为StatefulWidget
    选择第一种方案, 统一的管理, 数组添加移除

favor_view_model.dart
class JHFavorViewModel extends ChangeNotifier {}
操作方法
void addMeal(JHMealModel meal) {}
void removeMeal(JHMealModel meal) {}
操作后发出通知 notifyListeners()
搞一个车方法返账bool
bool isFavor(JHMealModel meal) {
return
}

detail.dart中

添加floatingActionButton: Consumer<JHFavorViewModel>(

    return ;
)
  1. 判断是否是收藏状态
    final iconData = favorVM.isFavor(meal) ? Icons.favorite : Icons.favorite_border;
    final iconColor = favorVM. ? Colors.red : Colors.black;
    写的过程中进行重构
    报错, 找不到Provider
    main.dart中改东西
    providers: [
    多个Provider
    ],

布局的一个问题
Column交叉轴上确实是个Center
默认占据的宽度不是整个屏幕的宽度
是最大的子Widget的宽度
有图片加载完后, 图片会撑大宽度
不能直接设置宽度, 给里面的一个Widget设置一个尽可能大的宽度infinity
遇到问题分析问题

再次点击收藏按钮判断进行收藏或者取消操作
if判断其他地方可能也需要使用
在favor_view_model.dart中
void handleMeal(JHMealModel meal) {}

detail_floating_button.dart
JHDetailFloatingButton
detail.dart就比较简洁了

菜谱列表页的收藏
之前是直接写死的
之前封装的Widget里面
meal_item.dart
很多东西需要做一个改变
搞一个方法

Widget buildFavorItem() {}
Consumer<JHFavorViewModel>(
    builder: (ctx, favorVM, child) {
        //1. 判断时候收藏状态
        final iconData = favorVM.isFavor(_meal) ? Icons.favorite :
        final iconColor = ...
        final title = favorVM.isFavor(_meal) ? "收藏" : "未收藏";

        return JHOperationItem(Icon(iconData, color: iconColor,), title);
    }
)

可选参数的名字不能以下划线开头
operation_item.dart修改

监听点击GestureDetector(child:JHOperationItem())
文字的长度发生改变的世邦, 其他会一起变

  1. 给收藏按钮一个确定的宽度
  2. 文本长度改成一样 未收藏 已收藏
    Row里面Space
    一开始给一个固定的宽度
    operation_item.dart中给一个固定的宽度, 要写成.px
    很容易点到旁边, 给包裹一个padding, 让收藏按钮的高度增大
    开发中的常见做法, 返回按钮的点击区域

favor.dart
创建favor_content.dart
JHFavorContent
拿到数据, 展示列表
没有收藏时空的判断处理

另一个方案, 模型中搞个isFavor, 可以自己尝试一下

再讲一个知识点
抽屉效果
Flutter里面实现起来非常的简单
添加到首页, 导航栏抽屉图标
home.dart
drawer: Drawer()
默认情况下有一定的宽度
包裹一个Container, 调整宽度

如何改首页导航栏上抽屉的图标?
appBar中
leading: Icon(Icons.setting)
直接改了以后Drawer不弹出来了
遇到问题自己搜索
谷歌flutter drawer icon
不能搞一个Icon => IconButton(icon: Icon(Icons.build), onPressed: () {})
怎么弹出?
Navigator.of(context).openDrawer();
依然不可以
解决思路, 自己学习方法
Scaffold上下文拿到的不一样
如何处理?
leading: Builder(
builder: (ctx) {
return IconButton
}
)

抽屉里面的东西做一下
再做一个封装
AppBar也做一个封装
home_app_bar.dart
JHHomeAppBar : super中写
home_drawer.dart
JHHomeDrawer
child: Drawer(
child: Column
)

Widget buildHeaderView() {}
alignment: Alignment(0, 0.5)
增加一个超大字体主题, 需要重新跑一下, 字体比较细, 需要加粗

进餐、过了
Widget buildListTile(Widget icon, String title) {}
弹出Drawer
点击传入函数
Navigator.of(context).pop()

过滤下节课来说

20.4.8 三

继续讲项目
点击按钮弹出Drawer
应该是覆盖上底部栏
下课试着做了出来
很简单

思路:
之前drawer写在JHHomeScreen中
包含下面的是MainScreen
给JHMainScreen一个drawer
导航上的按钮是属于JHHomeScreen
事件传递, 刚好是上层的

还有一个功能没有做, 过滤
一些食品不喜欢, 过滤掉
学习思想, 项目架构的思路

底部模态出选择界面
创建filter文件夹, filter.dart
JHFilterScreen
filter_content.dart, JHFilterContent
路由, 默认的方式弹出, push
自定义, 从下面弹出
generateRoute做一个监听
如果setting.name == ... 做一些自定义
统一管理
改路由, 需要HotRestart

上面是固定
下面是滚动列表
Column布局, 下半部占据剩余所有空间
Widget buildChoiceTitle() {}

Widget buildChoiceSelect() {}
返回一个ListView
hasSize报错, shrinkWrap不用了, 换个写法
Expanded包裹一个ListView,
重复的东西抽取
Widget buildListTitle(String title, String subtitle, Function onChange)

布局完成后做一些事情
选中, 做过滤
三个界面
布尔类型, 值在多个界面进行共享
数据保存在哪里合适?
=> Provider MealViewModel
这样做会出现问题
相互依赖, 耦合性太过, 只是想使用布尔类型, 却要依赖整个ViewModel

=> 再搞一个FilterViewModel

生成setter、getter, 快捷生成
Switch的value不要写死, 根据之前的选择显示

对meals数据进行一个过滤 _meals.where((meal) {
//过滤
}).toList();
相互依赖, 改一个代码 JHMealViewModel依赖JHFilterViewModel
main.dart
ChangeNotifierProxyProvider
删除JHMealViewModel,
有个update必传参数
ChangeNotifierProxyProvider(
create: (ctx) => JHMealViewModel(),
update: (ctx, filterVM, mealVM) {

}

)

meal_view_model中
搞一个全局变量
void updateFilters(JHFilterViewModel filterVM) {
}
前面对返回false, 最终return true;

收藏没有过滤掉,
如果需求就是这样就没有问题
如果收藏也需要过滤掉
JHFavorViewModel依赖JHFilterViewModel
代码都是拷贝的, 重复代码 => 抽取
两个类里面有重复代码 => 搞一个基类
class BaseMealViewModel extends ChangeNotifer
notifyListenses()放在基类中

过滤之后的meals生成新的对象

在搞一个getter, 拿原始的meals
originMeals {
return _meals;
}

多个Provider之间相互的依赖

Flutter没有iOS中的ViewWillAppear

K appstore---大象期货

国际化

i18n
在写一个简单的例子, 新建一个项目

想要做国际化, 根据当前系统的语言
点击按钮显示时间选择器
里面的文本默认显示英文
希望在不同语言下显示不同语言
当前的语言就是中文, 没有显示中文
国际化的依赖
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter

supportedLocales: [
Local("zh"),
Locale("en")
]
告诉Widget需要国际化
设置delegate
localizationsDelegates: [
GlobalWidgetsLocalizations.delegate
]

iOS非常的特殊
需要修改info.plist文件

i18n文件夹放所有国际化相关的东西
localizations.dart
class JHLocalizations {
static Map<String, Map<String, String>>
}
初始化对象的时候传入一个local

localizations.dart

创建一个实例, 共享一个实例
参考MaterialApp的做法

isSupport判断是否支持
显示默认的语言
shouldReload有点不好理解
load方法, 真正加载数据
资源是异步加载的, 返回Future

localizations_delegate.dart

抽取
static JHLocalization of(BuildContext context)

数据如果是放在服务器或json文件中, 异步加载
以json来举例子

  1. 加载json文件
  2. 对json进行解析
    cast函数

抽到json文件中还不是最优的方案
getter还是要手动写
最优的方案: 将数据抽到arb文件中(应用资源包)
开发时依赖
AS的IDE插件
arb文件支持传参数
执行了shell脚本