0


WEB转Flutter基础学习笔记(内含vue和flutter对比)

一、Widget简要概括

如果说Vue的UI是template包裹的一个个组件

那么Flutter的UI就是baseBuild中return出来的嵌套罗列的widget

StatelessWidget

用于不需要维护状态的场景,它通常在build方法中通过嵌套其他 widget 来构建UI,在构建过程中会递归的构建其嵌套的 widget

StatefulWidget

调用createState()来创建状态(State)对象。当一个 StatefulWidget 同时插入到 widget 树的多个位置时,Flutter 框架就会调用该方法为每一个位置生成一个独立的State实例,其实,本质上就是一个StatefulElement对应一个State实例。

State

当State被改变时,可以手动调用其setState()方法通知Flutter 框架状态发生改变,Flutter 框架在收到消息后,会重新调用其build方法重新构建 widget 树,从而达到更新UI的目的。

在Vue中,对应data中return出来的数据,vue贴心的为我们实现了数据双向绑定,即

  1. View

的改变能实时让

  1. Model

发生变化,而

  1. Model

的变化也能实时更新

  1. View

。 但在Flutter中我们需要主动的更新UI

生命周期

initState

当 widget 第一次插入到 widget 树时会被调用,对于每一个State对象,Flutter 框架只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。

在此周期中,对应Vue中已经挂载完成

didChangeDependencies

当State对象的依赖发生变化时会被调用,组件第一次被创建后挂载的时候(包括重创建)对应的didChangeDependencies也会被调用

build

在调用initState()之后。在调用didUpdateWidget()之后。在调用setState()之后。在调用didChangeDependencies()之后。在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其他位置之后。

reassemble

在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。

didUpdateWidget

调用widget.canUpdate来检测 widget 树中同一位置的新旧节点,然后决定是否需要更新,如果widget.canUpdate返回true则会调用此回调,新旧 widget 的key和runtimeType同时相等时didUpdateWidget()就会被调用。

deactivate

当 State 对象从树中被移除时,会调用此回调。

dispose

当 State 对象从树中被永久移除时调用;通常在此回调中释放资源。

Vue中我们常用的离开页面时的释放资源:destroyed

在widget 树中获取State对象
  1. 通过Context获取

如果 StatefulWidget 的状态是希望暴露出的,应当在 StatefulWidget 中提供一个of 静态方法来获取其 State 对象,开发者便可直接通过该方法来获取;如果 State不希望暴露,则不提供of方法

  1. Builder(builder: (context) {
  2. return ElevatedButton(
  3. onPressed: () {
  4. // 直接通过of静态方法来获取ScaffoldState
  5. ScaffoldState _state=Scaffold.of(context);
  6. // 打开抽屉菜单
  7. _state.openDrawer();
  8. },
  9. child: Text('打开抽屉菜单2'),
  10. );
  11. }),

vue中,可以理解为this中挂载的数据

  1. 通过GlobalKey

定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
⚠️:非必要不要定义在全局,容易造成内存泄漏

  1. static GlobalKey<ScaffoldState> _globalKey= GlobalKey();
  2. ...
  3. Scaffold(
  4. key: _globalKey , //设置key
  5. ...
  6. )

二、状态管理

你以为状态管理就是Vuex?store?NONONO!!!不全是;在Flutter中,我们可以理解数据和UI是分离的,而数据如何驱动UI、父子UI中的数据流向也是我们说的状态管理,而Vuex对应的是Flutter中的Provider等专门用于状态管理的包

Widget管理自身状态

将变量维护到本widget中,控制这个变量变化,调用

  1. setState()

更新UI

父Widget管理子Widget的状态

子widget中变化值,通知父widget,变化后,父widget中刷新ui,

混合管理

在父子widget中都有相关状态的管理,组件自身管理一些内部状态,而父组件管理一些其他外部状态。

全局状态管理

当应用中需要一些跨组件(包括跨路由)的状态需要同步时,上面介绍的方法便很难胜任了,此时需要专门用于状态管理的包,如 Provider、Redux等

三、包管理

说到包,我们想到了package依赖,安卓也有这样的依赖。不同的是,安卓还支持你在工程中自行开发使用package,只需要在主工程中引入即可

Flutter:pubspec.yaml

  1. name: flutter_in_action
  2. description: First Flutter Application.
  3. version: 1.0.0+1
  4. dependencies:
  5. flutter:
  6. sdk: flutter
  7. cupertino_icons: ^0.1.2
  8. dev_dependencies:
  9. flutter_test:
  10. sdk: flutter
  11. flutter:
  12. uses-material-design: true

Vue:package.json

  1. "dependencies": {
  2. "axios": "0.18.1",
  3. "clipboard": "^2.0.4",
  4. "core-js": "^3.32.0",
  5. "crypto-js": "^4.1.1",
  6. "dom-to-image": "^2.6.0",
  7. "echarts": "^4.1.0",
  8. "echarts-wordcloud": "1.1.3",
  9. "element-ui": "^2.4.11",
  10. "file-saver": "^2.0.1",
  11. "leaflet": "^1.7.1",
  12. "element-ui": "^2.4.11",
  13. "file-saver": "^2.0.1",
  14. "leaflet": "^1.7.1",
  15. "lodash": "^4.17.21",
  16. }

四、路由管理

路由(Route)在移动开发中通常指页面(Page),这跟 Web 开发中单页应用的 Route 概念意义是相同的,Route 在 Android中 通常指一个 Activity

Flutter

  1. Navigator.push(
  2. context,
  3. MaterialPageRoute(builder: (context) {
  4. return NewRoute();
  5. }),
  6. );

vue

  1. this.$router.push({
  2. path: '/yourRouter',
  3. query: { yourArg: this.yourArg, }
  4. })
路由传值

通过

  1. TipRoute

  1. text

参数传递给新路由

  1. onPressed: () async {
  2. // 打开`TipRoute`,并等待返回结果
  3. var result = await Navigator.push(
  4. context,
  5. MaterialPageRoute(
  6. builder: (context) {
  7. return TipRoute(
  8. // 路由参数
  9. text: "我是提示xxxx",
  10. );
  11. },
  12. ),
命名路由
注册路由表
  1. MaterialApp(
  2. title: 'Flutter Demo',
  3. initialRoute:"/", //名为"/"的路由作为应用的home(首页)
  4. theme: ThemeData(
  5. primarySwatch: Colors.blue,
  6. ),
  7. //注册路由表
  8. routes:{
  9. "new_page":(context) => NewRoute(),
  10. "/":(context) => MyHomePage(title: 'Flutter Demo Home Page'), //注册首页路由
  11. }
  12. );
通过路由名打开
  1. onPressed: () {
  2. Navigator.pushNamed(context, "new_page");
  3. },
参数传递

参数传递

  1. Navigator.of(context).pushNamed("new_page", arguments: "hi");

参数获取

  1. class EchoRoute extends StatelessWidget {
  2. @override
  3. Widget build(BuildContext context) {
  4. //获取路由参数
  5. var args=ModalRoute.of(context).settings.arguments;
  6. //...省略无关代码
  7. }
  8. }
路由生成钩子

相当于vue-router的"导航守卫",在打开命名路由时被调用

Flutter

  1. onGenerateRoute: (settings) {
  2. if (settings.name == PassArgumentsScreen.routeName) {
  3. final args = settings.arguments as ScreenArguments;
  4. return MaterialPageRoute(
  5. builder: (context) {
  6. return PassArgumentsScreen(
  7. title: args.title,
  8. message: args.message,
  9. );
  10. },
  11. );
  12. }

Vue

  1. router.beforeEach(async (to, from) => {
  2. if (
  3. // 检查用户是否已登录
  4. !isAuthenticated &&
  5. // ❗️ 避免无限重定向
  6. to.name !== 'Login'
  7. ) {
  8. // 将用户重定向到登录页面
  9. return { name: 'Login' }
  10. }
  11. })

五、常见布局方式

web中的布局通过display、position等控制,常见的flex、Grid布局等;

在Flutter中,我们通过布局约束控制,常见的Row、Expanded等作为包裹你组件的约束,控制了子组件的布局;

布局原理和约束
Flutter 中有两种布局模型:
  • 基于 RenderBox 的盒模型布局。
  • 基于 Sliver ( RenderSliver ) 按需加载列表布局。

两种布局方式在细节上略有差异,但大体流程相同,布局流程如下:

  1. 上层组件向下层组件传递约束(constraints)条件。
  2. 下层组件确定自己的大小,然后告诉上层组件。注意下层组件的大小必须符合父组件的约束。
  3. 上层组件确定下层组件相对于自身的偏移和确定自身的大小(大多数情况下会根据子组件的大小来确定自身的大小)。
BoxConstraints

BoxConstraints 是盒模型布局过程中父渲染对象传递给子渲染对象的约束信息,包含最大宽高信息,子组件大小需要在约束的范围内

  1. const BoxConstraints({
  2. this.minWidth = 0.0, //最小宽度
  3. this.maxWidth = double.infinity, //最大宽度
  4. this.minHeight = 0.0, //最小高度
  5. this.maxHeight = double.infinity //最大高度
  6. })
ConstrainedBox
  1. ConstrainedBox

用于对子组件添加额外的约束。例如,如果你想让子组件的最小高度是50像素,你可以使用

  1. const BoxConstraints(minHeight: 50.0)

作为子组件的约束。

  1. ConstrainedBox(
  2. constraints: BoxConstraints(
  3. minWidth: double.infinity, //宽度尽可能大
  4. minHeight: 50.0 //最小高度为50像素
  5. ),
  6. child: Container(
  7. height: 5.0,
  8. child: redBox ,
  9. ),
  10. )
SizedBox
  1. SizedBox

用于给子元素指定固定的宽高

  1. SizedBox(
  2. width: 80.0,
  3. height: 80.0,
  4. child: redBox
  5. )
多重限制

如果某一个组件有多个父级

  1. ConstrainedBox

限制,多重限制时,对于

  1. minWidth

  1. minHeight

来说,是取父子中相应数值较大的。实际上,只有这样才能保证父限制与子限制不冲突。

UnconstrainedBox

父子关系中,子组件都必须遵守其父组件的约束,

  1. UnconstrainedBox

用来"解除"约束。

  1. UnconstrainedBox

的子组件将不再受到约束,但需要合理使用避免出现:溢出

线性布局(Row和Column)
Row
  1. Row

可以沿水平方向排列其子widget

  1. Row({
  2. ...
  3. TextDirection textDirection, //水平方向子组件的布局顺序(是从左往右还是从右往左
  4. MainAxisSize mainAxisSize = MainAxisSize.max, //表示Row在主轴(水平)方向占用的空间,默认是MainAxisSize.max,表示尽可能多的占用水平方向的空间
  5. MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,//表示子组件在Row所占用的水平空间内对齐方式,MainAxisAlignment.start表示沿textDirection的初始方向对齐
  6. VerticalDirection verticalDirection = VerticalDirection.down, //表示Row纵轴(垂直)的对齐方向,
  7. CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center, //表示子组件在纵轴方向的对齐方式
  8. List<Widget> children = const <Widget>[], // 子组件数组
  9. })
Column
  1. Column

可以在垂直方向排列其子组件

参数和Row一致

弹性布局
Flex
  1. Flex

组件可以沿着水平或垂直方向排列子组件,

  1. Flex({
  2. ...
  3. required this.direction, //弹性布局的方向, Row默认为水平方向,Column默认为垂直方向
  4. List<Widget> children = const <Widget>[],
  5. })
Expanded

Expanded 只能作为 Flex 的孩子(否则会报错),它可以按比例“扩伸”

  1. Flex

子组件所占用的空间

  1. const Expanded({
  2. int flex = 1,
  3. required Widget child,
  4. })
流式布局

Row 和 Column 时,如果子 widget 超出屏幕范围,则会报溢出错误,将Row 换成Wrap后溢出部分则会自动折行

Wrap

Wrap的很多属性在

  1. Row

中也有,可以认为

  1. Wrap

  1. Flex

除了超出显示范围后

  1. Wrap

会折行外,其他行为基本相同。

  1. Wrap

特有的几个属性:

  • spacing:主轴方向子widget的间距
  • runSpacing:纵轴方向的间距
  • runAlignment:纵轴方向的对齐方式
  1. Wrap({
  2. ...
  3. this.direction = Axis.horizontal,
  4. this.alignment = WrapAlignment.start,
  5. this.spacing = 0.0,
  6. this.runAlignment = WrapAlignment.start,
  7. this.runSpacing = 0.0,
  8. this.crossAxisAlignment = WrapCrossAlignment.start,
  9. this.textDirection,
  10. this.verticalDirection = VerticalDirection.down,
  11. List<Widget> children = const <Widget>[],
  12. })
Flow

很少用、需要自己实现子 widget 的位置转换

层叠布局

Flutter中使用

  1. Stack

  1. Positioned

这两个组件来配合实现绝对定位。

  1. Stack

允许子组件堆叠,而

  1. Positioned

用于根据

  1. Stack

的四个角来确定子组件的位置。

对齐与相对定位
  1. Align

组件可以调整子组件的位置,

  1. Alignment

继承自

  1. AlignmentGeometry

,表示矩形内的一个点,他有两个属性

  1. x

  1. y

,分别表示在水平和垂直方向的偏移

  1. FractionalOffset

继承自

  1. Alignment

,但坐标原点不同,

  1. FractionalOffset

的坐标原点为矩形的左侧顶点。

  1. Center

继承自

  1. Align

,我们可以认为

  1. Center

组件其实是对齐方式确定(

  1. Alignment.center

)了的

  1. Align

  1. Align

  1. Stack

/

  1. Positioned

都可以用于指定子元素相对于父元素的偏移,但它们还是有两个主要区别:

  1. 定位参考系统不同;Stack/Positioned定位的参考系可以是父容器矩形的四个顶点;而Align则需要先通过alignment参数来确定坐标原点,不同的alignment会对应不同原点,最终的偏移是需要通过alignment的转换公式来计算出。
  2. Stack可以有多个子元素,并且子元素可以堆叠,而Align只能有一个子元素,不存在堆叠。

六、常用组件

Padding

和前端类似,只不过使用形式是包裹组件实现的

  1. Padding

可以给其子节点添加填充(留白),可设置的EdgeInsets:

  1. all(double value)、only({left, top, right ,bottom })、symmetric
  1. Padding(
  2. //左边添加8像素补白
  3. padding: EdgeInsets.only(left: 8),
  4. child: Text("Hello world"),
  5. ),
  6. Padding(
  7. //上下各添加8像素补白
  8. padding: EdgeInsets.symmetric(vertical: 8),
  9. child: Text("I am Jack"),
  10. ),
  11. Padding(
  12. // 分别指定四个方向的补白
  13. padding: EdgeInsets.fromLTRB(20, 0, 20, 20),
  14. child: Text("Your friend"),
  15. )

Flutter

  1. Padding({
  2. ...
  3. EdgeInsetsGeometry padding,
  4. Widget child,
  5. })

Css

  1. .padding-set {
  2. padding: 0 0 10px 10px;
  3. }
Container
  1. Container

是一个组合类容器

可以理解为div

ListView
  1. ListView.builder

适合列表项比较多或者列表项不确定的情况

可以理解为,v-for的功能,itemBuilder就是每一项数据

  1. ListView.builder(
  2. itemCount: 100,
  3. itemExtent: 50.0, //强制高度为50.0
  4. itemBuilder: (BuildContext context, int index) {
  5. return ListTile(title: Text("$index"));
  6. }
  7. );

以上仅为Flutter基础、后续部分会继续总结…
image.png

标签: vue.js 前端 flutter

本文转载自: https://blog.csdn.net/weixin_44717486/article/details/139003978
版权归原作者 北巷` 所有, 如有侵权,请联系我们删除。

“WEB转Flutter基础学习笔记(内含vue和flutter对比)”的评论:

还没有评论