当前位置:   article > 正文

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

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贴心的为我们实现了数据双向绑定,即View的改变能实时让Model发生变化,而Model的变化也能实时更新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方法

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

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

  1. 通过GlobalKey

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

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

二、状态管理

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

Widget管理自身状态

将变量维护到本widget中,控制这个变量变化,调用setState()更新UI

父Widget管理子Widget的状态

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

混合管理

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

全局状态管理

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

三、包管理

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

Flutter:pubspec.yaml

name: flutter_in_action
description: First Flutter Application.

version: 1.0.0+1

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^0.1.2

dev_dependencies:
  flutter_test:
    sdk: flutter
  
flutter:
  uses-material-design: true
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

Vue:package.json

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

四、路由管理

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

Flutter

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

vue

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

通过TipRoutetext参数传递给新路由

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

参数传递

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

参数获取

class EchoRoute extends StatelessWidget {

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

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

Flutter

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

Vue

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

五、常见布局方式

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

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

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

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

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

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

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

ConstrainedBox用于对子组件添加额外的约束。例如,如果你想让子组件的最小高度是50像素,你可以使用const BoxConstraints(minHeight: 50.0)作为子组件的约束。

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

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

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

如果某一个组件有多个父级ConstrainedBox限制,多重限制时,对于minWidthminHeight来说,是取父子中相应数值较大的。实际上,只有这样才能保证父限制与子限制不冲突。

UnconstrainedBox

父子关系中,子组件都必须遵守其父组件的约束,UnconstrainedBox用来"解除"约束。UnconstrainedBox的子组件将不再受到约束,但需要合理使用避免出现:溢出

线性布局(Row和Column)
Row

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

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

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

参数和Row一致

弹性布局
Flex

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

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

Expanded 只能作为 Flex 的孩子(否则会报错),它可以按比例“扩伸”Flex子组件所占用的空间

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

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

Wrap

Wrap的很多属性在Row中也有,可以认为WrapFlex除了超出显示范围后Wrap会折行外,其他行为基本相同。

Wrap特有的几个属性:

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

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

层叠布局

Flutter中使用StackPositioned这两个组件来配合实现绝对定位。Stack允许子组件堆叠,而Positioned用于根据Stack的四个角来确定子组件的位置。

对齐与相对定位

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

Alignment继承自AlignmentGeometry,表示矩形内的一个点,他有两个属性xy,分别表示在水平和垂直方向的偏移

FractionalOffset继承自Alignment,但坐标原点不同,FractionalOffset的坐标原点为矩形的左侧顶点。

Center继承自Align,我们可以认为Center组件其实是对齐方式确定(Alignment.center)了的Align

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

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

六、常用组件

Padding

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

Padding可以给其子节点添加填充(留白),可设置的EdgeInsets:all(double value)、only({left, top, right ,bottom })、symmetric

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

Flutter

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

Css

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

Container是一个组合类容器

可以理解为div

ListView

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

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/你好赵伟/article/detail/588413
推荐阅读
相关标签
  

闽ICP备14008679号