Widget 简介

文章目录

    • 一、默认计数器应用
    • 二、Widget 接口
    • 三、三棵树
    • 四、StatelessWidget 和 StatefulWidget
    • 五、Widget 生命周期
    • 六、在 widget 树中获取 State 对象
    • 七、通过 RenderObject 自定义 Widget
    • 八、Flutter SDK 内置组件库介绍

一、默认计数器应用

计数器执行流程:当右下角的floatingActionButton按钮被点击之后,会调用_incrementCounter方法。在_incrementCounter方法中,首先会自增_counter计数器(状态),然后setState会通知 Flutter 框架状态发生变化,接着,Flutter 框架会调用build方法以新的状态重新构建UI,最终显示在设备屏幕上。

Flutter 中是通过 Widget 嵌套 Widget 的方式来构建UI和进行实践处理的,所以记住,Flutter 中万物皆为Widget。

import 'package:flutter/material.dart'; //导包

void main() {
     
  runApp(const MyApp()); //应用入口
}

/// 代表 Flutter 应用
class MyApp extends StatelessWidget {
     
  const MyApp({
     Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
     
    return MaterialApp(
      //应用名称
      title: 'Flutter Demo',
      theme: ThemeData(
        //蓝色主题
        primarySwatch: Colors.blue,
      ),
      //应用首页路由
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

/// 应用的首页
class MyHomePage extends StatefulWidget {
     
  const MyHomePage({
     Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

/// MyHomePage类对应的状态类
class _MyHomePageState extends State<MyHomePage> {
     
  int _counter = 0;

  //当按钮点击时,会调用此函数,该函数的作用是先自增_counter,然后调用setState 方法。
  // setState方法的作用是通知 Flutter 框架,有状态发生了改变,
  // Flutter 框架收到通知后,会执行 build 方法来根据新的状态重新构建界面
  void _incrementCounter() {
     
    setState(() {
     
      _counter++;
    });
  }

  // 构建UI界面的逻辑
  @override
  Widget build(BuildContext context) {
     
    // 1、Scaffold 是 Material 库中提供的页面脚手架,它提供了默认的导航栏、标题和包含主屏幕 widget 树(后同“组件树”或“部件树”)的body属性,组件树可以很复杂。
    // 2、body的组件树中包含了一个Center 组件,Center 可以将其子组件树对齐到屏幕中心。
    // 3、
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        // Center 子组件是一个Column 组件,Column的作用是将其所有子组件沿屏幕垂直方向依次排列;
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      // floatingActionButton是页面右下角的带“+”的悬浮按钮,它的onPressed属性接受一个回调函数,代表它被点击后的处理器,
      // 本例中直接将_incrementCounter方法作为其处理函数。
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

二、Widget 接口

Widget 的部分源码。

@immutable // 不可变的
abstract class Widget extends DiagnosticableTree {
     
  const Widget({
      this.key });

  final Key? key;

  @protected
  @factory
  Element createElement();

  @override
  String toStringShort() {
     
    final String type = objectRuntimeType(this, 'Widget');
    return key == null ? type : '$type-$key';
  }

  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
     
    super.debugFillProperties(properties);
    properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense;
  }

  @override
  @nonVirtual
  bool operator ==(Object other) => super == other;

  @override
  @nonVirtual
  int get hashCode => super.hashCode;

  static bool canUpdate(Widget oldWidget, Widget newWidget) {
     
    return oldWidget.runtimeType == newWidget.runtimeType
        && oldWidget.key == newWidget.key;
  }
  ...
}
  • @immutable 代表 Widget 是不可变的,这会限制 Widget 中定义的属性(即配置信息)必须是不可变的(final),为什么不允许 Widget 中定义的属性变化呢?这是因为,Flutter 中如果属性发生则会重新构建Widget树,即重新创建新的 Widget 实例来替换旧的 Widget 实例,所以允许 Widget 的属性变化是没有意义的,因为一旦 Widget 自己的属性变了自己就会被替换。这也是为什么 Widget 中定义的属性必须是 final 的原因。
  • Widget 类继承自 DiagnosticableTree,DiagnosticableTree 即“诊断树”,主要作用是提供调试信息。
  • Key: 这个 key 属性的主要的作用是决定是否在下一次 build时 复用旧的 Widget ,决定的条件在canUpdate() 方法中。
  • createElement():“一个 Widget 可以对应多个 Element”;Flutter 框架在构建 UI 树时,会先调用此方法生成对应节点的 Element 对象。此方法是 Flutter 框架隐式调用的,在我们开发过程中基本不会调用到。
  • debugFillProperties(…) 复写父类的方法,主要是设置诊断树的一些特性。
  • canUpdate(…) 是一个静态方法,它主要用于在 Widget 树重新 build 时复用旧的 widget ,其实具体来说,应该是:是否用新的 Widget 对象去更新旧 UI 树上所对应的 Element 对象的配置;通过其源码我们可以看到,只要 newWidget 与 oldWidget 的 runtimeType 和 key 同时相等时就会用 newWidget 去更新Element 对象的配置,否则就会创建新的 Element。

三、三棵树

Flutter 中有三棵树:Widget 树,Element 树和 RenderObject 树。当应用启动时 Flutter 会遍历并创建所有的 Widget 形成 Widget Tree,同时与 Widget Tree 相对应,通过调用 Widget 上的 createElement() 方法创建每个 Element 对象,形成 Element Tree。最后调用 Element 的 createRenderObject() 方法创建每个渲染对象,形成一个 Render Tree。 Element就是Widget在UI树具体位置的一个实例化对象,大多数Element只有唯一的renderObject,但还有一些Element会有多个子节点,如继承自RenderObjectElement的一些类,比如 MultiChildRenderObjectElement。最终所有 Elemen t的RenderObject 构成一棵树,我们称之为”Render Tree“即”渲染树“。总结一下,我们可以认为 Flutter 的UI 系统包含三棵树:Widget 树、Element 树、渲染树。他们的依赖关系是:根据 Widget 树生成Element 树,再依赖于 Element 树生成 RenderObject 树。
Widget 简介_第1张图片
在 flutter 中,Container、Text 等组件都属于 Widget,所以这课树就是 Widget 树,也可以叫做控件树,它就表示了我们在 dart 代码中所写的控件的结构。Element 就是 Widget 的另一种抽象。我们在代码中使用的像 Container、Text 等这类组件和其属性只不过是我们想要构建的组件的配置信息,当我们第一次调用 build()`方法想要在屏幕上显示这些组件时,Flutter 会根据这些信息生成该 Widget 控件对应的 Element,同样地,Element 也会被放到相应的 Element 树当中。RenderObject 在 Flutter 当中做组件布局渲染的工作,其为了组件间的渲染搭配及布局约束也有对应的 RenderObject 树,我们也称之为渲染树。

作者:邱穆
链接:https://www.jianshu.com/p/e2c2ea310bdc

四、StatelessWidget 和 StatefulWidget

在 Flutter 中,Widget 分为两类:Stateless(无状态)和 Stateful(有状态)Widget。StatelessWidget 没有内部状态,Icon、IconButton, 和 Text 都是无状态 Widget, 它们都是 StatelessWidget 的子类。StatefulWidget 是动态的,用户可以和其交互(例如输入一个表单、 或者移动一个 slider 滑块),或者可以随时间改变 (也许是数据改变导致的 UI 更新)。Checkbox, Radio, Slider, InkWell, Form, and TextField 都是 Stateless widgets, 它们都是 StatefulWidget 的子类。

StatelessWidget
StatelessWidget 是不可变的, 这意味着它们的属性不能改变——所有的值都是最终的。如果无状态Widget 里面有子 Widget,并且子 Widget 是有状态的,则子 Widget 的内容是可以通过 setState 来更改的。无状态 Widget 影响的仅仅是自己是无状态的,不会影响他的父 Widget 和子 Widget。

StatefulWidget
StatefulWidget 持有的状态可能在 Widget 生命周期中发生变化。

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

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

State 中有两个常用属性:

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

五、Widget 生命周期

Widget 生命周期主要分为 StatefullWidget 的生命周期和 StatelessWidget 的生命周期,StatelessWidget 的生命周期比较简单,它只有一个 build 方法,我们在这儿不做过多关注,以下主要分析 StatefullWidget 的生命周期。

仍然以计数器功能为例,实现一个计数器 CounterWidget 组件 ,点击它可以使计数器加1,由于要保存计数器的数值状态,所以我们应继承 StatefulWidget。

import 'package:flutter/material.dart'; //导包

void main() {
     
  runApp(const MyApp()); //应用入口
}

/// 代表 Flutter 应用
class MyApp extends StatelessWidget {
     
  const MyApp({
     Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
     
    return const MaterialApp(
      home: StateLifecycleTest(),
    );
  }
}

/// 路由
class StateLifecycleTest extends StatelessWidget {
     
  const StateLifecycleTest({
     Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
     
    return const CounterWidget();
  }
}

/// 计数器组件
class CounterWidget extends StatefulWidget {
     
  const CounterWidget({
     Key? key, this.initValue = 0});

  final int initValue;

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

/// State 代码
class _CounterWidgetState extends State<CounterWidget> {
     
  int _counter = 0;

  @override
  void initState() {
     
    super.initState();
    //初始化状态
    _counter = widget.initValue;
    print("initState");
  }

  @override
  Widget build(BuildContext context) {
     
    print("build");
    return Scaffold(
      body: Center(
        child: TextButton(
          child: Text('$_counter'),
          //点击后计数器自增
          onPressed: () => setState(
            () => ++_counter,
          ),
        ),
      ),
    );
  }

  @override
  void didUpdateWidget(CounterWidget oldWidget) {
     
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget ");
  }

  @override
  void deactivate() {
     
    super.deactivate();
    print("deactivate");
  }

  @override
  void dispose() {
     
    super.dispose();
    print("dispose");
  }

  @override
  void reassemble() {
     
    super.reassemble();
    print("reassemble");
  }

  @override
  void didChangeDependencies() {
     
    super.didChangeDependencies();
    print("didChangeDependencies");
  }
}

监听 App 生命周期

1、组合 WidgetsBindingObserver 类。

class _CounterWidgetState extends State<CounterWidget> with WidgetsBindingObserver

2、在 initState 中添加监听。

WidgetsBinding.instance.addObserver(this);

3、在 dispose 中添加监听。

WidgetsBinding.instance.removeObserver(this);

4、State 中实现状态监听。

@override
void didChangeAppLifecycleState(AppLifecycleState state) {
     
  super.didChangeAppLifecycleState(state);
  if (state == AppLifecycleState.paused) {
     
    // The application is not currently visible to the user, not responding to
    // user input, and running in the background.
    // 不可见,不可操作
  }
  if (state == AppLifecycleState.resumed) {
     
    // The application is visible and responding to user input.
    // 可见,可操作
  }
  if (state == AppLifecycleState.inactive) {
     
    // The application is in an inactive state and is not receiving user input.
    // 可见,不可操作
  }
  if (state == AppLifecycleState.detached) {
     
    // The application is still hosted on a flutter engine but is detached from any host views.
    // 虽然还在运行,但已经没有任何存在的界面。
  }
}

使用场景:
场景 1:前台转后台:

  • AppLifecycleState.inactive
  • AppLifecycleState.paused

场景 2:后台转前台:

  • AppLifecycleState.inactive
  • AppLifecycleState.resumed

生命周期方法说明

  1. constructor:构造函数
  2. createState:createState 是 StatefulWidget 里创建 State 的方法,当要创建新的 StatefulWidget 的时候,会立即执行 createState,而且只执行一次。
  3. initState:这个方法是组件创建后的第一个方法,它相当于原生的 onCreate 和 viewDidLoad,大部分页面初始逻辑调用会写到这个方法中。
  4. didChangeDependencies:当 State 对象的依赖发生变化时会被调用。例:你的 StatefulWidget 依赖一个 InheritedWidget 的数据,当数据发生变化时,会调用子 Widget 的该方法。第一次打开页面时 initState 之后会立刻调用该方法。
  5. didUpdateWidget:当父组件改变并且需要重新绘制 UI 时,这个方法会调用。通过这个方法,你可以通过参数获取到旧的组件,你可以通过对比新旧组件后做一些额外的逻辑。
  6. build:这个方法是最为重要的一个方法,它用来构建你的整个组件树。
  7. deactivate:当 State 对象从树中被移除时,会调用此回调。有些情况下,framework 会将该 State 插入到树的其它部分。
  8. dispose:当 State 对象从树中被永久移除时调用。通常在此回调中释放资源。
  9. reassemble:此回调是专门为了开发调试而提供的,在热重载(hot reload)时会被调用,此回调在 Release 模式下永远不会被调用。

使用场景
场景 1:打开页面:

  1. constructor
  2. createState
  3. initState
  4. didChangeDependencies
  5. build

场景 2:退出页面:

  1. deactivate
  2. dispose

场景 3:热重载:

  1. reassemble
  2. didUpdateWidget
  3. build

场景 4:横竖屏切换:

  1. didUpdateWidget
  2. build
  3. didUpdateWidget
  4. build

————————————————
原文链接:https://blog.csdn.net/haha223545/article/details/105483176

六、在 widget 树中获取 State 对象

有两种方法在子 widget 树中获取父级 StatefulWidget 的State 对象。

通过 Context 获取
一般来说,如果 StatefulWidget 的状态是私有的(不应该向外部暴露),那么我们代码中就不应该去直接获取其 State 对象;如果 StatefulWidget 的状态是希望暴露出的(通常还有一些组件的操作方法),我们则可以去直接获取其 State 对象。但是通过 context.findAncestorStateOfType 获取 StatefulWidget 的状态的方法是通用的,我们并不能在语法层面指定 StatefulWidget 的状态是否私有,所以在 Flutter 开发中便有了一个默认的约定:如果 StatefulWidget 的状态是希望暴露出的,应当在 StatefulWidget 中提供一个 of 静态方法来获取其 State 对象,开发者便可直接通过该方法来获取;如果 State不希望暴露,则不提供 of 方法。这个约定在 Flutter SDK 里随处可见。

import 'package:flutter/material.dart'; //导包

void main() {
     
  runApp(const MyApp()); //应用入口
}

/// 代表 Flutter 应用
class MyApp extends StatelessWidget {
     
  const MyApp({
     Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
     
    return const MaterialApp(
      //应用首页路由
      home: GetStateObjectRoute(),
    );
  }
}

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

  @override
  State<GetStateObjectRoute> createState() => _GetStateObjectRouteState();
}

class _GetStateObjectRouteState extends State<GetStateObjectRoute> {
     
  @override
  Widget build(BuildContext context) {
     
    return Scaffold(
      appBar: AppBar(
        title: Text("子树中获取State对象"),
      ),
      body: Center(
        child: Column(
          children: [
            Builder(builder: (context) {
     
              return ElevatedButton(
                onPressed: () {
     
                  // 查找父级最近的Scaffold对应的ScaffoldState对象
                  ScaffoldState _state =
                      context.findAncestorStateOfType<ScaffoldState>()!;
                  // 打开抽屉菜单
                  _state.openDrawer();
                },
                child: Text('打开抽屉菜单1'),
              );
            }),
            Builder(builder: (context) {
     
              return ElevatedButton(
                onPressed: () {
     
                  // 直接通过of静态方法来获取ScaffoldState
                  ScaffoldState _state = Scaffold.of(context);
                  // 打开抽屉菜单
                  _state.openDrawer();
                },
                child: Text('打开抽屉菜单2'),
              );
            }),
            Builder(builder: (context) {
     
              return ElevatedButton(
                onPressed: () {
     
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text("我是SnackBar")),
                  );
                },
                child: Text('显示SnackBar'),
              );
            }),
          ],
        ),
      ),
      drawer: Drawer(),
    );
  }
}

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

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

import 'package:flutter/material.dart'; //导包

void main() {
     
  runApp(const MyApp()); //应用入口
}

/// 代表 Flutter 应用
class MyApp extends StatelessWidget {
     
  const MyApp({
     Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
     
    return const MaterialApp(
      //应用首页路由
      home: GetStateObjectRoute(),
    );
  }
}

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

  @override
  State<GetStateObjectRoute> createState() => _GetStateObjectRouteState();
}

class _GetStateObjectRouteState extends State<GetStateObjectRoute> {
     
  //定义一个globalKey, 由于GlobalKey要保持全局唯一性,我们使用静态变量存储
  static GlobalKey<ScaffoldState> _globalKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
     
    return Scaffold(
      key: _globalKey, //设置key
      appBar: AppBar(
        title: Text("子树中获取State对象"),
      ),
      body: Center(
        child: Column(
          children: [
            Builder(builder: (context) {
     
              return ElevatedButton(
                onPressed: () {
     
                  //通过GlobalKey来获取State对象
                  _globalKey.currentState?.openDrawer();
                },
                child: Text('打开抽屉菜单1'),
              );
            }),
          ],
        ),
      ),
      drawer: Drawer(),
    );
  }
}

Widget 简介_第3张图片

七、通过 RenderObject 自定义 Widget

StatelessWidget 和 StatefulWidget 都是用于组合其它组件的,它们本身没有对应的 RenderObject。Flutter 组件库中的很多基础组件都不是通过 StatelessWidget 和 StatefulWidget 来实现的,比如 Text 、Column、Align 等。

如果组件不会包含子组件,则我们可以直接继承自 LeafRenderObjectWidget ,它是 RenderObjectWidget 的子类,而 RenderObjectWidget 继承自 Widget 。

class CustomWidget extends LeafRenderObjectWidget{
     
  @override
  RenderObject createRenderObject(BuildContext context) {
     
    // 创建 RenderObject
    return RenderCustomObject();
  }
  @override
  void updateRenderObject(BuildContext context, RenderCustomObject  renderObject) {
     
    // 更新 RenderObject
    super.updateRenderObject(context, renderObject);
  }
}

class RenderCustomObject extends RenderBox{
     

  @override
  void performLayout() {
     
    // 实现布局逻辑
  }

  @override
  void paint(PaintingContext context, Offset offset) {
     
    // 实现绘制
  }
}

如果自定义的 widget 可以包含子组件,则可以根据子组件的数量来选择继承SingleChildRenderObjectWidget 或 MultiChildRenderObjectWidget。通常,我们的 Widget 可以继承自以下三种类

  • SingleChildRenderObjectWidget:RenderObject只有一个 child。
  • MultiChildRenderObjectWidget:可以有多个 child。
  • LeafRenderObjectWidget :RenderObject 是一个叶子节点,没有 child。
    ————————————————
    原文链接:https://blog.csdn.net/yingshukun/article/details/107814111

八、Flutter SDK 内置组件库介绍

Flutter 提供了一套丰富、强大的基础组件,在基础组件库之上 Flutter 又提供了一套 Material 风格( Android 默认的视觉风格)和一套 Cupertino 风格(iOS视觉风格)的组件库。要使用基础组件库,需要先导入:

import 'package:flutter/widgets.dart';

基础组件

  • Text (opens new window):创建一个带格式的文本。
  • Row (opens new window)、 Column (opens new window): 这些具有弹性空间的布局类 widget 可让您在水平(Row)和垂直(Column)方向上创建灵活的布局。
  • Stack (opens new window): 取代线性布局 (和 Android 中的 FrameLayout 相似),Stack 允许子 widget 堆叠, 你可以使用 Positioned (opens new window)来定位他们相对于 Stack 的上下左右四条边的位置。
  • Container (opens new window): Container (opens new window)可让您创建矩形视觉元素。Container 可以装饰一个 BoxDecoration (opens new window), 如 background、一个边框、或者一个阴影。 Container (opens new window)也可以具有边距(margins)、填充(padding)和应用于其大小的约束(constraints)。另外, Container (opens new window)可以使用矩阵在三维空间中对其进行变换。

Material 组件
Flutter 提供了一套丰富 的 Material 组件,它可以帮助我们构建遵循 Material Design 设计规范的应用程序。Material 应用程序以 MaterialApp (opens new window) 组件开始, 该组件在应用程序的根部创建了一些必要的组件,比如 Theme 组件,它用于配置应用的主题。 是否使用 MaterialApp (opens new window)完全是可选的,但是使用它是一个很好的做法。要使用 Material 组件,需要先引入它:

import 'package:flutter/material.dart';

Cupertino 组件
Flutter 也提供了一套丰富的 Cupertino 风格的组件,尽管目前还没有 Material 组件那么丰富,但是它仍在不断的完善中。值得一提的是在 Material 组件库中有一些组件可以根据实际运行平台来切换表现风格,比如 MaterialPageRoute,在路由切换时,如果是 Android 系统,它将会使用 Android 系统默认的页面切换动画(从底向上);如果是 iOS 系统,它会使用 iOS 系统默认的页面切换动画(从右向左)。

你可能感兴趣的:(Flutter实战—第二版,flutter)