目录
3.3 核心规则:Constraints go down. Sizes go up. Positions are set by parents.
1. 一切都是 widget
1.1 UI 组件
这有三个帮助开发的文档,主要推荐 2和3,直观一点

Flutter 画廊 (flutter-gallery-archive.web.app)


如果需要什么组件,可以先去以上两个网址看看有现成的能否满足需求
1.2 架构设计



渲染引擎
1.3 sdk 源码目录
sdk/packages/flutter/lib/src


1.4 widget 分类
- 基础组件 Widget(Basics):
Container、Row、Column、Image、Text、Icon、RaisedButton、Scaffold、Appbar、FlutterLogo、Placeholder
Material Components:
App 结构和导航类
Scaffold、Appbar、BottomNavigationBar、TabBar、TabBarView、MaterialApp、WidgetsApp、Drawer、SliverAppBar
- 按钮类
RaisedButton、FloatingActionButton、FlatButton、IconButton、DropdownButton、PopupMenuButton、ButtonBar
- 输入和选择类
TextField、Checkbox、Raido、Switch、Slider、Date&Time Pickers
- 对话框和控制面板类
SimpleDialog、AlertDialog、BottomSheet、ExpansionPanel、SnackBar);
信息显示类(Image、Icon、Chip、Tooltip、DataTable、Card、LinearProgressIndicator、CircularProgressIndicator、GridView
- 布局类
ListTile、Stepper、Divider
- Cupertino (iOS-style widgets):
CupertinoActionSheet、CupertinoActivityIndicator、CupertinoAlertDialog、CupertinoButton、CupertinoDatePicker、CupertinoDialog、CupertinoDialogAction、CupertinoFullscreenDialogTransition、CupertinoPageScaffold、CupertinoPageTransition、CupertinoPicker、CupertinoPopupSurface、CupertinoSegmentedControl、CupertinoSlider、CupertinoSwitch、CupertinoNavigationBar、CupertinoTabBar、CupertinoTabScaffold、CupertinoTabView、CupertinoTextField、CupertinoTimerPicker
Layout:
单个子元素的布局 Widget
Container、Padding、Center、Align、FittedBox、AspectRatio、ConstrainedBox、Baseline、FractionallySizedBox、IntrinsicHeight、IntrinsicWidth、LimitedBox、Offstage、OverflowBox、SizedBox、SizedOverflowBox、Transform、CustomSingleChildLayout
- 多个子元素的布局 Widget
Row、Column、Stack、IndexedStack、GridView、Flow、Table、Wrap、ListBody、CustomMultiChildLayout、LayoutBuilder、ListView、Expanded
- Text 文本显示类:
Text、RichText、DefaultTextStyle
- Assets、图片、Icons 类:
Image、Icon、RawImage、AssetBundle
- Input 输入类:
Form、FormField、RawKeyboardListener
- 动画和 Motion 类:
AnimatedContainer、AnimatedCrossFade、Hero、AnimatedBuilder、DecoratedBoxTransition、FadeTransition、PositionedTransition、RotationTransition、ScaleTransition、SizeTransition、SlideTransition、AnimatedDefaultTextStyle、AnimatedListState、AnimatedModalBarrier、AnimatedOpacity、AnimatedPhysicalModel、AnimatedPositioned、AnimatedSize、AnimatedWidget、AnimatedWidgetBaseState
交互模型类:
触摸交互
Draggable、LongPressDraggable、GestureDetector、DragTarget、Dismissible、IgnorePointer、AbsorbPointer、Scrollable
- 路由导航
Hero、Navigator
- 样式类:
Padding、Theme、MediaQuery
- 绘制和效果类:
Transform、Opacity、DecoratedBox、FractionalTranslation、RotatedBox、ClipOval、ClipPath、ClipRect、CustomPaint、BackdropFilter
- Async 异步模型类:
FutureBuilder、StreamBuilder
- 滚动类:
GridView、ListView、NestedScrollView、SingleChildScrollView、Scrollable、Scrollbar、CustomScrollView、NotificationListener、ScrollConfiguration、RefreshIndicator、PageView
- 辅助功能类:
Semantics、MergeSemantics、ExcludeSemantics
参考文献
2. devTools 调试工具
2.1 启动调试器
- 启动调试后 
  - 1 点击右上角按钮
- 2 点击选中按钮
- 3 布局浏览器
 

- 命令模式 Dart: Open DevTools
cmd + shift + p 启动命令

- 我们选择 in web browser

- 如果是第一次打开,选择允许打开 always open
- 会在浏览器中打开调试界面
- 面板选项 
  - Flutter Inspector 布局组件
- Performance 性能
- CPU Profiler 耗能
- Memory 内存
- Debugger 调试信息
- Network 抓包
- Logging 日志
- App Size 文件打包尺寸分析
 

性能模式要创建launch.json文件,选择第二个模式(第一个是传统debug;第二个是性能模式;第三个是release)

2.2 布局面板
- 说明 
  - 1 组件树
- 2 纵向布局信息
- 3 横向布局信息
- 4 约束信息
 

参考文献
Flutter and Dart DevTools | Flutter
3. 布局约束规则
3.1 让子元素竟可能的大,撑满父元素
在 main 函数中,直接创建 Container 显示
void main(List<String> args) {
  runApp(build());
}
Widget build() {
  return Container(
    width: 200,
    height: 200,
    color: Colors.amber,
  );
}
3.2 确认位置后,按子元素大小显示
用 Center 包裹 Container
void main(List<String> args) {
  runApp(build());
}
Widget build() {
  return Center(
    child: Container(
      width: 200,
      height: 200,
      color: Colors.amber,
    ),
  );
}
3.3 核心规则:Constraints go down. Sizes go up. Positions are set by parents.
- 上层 widget 向下层 widget 传递约束条件
- 下层 widget 向上层 widget 传递大小信息
- 上层 widget 决定下层 widget 的位置

我们写3个文字组件纵向排列
Widget _buildScaffold() {
  return Scaffold(
    body: Column(
      children: const <Widget>[
        Text("aaaaaa"),
        Text("bbbbb"),
        Text("cccc"),
      ],
    ),
  );
}
宽度 0.0 <= w <= 303.0 , 高度 0.0 <= h <= 523.0 ,就是上层传下来的约束

宽度 w=32.0 , 高度 h=16.0 就是组件向上层传递的大小信息

元素左边 w=7.5,右边 w=7.5,就是上层决定下层的组件位置

参考文献
https://juejin.cn/post/6846687593745088526
Understanding constraints | Flutter
4. 松约束
4.1 Column 宽度等于子元素最大宽度
就是说子元素宽带如果增加到比Column的宽度还大,Column也会随着增加宽度
import 'package:flutter/material.dart';
void main() {
  runApp(build());
}
Widget build() {
  return MaterialApp(
    home: Scaffold(
      body: Column(
        children: const [
          Text("aaaaaaaaaaaaaaaaaaaaaa"),
          Text("aaaaaaaaaaa"),
        ],
      ),
    ),
  );
}

4.2 Container 会紧包裹子元素
Scaffold 填充了整个屏幕,Container 包裹了 Column
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Container(
          color: Colors.amber,
          child: Column(
            children: const [
              Text("aaaaaaaaaaaaaaaa"),
              Text("bbbbbbbbb"),
            ],
          ),
        ),
      ),
    );
  }
}
Container 的宽高随着 Column 一起变化 w = 124.0,紧包裹

4.3 松约束定义
当一个 widget 告诉其子级可以比自身更小的话, 我们通常称这个 widget 对其子级使用 宽松约束(loose),意思就是说,比如有(0.0<=w<=375.0)范围这种,就是松约束
Scaffold 对 Column 的约束是宽高屏幕宽度内即可

Column 的宽度按 Text 最大宽度为准

参考文献
BoxConstraints class - rendering library - Dart API
5. 紧约束
5.1 ConstrainedBox 约束组件
constraints 通过 maxWidth maxHeight ,来设置子组件最大约束
void main() {
  runApp(build());
}
Widget build() {
  return MaterialApp(
    home: Scaffold(
      body: Center(
        child: ConstrainedBox(
          constraints: const BoxConstraints(
            maxWidth: 200,
            maxHeight: 200,
          ),
          child: Container(
            color: Colors.amber,
            width: 50,
            height: 50,
          ),
        ),
      ),
    ),
  );
}显示了一个 50 x 50 的 Container

我们加上 minWidth minHeight
void main() {
  runApp(build());
}
Widget build() {
  return MaterialApp(
    home: Scaffold(
      body: Center(
        child: ConstrainedBox(
          constraints: const BoxConstraints(
            minWidth: 100,
            minHeight: 100,
            maxWidth: 200,
            maxHeight: 200,
          ),
          child: Container(
            color: Colors.amber,
            width: 10,
            height: 10,
          ),
        ),
      ),
    ),
  );
}这时候 尺寸强制被设置为了 100 * 100

查看约束情况

5.2 紧约束定义
它的最大/最小宽度是一致的,高度也一样
通过 BoxConstraints.tight 可以设置紧约束
void main() {
  runApp(build());
}
Widget build() {
  return MaterialApp(
    home: Scaffold(
      body: Center(
        child: ConstrainedBox(
          constraints: BoxConstraints.tight(const Size(100, 100)),
          child: Container(
            color: Colors.amber,
            width: 10,
            height: 10,
          ),
        ),
      ),
    ),
  );
}
宽高约束都被设置成了 100

6. 无边界 unbounded
6.1 UnconstrainedBox 不受约束
UnconstrainedBox 包裹内部的 Container 10*10 可以不受约束自己控制大小
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: ConstrainedBox(
          constraints: const BoxConstraints(
            minWidth: 100,
            minHeight: 100,
            maxWidth: 300,
            maxHeight: 300,
          ),
          child: UnconstrainedBox(
            child: Container(
              width: 10,
              height: 10,
              color: Colors.blue,
            ),
          ),
        ),
      ),
      debugShowCheckedModeBanner: false,
    );
  }
}显示了一个 10*10 的正方形,没有收到 ConstrainedBox 的影响

约束查看 Container 10*10,父级约束 最小宽高 100

6.2 unbounded 组件
Row;Column;ListView 这种组件 属于 unbounded
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Column(
          children: const [
            FlutterLogo(size: 50),
            FlutterLogo(size: 20),
            // Container(
            //   height: 2000,
            //   color: Colors.amber,
            // ),
          ],
        ),
      ),
      debugShowCheckedModeBanner: false,
    );
  }
}
Column 垂直元素 unconstrained 没有约束,高度不限制

加入一个高度超出屏幕区域的 Container
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Column(
          children: [
            const FlutterLogo(size: 50),
            const FlutterLogo(size: 20),
            Container(
              height: 2000, // 设置了 2000 高
              color: Colors.amber,
            ),
          ],
        ),
      ),
      debugShowCheckedModeBanner: false,
    );
  }
}提示底部超出溢出

- 清楚的显示了你溢出的情况,屏幕高 430 ,Column 被撑开 2070 , Container 高 2000 , 底部溢出高度 1640

7. 有状态无状态组件
在 Flutter 中,组件(也称为 Widget)分为有状态(StatefulWidget)和无状态(StatelessWidget)两种类型。这两者的区别在于是否需要保存和管理状态
无状态组件(StatelessWidget)
无状态组件是一种在应用运行过程中不会改变的组件。它们的属性是不可变的(immutable),这意味着一旦创建,它的显示内容就不会改变。StatelessWidget 的典型例子包括文本、图标或静态图片等简单的 UI 元素
特点:
- 不会随着时间或用户交互而改变
- 不包含内部状态
- 适用于静态内容的场景
有状态组件(StatefulWidget)
有状态组件是一种在应用运行过程中可以改变的组件。它们需要管理自己的状态,并通过 State 类来控制该状态的变化。StatefulWidget 可以用于需要更新 UI 的场景,比如用户交互、动画或实时数据展示。
特点:
- 可以动态改变内容。
- 包含内部状态。
- 适用于动态内容的场景。
7.1 无状态 StatelessWidget
准备两张图片
const img1 =
    "https://ducafecat.tech/2021/12/09/blog/2021-jetbrains-fleet-vs-vscode/2021-12-09-10-30-00.png";
const img2 =
    "https://ducafecat.tech/2021/12/09/blog/2021-jetbrains-fleet-vs-vscode/2021-12-09-20-45-02.png";编写图片显示组件 BannerWidget
class BannerWidget extends StatelessWidget {
  const BannerWidget({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Image.network(img1);
  }
}MyApp 执行
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Column(
          children: const [
            Text('有无状态组件'),
            BannerWidget(),
          ],
        ),
      ),
      debugShowCheckedModeBanner: false,
    );
  }
}运行

7.2 有状态 StatefulWidget
改写成有状态组件
class BannerWidget extends StatefulWidget {
  const BannerWidget({Key? key}) : super(key: key);
  @override
  State<BannerWidget> createState() => _BannerWidgetState();
}
class _BannerWidgetState extends State<BannerWidget> {
  String? imgUrl;
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () {
            setState(() {
              imgUrl = imgUrl == img1 ? img2 : img1;
            });
          },
          child: const Text("切换图片"),
        ),
        Image.network(imgUrl ?? img1),
      ],
    );
  }
}运行

可以发现通过 setState 来管理和维护内部数据状态
7.3 有状态包裹无状态组件
编写图片显示组件
class ImageWidget extends StatelessWidget {
  const ImageWidget({Key? key, required this.imgUrl}) : super(key: key);
  final String imgUrl;
  @override
  Widget build(BuildContext context) {
    return Container(
        padding: const EdgeInsets.all(10),
        color: Colors.amber,
        child: Image.network(imgUrl));
  }
}这里加了个 Container 显示边框
直接调用
      children: [
        ...
        ImageWidget(
          imgUrl: imgUrl ?? img1,
        ),
      ],执行

可以发现很多组件都是无状态的,文本、图片、输入框、按钮、卡片、图标...
组件树

7.4 使用函数编写组件
编写函数组件
Widget imageWidget({required String imgUrl}) {
  return Container(
    padding: const EdgeInsets.all(10),
    color: Colors.amber,
    child: Image.network(imgUrl),
  );
}调用
...
imageWidget(
          imgUrl: imgUrl ?? img1,
        ),查看组件树

可以发现没有显示组件的名称 imageWidget 只是Container
Flutter 这样做是出于性能考虑,所以如果你是可复用组件,需要用 class 包裹
参考文献
Flutter Stateless and Stateful Widget
Flutter: Stateful vs Stateless Widget
8. 生命周期
Flutter 的生命周期主要针对的是组件(即 Widget)的生命周期
8.1 StatefulWidget 生命周期

执行顺序,从上往下

不要再 build 里面更新状态, 影响性能
initState 初始状态数据
  @override
  void initState() {
    super.initState();
    print("initState");
  }build 渲染视图,可多次
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () {
          },
          child: const Text("切换图片"),
        ),
        imageWidget(
          imgUrl: imgUrl ?? img1,
        ),
      ],
    );
  }mounted 状态
flutter 分配完你的组件树位置,会设置 mounted 为 true
你需要在 mounted == true 情况下,调用 setState() 来更新 UI ,这才是安全的操作
  ElevatedButton(
    onPressed: () {
      if (mounted == true) {
        setState(() {
          imgUrl = imgUrl == img1 ? img2 : img1;
        });
      }
    },
    child: const Text("切换图片"),
  ),didChangeDependencies
父或祖先widget中的InheritedWidget改变时会被调用
  // 父或祖先widget中的InheritedWidget改变时会被调用
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("didChangeDependencies");
  }didUpdateWidget
父类 setState 后,子类就会触发
  // 父类 setState 后,子类就会触发
  @override
  void didUpdateWidget(oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget");
  }addPostFrameCallback
渲染结束回调,只执行1次
如我们可以放在 initState 时设置
  // 初始 State, mounted 等于 true, 只执行1次
  @override
  void initState() {
    super.initState();
    print("initState");
    // 渲染结束调用,只执行1次
    SchedulerBinding.instance?.addPostFrameCallback((timeStamp) {
      print("addPostFrameCallback");
      print(timeStamp);
    });
  }deactivate
从组件树中移除 State 时调用
  @override
  void deactivate() {
    super.deactivate();
    print("deactivate");
  }dispose
组件被释放时调用
  @override
  void dispose() {
    print("dispose");
    super.dispose();
  }代码
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
void main() {
  runApp(const MyApp());
}
const img1 =
    "https://ducafecat.tech/2021/12/09/blog/2021-jetbrains-fleet-vs-vscode/2021-12-09-10-30-00.png";
const img2 =
    "https://ducafecat.tech/2021/12/09/blog/2021-jetbrains-fleet-vs-vscode/2021-12-09-20-45-02.png";
Widget imageWidget({required String imgUrl}) {
  return Container(
    padding: const EdgeInsets.all(10),
    color: Colors.amber,
    child: Image.network(imgUrl),
  );
}
class BannerWidget extends StatefulWidget {
  const BannerWidget({Key? key}) : super(key: key);
  // 创建 State 只执行1次
  @override
  State<BannerWidget> createState() {
    print("createState");
    return _BannerWidgetState();
  }
}
class _BannerWidgetState extends State<BannerWidget> {
  String? imgUrl;
  // 初始 State, mounted 等于 true, 只执行1次
  @override
  void initState() {
    super.initState();
    print("initState");
    // 渲染结束调用,只执行1次
    SchedulerBinding.instance?.addPostFrameCallback((timeStamp) {
      print("addPostFrameCallback");
      print(timeStamp);
    });
  }
  // 父或祖先widget中的InheritedWidget改变时会被调用
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    print("didChangeDependencies");
  }
  // 父类 setState 后,子类就会触发
  @override
  void didUpdateWidget(oldWidget) {
    super.didUpdateWidget(oldWidget);
    print("didUpdateWidget");
  }
  // 从组件树中移除 State 时调用
  @override
  void deactivate() {
    super.deactivate();
    print("deactivate");
  }
  // 组件被释放时调用
  @override
  void dispose() {
    print("dispose");
    super.dispose();
  }
  // UI 被重新渲染的时候多次执行
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () {
            if (mounted == true) {
              setState(() {
                imgUrl = imgUrl == img1 ? img2 : img1;
              });
            }
          },
          child: const Text("切换图片"),
        ),
        imageWidget(
          imgUrl: imgUrl ?? img1,
        ),
      ],
    );
  }
}
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Column(
          children: const [
            Text('有无状态组件'),
            BannerWidget(),
          ],
        ),
      ),
      debugShowCheckedModeBanner: false,
    );
  }
}输出
flutter: createState
flutter: initState
flutter: didChangeDependencies
flutter: addPostFrameCallback
flutter: 0:00:00.0000008.2 StatelessWidget 生命周期
无状态组件,不需要处理生命周期,直接显示即可
abstract class StatelessWidget extends Widget {
  /// Initializes [key] for subclasses.
  const StatelessWidget({ Key? key }) : super(key: key);
  /// Creates a [StatelessElement] to manage this widget's location in the tree.
  ///
  /// It is uncommon for subclasses to override this method.
  @override
  StatelessElement createElement() => StatelessElement(this);在源码中可见 createElement() 创建组件到组件树,不需要重写去维护
8.3 App生命周期
第一步: 创建 StatefulWidget 组件,混入 WidgetsBindingObserver
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  ...第二步:添加观察者 addObserver
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance?.addObserver(this); //添加观察者
  }生命周期变化时回调 didChangeAppLifecycleState
  //  生命周期变化时回调
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    print("didChangeAppLifecycleState: $state");
  }
代码
import "package:flutter/material.dart";
void main() {
  runApp(const MyApp());
}
class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  _MyAppState createState() => _MyAppState();
}
//实现WidgetsBindingObserver观察者
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance?.addObserver(this); //添加观察者
  }
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text("App生命周期"),
        ),
        body: Column(
          children: <Widget>[
            const Text("首页"),
          ],
        ),
      ),
    );
  }
  //  生命周期变化时回调
  //  resumed:应用可见并可响应用户操作,app进入前台
  //  inactive:用户可见,但不可响应用户操作,比如来了个电话,前后台切换的过渡状态
  //  paused:已经暂停了,用户不可见、不可操作,app进入后台
  //  suspending:应用被挂起,此状态IOS永远不会回调
  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    print("didChangeAppLifecycleState: $state");
  }
  //当前系统改变了一些访问性活动的回调
  @override
  void didChangeAccessibilityFeatures() {
    super.didChangeAccessibilityFeatures();
    print("didChangeAccessibilityFeatures");
  }
  //低内存回调
  @override
  void didHaveMemoryPressure() {
    super.didHaveMemoryPressure();
    print("didHaveMemoryPressure");
  }
  //用户本地设置变化时调用,如系统语言改变
  @override
  void didChangeLocales(List<Locale>? locale) {
    super.didChangeLocales(locale);
    print("didChangeLocales");
  }
  //应用尺寸改变时回调,例如旋转
  @override
  void didChangeMetrics() {
    super.didChangeMetrics();
    Size? size = WidgetsBinding.instance?.window.physicalSize;
    print("didChangeMetrics  :宽:${size?.width} 高:${size?.height}");
  }
  //系统切换主题时回调
  @override
  void didChangePlatformBrightness() {
    super.didChangePlatformBrightness();
    print("didChangePlatformBrightness");
  }
  ///文字系数变化
  @override
  void didChangeTextScaleFactor() {
    super.didChangeTextScaleFactor();
    print(
        "didChangeTextScaleFactor  :${WidgetsBinding.instance?.window.textScaleFactor}");
  }
  @override
  void dispose() {
    super.dispose();
    WidgetsBinding.instance?.removeObserver(this); //销毁观察者
  }
}输出信息
flutter: didChangeMetrics  :宽:1125.0 高:2436.0
flutter: didChangeAppLifecycleState: AppLifecycleState.inactive
flutter: didHaveMemoryPressure
flutter: didChangeAppLifecycleState: AppLifecycleState.paused
flutter: didChangePlatformBrightness
flutter: didChangeMetrics  :宽:1125.0 高:2436.0
flutter: didChangePlatformBrightness
flutter: didChangeMetrics  :宽:1125.0 高:2436.0
flutter: didChangeAppLifecycleState: AppLifecycleState.inactive
flutter: didChangeAppLifecycleState: AppLifecycleState.resumed创作不易,希望读者三连支持 💖
赠人玫瑰,手有余香 💖
