【Flutter之旅】Widget

环境介绍以及参考文献

本示例是在 Linux 16.04.1-Ubuntu 搭配 VS Code 使用。

《Flutter实战》电子书
Flutter中文网

Widget 与 Element

  • Widget 实际上就是 Element 的配置数据,Widget 树实际上是一个配置树,而真正的 UI 渲染树是由 Element 构成;不过,由于 Element 是通过 Widget 生成的,所以它们之间有对应关系,在大多数场景,我们可以宽泛地认为 Widget 树就是指 UI 控件树或 UI 渲染树。
  • 一个 Widget 对象可以对应多个 Element 对象。这很好理解,根据同一份配置(Widget),可以创建多个实例(Element)。

Widget 类

@immutable
abstract class Widget extends DiagnosticableTree { //DiagnosticableTree 类提供调试信息
  const Widget({ this.key });
  final Key key; //决定是否在下一次 build 时复用旧的 widget,决定的条件在 canUpdate() 方法中
    
  @protected
  Element createElement(); //Flutter Framework 在构建 UI 树时,会先隐式调用此方法生成对应节点的 Element 对象

  @override
  String toStringShort() {
    return key == null ? '$runtimeType' : '$runtimeType-$key'; //toString 方法
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) { //复写父类 DiagnosticableTree 的方法,设置诊断树的一些特性
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }
  
  static bool canUpdate(Widget oldWidget, Widget newWidget) { // 是否用新的 Widget 对象去更新就 UI 树上对应的 Element 对象的配置。
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
}

Widget 类本身是一个抽象类,其中最核心的就是定义了 createElement() 接口,在 Flutter 开发中,我们一般都不用直接继承 Widget 类来实现一个新组件,相反,我们通常会通过继承 StatelessWidget 或 StatefulWidget 来间接继承 Widget 类来实现。

StatelessWidget

StatelessWidget 比较简单,继承 Widget 类,重写了 createElement() 方法。

// StatelessWidget 类的定义,是一个抽象类
abstract class StatelessWidget extends Widget {

  const StatelessWidget({ Key? key }) : super(key: key); //构造函数

  @override
  StatelessElement createElement() => StatelessElement(this); //StatelessElement 类简介继承自 Element 类

  @protected
  Widget build(BuildContext context); //子类将在 build 方法中通过嵌套其他 Widget 来构建 UI。
}

// 一个示例:
class Echo extends StatelessWidget {
  const Echo({
    Key? key,  
    required this.text,
    this.backgroundColor:Colors.grey,
  }):super(key:key);
    
  // Widget 的属性应尽可能的被声明为 final,防止被意外改变。  
  final String text;
  final Color backgroundColor;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Container(
        color: backgroundColor,
        child: Text(text),
      ),
    );
  }
}

// 使用示例:
Widget build(BuildContext context) {
  return Echo(text: "hello world");
}

StatefulWidget

StatefulWidget 也是继承自 Widget 类,并重写了 createElement() 方法,不同的是返回的 Element 对象并不相同;

另外 StatefulWidget 类中添加了一个新的接口 createState()。

abstract class StatefulWidget extends Widget {

  const StatefulWidget({ Key? key }) : super(key: key);

  // StatefulElement 间接继承自 Element 类
  // StatefulElement 中可能会多次调用 createState() 来创建状态(State)对象
  @override
  StatefulElement createElement() => StatefulElement(this); 

  // createState() 用于创建和 Stateful widget 相关的状态,它在 Stateful widget 的生命周期中可能会被多次调用。
  // 例如,当一个 Stateful widget 同时插入到 widget 树的多个位置时,Flutter framework 就会调用该方法为每一个位置生成一个独立的 State 实例,
  // 其实,本质上就是一个 StatefulElement 对应一个 State 实例。
  @protected
  @factory
  State createState();
}
State

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

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

State 中有两个常用属性:

  • widget,它表示与该 State 实例关联的 widget 实例,由 Flutter framework 动态设置。注意,这种关联并非永久的,因为在应用生命周期中,UI 树上的某一个节点的 widget 实例在重新构建时可能会变化,但 State 实例只会在第一次插入到树中时被创建,当在重新构建时,如果 widget 被修改了,Flutter framework 会动态设置 State.widget 为新的 widget 实例。
  • context,StatefulWidget 对应的 BuildContext,作用同 StatelessWidget 的 BuildContext。

理解 State 生命周期

class MyHomePage extends StatefulWidget {
  MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  // 当 Widget 第一次插入到 Widget 树时被调用,对于每一个 State 对象,只会被调用一次。
  @override
  void initState() {
    super.initState();
    print("initState");
  }

  // 在 Widget 重新构造时,当新旧 widget 的 key 和 runtimeType 同事相等时被调用
  @override
  void didUpdateWidget(MyHomePage oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget");
  }

  // 当 State 对象从树中移除时被调用
  @override
  void deactivate() {
    super.deactivate();
    print("deactive");
  }

  // 当 State 对象从树中被永久移除时被调用
  @override
  void dispose() {
    super.dispose();
    print("dispose");
  }

  // 在热重载时被调用,在 Release 模式下永不会被调用
  @override
  void reassemble() {
    super.reassemble();
    print("reassemble");
  }

  // 当 State 对象的依赖发生变化时被调用,典型的场景是当系统语言 Local 或应用主题改变时,被回调。
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("didChangeDependencies");
  }

  void _incrementCounter() {
    setState(() {
      _counter++;
    });
  }

  // 在构建 Widget 子树时被调用
  @override
  Widget build(BuildContext context) {
    print("build");
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: Icon(Icons.add),
      ),
    );
  }
}
在 Widget 树中获取 State 对象

需要注意的地方,showSnackBar 的句柄改变了,需要用 ScaffoldMessengerState 去操作,具体请看下面的代码

通过 Context 获取
context 对象有一个 findAncestorStateOfType() 方法,该方法可以从当前节点沿着 widget 树向上查找指定类型的 StatefulWidget 对应的 State 对象。

例:(打开 SnackBar)

Scaffold(
  appBar: AppBar(
    title: Text("子树中获取State对象"),
  ),
  body: Center(
    child: Builder(builder: (context) {
      return RaisedButton(
        onPressed: () {
          // 查找父级最近的Scaffold对应的ScaffoldState对象
          ScaffoldMessengerState? _state = context.findAncestorStateOfType<ScaffoldMessengerState>();
          //调用ScaffoldState的showSnackBar来弹出SnackBar
          _state?.showSnackBar(
            SnackBar(
              content: Text("我是SnackBar"),
            ),
          );
        },
        child: Text("显示SnackBar"),
      );
    }),
  ),
);

// Flutter 开发中便有了一个默认的约定:
// 如果 StatefulWidget 的状态是希望暴露出的,应当在 StatefulWidget 中提供一个 of 静态方法来获取其 State 对象,开发者便可直接通过该方法来获取;
// 如果 State 不希望暴露,则不提供 of 方法。
// eg.
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, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
static GlobalKey<ScaffoldState> _globalKey= GlobalKey();

Scaffold(
    key: _globalKey , //设置key 
)

_globalKey.currentState.openDrawer()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值