自学flutter,记录一些小tips

336 阅读6分钟

flutter的四棵树

image.png

widgetTree、elementTree、renderTree 三棵渲染树的关系

渲染树在上屏前会生成一棵 Layer 树

StatelessWidget

StatefulWidget

  • 继承于widget ,重写createElement(),不同的是返回的Element 对象并不相同;另外StatefulWidget类中添加了一个新的接口createState();
  • 对于StatefulWidget,将build方法放在setState()可以提高开发灵活性,具体指的是build里面可以直接拿到属性;

State

一个 StatefulWidget 类会对应一个 State 类,State表示与其对应的 StatefulWidget 要维护的状态,State 中的保存的状态信息可以:

  1. 在 widget 构建时可以被同步读取。
  2. 在 widget 生命周期中可以被改变,当State被改变时,可以手动调用其setState()方法通知Flutter 框架状态发生改变,Flutter 框架在收到消息后,会重新调用其build方法重新构建 widget 树,从而达到更新UI的目的。 下面我们来看看各个回调函数:

state的生命周期

  • initState:当 widget 第一次插入到 widget 树时会被调用,对于每一个State对象,Flutter 框架只会调用一次该回调,所以,通常在该回调中做一些一次性的操作,如状态初始化、订阅子树的事件通知等。不能在该回调中调用BuildContext.dependOnInheritedWidgetOfExactType(该方法用于在 widget 树上获取离当前 widget 最近的一个父级InheritedWidget,关于InheritedWidget我们将在后面章节介绍),原因是在初始化完成后, widget 树中的InheritFrom widget也可能会发生变化,所以正确的做法应该在在build()方法或didChangeDependencies()中调用它。

  • didChangeDependencies():当State对象的依赖发生变化时会被调用;例如:在之前build() 中包含了一个InheritedWidget (第七章介绍),然后在之后的build() 中Inherited widget发生了变化,那么此时InheritedWidget的子 widget 的didChangeDependencies()回调都会被调用。典型的场景是当系统语言 Locale 或应用主题改变时,Flutter 框架会通知 widget 调用此回调。需要注意,组件第一次被创建后挂载的时候(包括重创建)对应的didChangeDependencies也会被调用。

  • build():此回调读者现在应该已经相当熟悉了,它主要是用于构建 widget 子树的,会在如下场景被调用:

    1. 在调用initState()之后。
    2. 在调用didUpdateWidget()之后。
    3. 在调用setState()之后。
    4. 在调用didChangeDependencies()之后。
    5. 在State对象从树中一个位置移除后(会调用deactivate)又重新插入到树的其他位置之后。
  • reassemble():此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在Release模式下永远不会被调用。

  • didUpdateWidget ():在 widget 重新构建时,Flutter 框架会调用widget.canUpdate来检测 widget 树中同一位置的新旧节点,然后决定是否需要更新,如果widget.canUpdate返回true则会调用此回调。正如之前所述,widget.canUpdate会在新旧 widget 的 key 和 runtimeType 同时相等时会返回true,也就是说在在新旧 widget 的key和runtimeType同时相等时didUpdateWidget()就会被调用。

  • deactivate():当 State 对象从树中被移除时,会调用此回调。在一些场景下,Flutter 框架会将 State 对象重新插到树中,如包含此 State 对象的子树在树的一个位置移动到另一个位置时(可以通过GlobalKey 来实现)。如果移除后没有重新插入到树中则紧接着会调用dispose()方法。

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

StatefulWidget 生命周期如图2-5所示:

图2-5

注意:在继承StatefulWidget重写其方法时,对于包含@mustCallSuper标注的父类方法,都要在子类方法中调用父类方法。

在 widget 树中获取State对象

context中获取state

  • context对象有一个findAncestorStateOfType()方法,该方法可以从当前节点沿着 widget 树向上查找指定类型的 StatefulWidget 对应的 State 对象.通过 context.findAncestorStateOfType 获取 StatefulWidget 的状态的方法是通用的,我们并不能在语法层面指定 StatefulWidget 的状态是否私有,所以在 Flutter 开发中便有了一个默认的约定:如果 StatefulWidget 的状态是希望暴露出的,应当在 StatefulWidget 中提供一个of 静态方法来获取其 State 对象,开发者便可直接通过该方法来获取;如果 State不希望暴露,则不提供of方法.
ScaffoldMessenger.of(context).showSnackBar(
  SnackBar(content: Text("我是SnackBar")),
);

通过GlobalKey

  • GlobalKey 是 Flutter 提供的一种在整个 App 中引用 element 的机制。如果一个 widget 设置了GlobalKey,那么我们便可以通过globalKey.currentWidget获得该 widget 对象、globalKey.currentElement来获得 widget 对应的element对象,如果当前 widget 是StatefulWidget,则可以通过globalKey.currentState来获得该 widget 对应的state对象。

注意:使用 GlobalKey 开销较大,如果有其他可选方案,应尽量避免使用它。另外,同一个 GlobalKey 在整个 widget 树中必须是唯一的,不能重复。

状态管理

如何决定使用哪种管理方法?下面是官方给出的一些原则可以帮助你做决定:

  • 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父 Widget 管理。
  • 如果状态是有关界面外观效果的,例如颜色、动画,那么状态最好由 Widget 本身来管理。
  • 如果某一个状态是不同 Widget 共享的则最好由它们共同的父 Widget 管理。

在 Widget 内部管理状态封装性会好一些,而在父 Widget 中管理会比较灵活。有些时候,如果不确定到底该怎么管理状态,那么推荐的首选是在父 Widget 中管理(灵活会显得更重要一些)

路由管理

MaterialPageRoute

  • MaterialPageRoute继承自PageRoute类,PageRoute类是一个抽象类,表示占有整个屏幕空间的一个模态路由页面,它还定义了路由构建及切换时过渡动画的相关接口及属性。MaterialPageRoute 是 Material组件库提供的组件,它可以针对不同平台,实现与平台页面切换动画风格一致的路由切换动画

Navigator

  • Navigator是一个路由管理的组件,它提供了打开和退出路由页方法。Navigator通过一个栈来管理活动路由集合。通常当前屏幕显示的页面就是栈顶的路由。
  • 最常用的两个方法:
  1. Future push(BuildContext context, Route route) 将给定的路由入栈(即打开新的页面),返回值是一个Future对象,用以接收新路由出栈(即关闭)时的返回数据。
  2. bool pop(BuildContext context, [ result ])将栈顶路由出栈,result 为页面关闭时返回给上一个页面的数据。
  • Navigator.push(BuildContext context, Route route) == Navigator.of(context).push(Route route)
// var result = await Navigator.pushNamed(context, "new_page");
// print("路由返回值: $result");
var result = await Navigator.of(context).pushNamed('new_page');

命名路由

路由表

  • 首先需要注册路由,路由表定义 Map<String, WidgetBuilder> routes;,注册代码如下:
MaterialApp(
  initialRoute:"home", //initialRoute主要功能主要是控制app启动的时候进入的是哪个页面。
  theme: ThemeData(
  routes:{ //注册路由表
   "new_page":(context) => NewRoute(),
   "home":(context) => MyHomePage(title: 'Flutter Demo Home Page'), //注册首页路由
  } 
  ),
);
...
//路由跳转以及传值
var result = await Navigator.of(context).pushNamed("new_page",arguments: 'parameterFromLastRoute');
print("路由返回值: $result");
.....
///新路由接收传过来的值
var args=ModalRoute.of(context)?.settings.arguments.toString();

通过路由名称来打开新路由

// var result = await Navigator.pushNamed(context, "new_page");
// print("路由返回值: $result");
var result = await Navigator.of(context).pushNamed('new_page');

路由生成钩子

这个博客的用法值得参考flutter onGenerateRoute原生路由简单封装及页面传参

包管理

  • Flutter 使用配置文件pubspec.yaml(位于项目根目录)来管理第三方依赖包(类似cocoapods),注意下图的添加位置 image.png

布局类组件

tip: 向下👇传递约束。向上👆传递尺寸。父节点设置位置

遇到一些小问题

  • 设置边框颜色,参考 链接
decoration: BoxDecoration(
  color: widget.active ? Colors.lightGreen[700] : Colors.grey[600],
  border: _highlight
      ? Border.all(
    // color: Colors.lightGreen[700], //报错,改成下面的方式ok
    color: (Colors.blue[300])!,
    width: 10.0,
  )
      : null,
),

=========
主要学习来源 - flutter中文网