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