目录
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.000000
8.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
创作不易,希望读者三连支持 💖
赠人玫瑰,手有余香 💖
