MaterialApp
是什么
MaterialApp
是 Flutter 提供的 应用级顶层 Widget,它帮你一次性配置好应用的 主题、路由、导航、本地化 等全局环境,让你只需专注写页面。
如果没有 MaterialApp
,你其实还是可以写 Flutter 应用,但很多 MaterialApp
自动帮你做的事情就要自己手动搭,而且步骤会很繁琐。
如果没有 MaterialApp
,你得手动写:
- Theme(全局主题)
- Directionality(文本方向)
- Material(Material 风格环境)
- Navigator(导航管理)
- Localizations(国际化支持)
- 调试横幅/标题等额外功能
有Material 风格的程序的构建,当然相对应的是 ios 风格是 CupertinoApp
- https://api.flutter.dev/flutter/material/MaterialApp-class.html
- https://api.flutter.dev/flutter/cupertino/CupertinoApp-class.html
常用构造参数
参数 | 作用 | 示例 |
---|---|---|
title |
应用标题(在任务管理器或某些系统 UI 显示) | "My App" |
home |
应用的首页 Widget | HomePage() |
routes |
命名路由表(Map<String, WidgetBuilder> ) |
{"/about": (_) => AboutPage()} |
theme |
主题(Material 风格) | ThemeData(primarySwatch: Colors.blue) |
darkTheme |
暗色模式主题 | ThemeData.dark() |
themeMode |
控制主题模式 | ThemeMode.system |
debugShowCheckedModeBanner |
是否显示右上角 DEBUG 横幅 | false |
locale / supportedLocales |
多语言支持 | Locale('en') |
onGenerateRoute |
动态路由生成逻辑 | 自定义函数 |
const MaterialApp({
Key key,
// 导航键 , key的作用提高复用性能
this.navigatorKey,
// 主页
this.home,
// 路由
this.routes = const <String, WidgetBuilder>{},
// 初始命名路由
this.initialRoute,
// 路由构造
this.onGenerateRoute,
// 未知路由
this.onUnknownRoute,
// 导航观察器
this.navigatorObservers = const <NavigatorObserver>[],
// 建造者
this.builder,
// APP 标题
this.title = '',
// 生成标题
this.onGenerateTitle,
// APP 颜色
this.color,
// 样式定义
this.theme,
// 主机暗色模式
this.darkTheme,
// 样式模式
this.themeMode = ThemeMode.system,
// 多语言 本地化
this.locale,
// 多语言代理
this.localizationsDelegates,
// 多语言回调
this.localeListResolutionCallback,
this.localeResolutionCallback,
// 支持的多国语言
this.supportedLocales = const <Locale>[Locale('en', 'US')],
// 调试显示材质网格
this.debugShowMaterialGrid = false,
// 显示性能叠加
this.showPerformanceOverlay = false,
// 检查缓存图片的情况
this.checkerboardRasterCacheImages = false,
// 检查不必要的setlayer
this.checkerboardOffscreenLayers = false,
// 显示语义调试器
this.showSemanticsDebugger = false,
// 显示debug标记 右上角
this.debugShowCheckedModeBanner = true,
})
简单示例
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
// APP 标题
// ios 没有用、 android 进程名称 、 web 标题tab栏名称
title: 'Material App',
// APP 颜色
color: Colors.green,
// 样式
theme: ThemeData(
primarySwatch: Colors.yellow,
),
// 主机暗色模式
darkTheme: ThemeData(
primarySwatch: Colors.red,
),
// 显示debug标记 右上角
// debugShowCheckedModeBanner: false,
// 调试显示材质网格
// debugShowMaterialGrid: true,
// 显示性能叠加
// showPerformanceOverlay: true,
// 检查缓存图片的情况
// checkerboardRasterCacheImages: true,
// 检查不必要的setlayer
// checkerboardOffscreenLayers: true,
// 显示语义调试器
// showSemanticsDebugger: true,
// 首页
home: Scaffold(
appBar: AppBar(
title: const Text('Material App'),
),
body: Center(
child: Column(
children: const [
Text("data"),
FlutterLogo(
size: 100,
),
],
),
),
),
);
}
}

Scaffold
脚手架
定义:一种用来快速生成项目、模块或页面基本骨架的工具或组件,省去重复写模板代码的过程。它可以是:
- 命令行工具(例如:
vue create my-app
会生成一个 Vue 项目骨架) - 代码框架类(例如:Flutter 的
Scaffold
帮你生成页面基础布局) - 自动化生成器(例如:Django 的
startapp
会帮你生成 MVC 目录结构)
为什么叫做脚手架:
- 在建筑里,脚手架本身不是房子的一部分,但它提供了施工的支撑,让工人能安全高效地完成工作。
- 在软件里,脚手架代码本身可能不是业务核心,但它提供了开发的基础结构,让开发者能更快地进入业务逻辑编写。
常见软件脚手架例子:
场景 | 脚手架示例 | 作用 |
---|---|---|
前端 | create-react-app / vue-cli |
生成项目目录、配置文件、入口文件 |
后端 | django-admin startproject |
创建 MVC 结构、配置数据库连接 |
Flutter | Scaffold |
提供 AppBar、body、drawer 等页面布局骨架 |
Node.js | express-generator |
自动生成路由、视图、静态文件结构 |
在 Flutter 里,Scaffold
叫做“页面布局脚手架”,意思是它帮你:
- 先搭好一个页面的标准结构(AppBar、body、Drawer、BottomNavigationBar、FAB…)
- 你只需要往这些位置放内容(Widget)就行,不必自己手写布局来摆放顶部、底部等
Scaffold
对应 ios 的是 CupertinoPageScaffold
- https://api.flutter.dev/flutter/material/Scaffold-class.html
- https://api.flutter.dev/flutter/cupertino/CupertinoPageScaffold-class.html
Scaffold 的主要作用
提供 Material 风格的基础布局结构
内置多个常用布局槽位(AppBar、Body、Drawer、BottomNavigationBar…)
统一管理 Material 特性(如 SnackBar
、BottomSheet
的显示)
Scaffold 常用参数
参数 | 作用 | 示例 |
---|---|---|
appBar |
顶部应用栏 | AppBar(title: Text('标题')) |
body |
主体内容区域 | Center(child: Text('内容')) |
drawer |
左侧抽屉菜单 | Drawer(child: ListView(...)) |
endDrawer |
右侧抽屉菜单 | Drawer(...) |
bottomNavigationBar |
底部导航栏 | BottomNavigationBar(items: [...]) |
floatingActionButton |
悬浮按钮 | FloatingActionButton(...) |
backgroundColor |
页面背景色 | Colors.white |
bottomSheet |
底部固定面板 | BottomSheet(...) |
persistentFooterButtons |
固定在底部的按钮组 | [TextButton(...), TextButton(...)] |
const Scaffold({
Key key,
// 菜单栏
this.appBar,
// 中间主体内容部分
this.body,
// 悬浮按钮
this.floatingActionButton,
// 悬浮按钮位置
this.floatingActionButtonLocation,
// 悬浮按钮动画
this.floatingActionButtonAnimator,
// 固定在下方显示的按钮
this.persistentFooterButtons,
// 左侧 侧滑抽屉菜单
this.drawer,
// 右侧 侧滑抽屉菜单
this.endDrawer,
// 底部菜单
this.bottomNavigationBar,
// 底部拉出菜单
this.bottomSheet,
// 背景色
this.backgroundColor,
// 自动适应底部padding
this.resizeToAvoidBottomPadding,
// 重新计算body布局空间大小,避免被遮挡
this.resizeToAvoidBottomInset,
// 是否显示到底部,默认为true将显示到顶部状态栏
this.primary = true,
// 用来控制侧边抽屉(Drawer)滑动手势的响应方式。
this.drawerDragStartBehavior = DragStartBehavior.down,
})
drawerDragStartBehavior
drawerDragStartBehavior
是 Scaffold
的一个可选参数,用来控制侧边抽屉(Drawer)滑动手势的响应方式。它的类型是 DragStartBehavior
枚举,取值有:
DragStartBehavior.start
DragStartBehavior.down
在 Flutter 中,Drawer
可以通过手势(从屏幕边缘滑动)拉出来。手势事件的触发有两种时机:
DragStartBehavior.start
(默认值):只有当手指真正滑动(有位移)时才触发拖动逻辑。也就是说,触摸屏幕不动,不会立刻触发拖动。DragStartBehavior.down
: 只要手指按下(down)就立即开始拖动逻辑,不等滑动位移。适合需要快速响应拖动的场景。
模式 | 行为 |
---|---|
start |
按下屏幕后必须有滑动位移,拖动才开始(更自然,防止误触) |
down |
按下的瞬间就进入拖动状态(响应更快,但可能会误触) |
大多数情况下用默认的 start
就好,避免用户只是点一下屏幕边缘时就意外拉出抽屉。
代码示例
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
// 下面这段注释了的代码是一个 Cupertino 风格的页面示例,
// class CupertinoPage extends StatelessWidget {
// const CupertinoPage({Key? key}) : super(key: key);
// @override
// Widget build(BuildContext context) {
// return const CupertinoPageScaffold(
// navigationBar: CupertinoNavigationBar(
// middle: Text('我是标题'),
// ),
// child: Center(
// child: Text('我是内容'),
// ),
// );
// }
// }
void main() {
runApp(const ScaffoldPage());
}
class ScaffoldPage extends StatelessWidget {
const ScaffoldPage({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
// 菜单栏
appBar: AppBar(title: const Text('菜单栏')),
// 悬浮按钮
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add_photo_alternate),
),
// 悬浮按钮位置
floatingActionButtonLocation: FloatingActionButtonLocation.centerTop,
// 固定在下方显示的按钮
persistentFooterButtons: const [
Text('固定在下方显示的按钮1'),
Text('固定在下方显示的按钮2'),
],
// 压缩顶部菜单空间
primary: true,
// 左侧 侧滑抽屉菜单
drawer: const Drawer(child: Text('data')),
// 右侧 侧滑抽屉菜单
endDrawer: const Drawer(child: Text('data')),
// 检测手势行为方式,与drawer配合使用 down 方式有卡顿,可以 start 方式
drawerDragStartBehavior: DragStartBehavior.start,
// 底部导航栏
bottomNavigationBar: const Text('底部导航栏'),
// 底部拉出菜单
bottomSheet: const Text('底部拉出菜单'),
// 背景色
backgroundColor: Colors.amberAccent,
// 自动适应底部padding
resizeToAvoidBottomInset: true,
// 正文:
// Builder 是一个特殊的 Widget,用来创建一个新的 BuildContext。
// 这么做的原因:Scaffold.of(context) 在 Flutter 里是用来获取离当前 context 最近的 ScaffoldState 实例的。
// 换句话说:找到当前 Widget 树里最靠近它的 Scaffold,然后你就可以用这个 Scaffold 的方法(比如 openDrawer()、showSnackBar())来操作页面。
// 如果直接用外部的 context,可能找不到最近的 Scaffold,会报错。
// 所以这里用 Builder 创建了一个位于 Scaffold 内部的新 context,让 Scaffold.of(context) 能找到 Scaffold。
body: Builder(
builder: (BuildContext context) {
return Center(
child: ElevatedButton(
onPressed: () {
// 脚手架管理
// 从当前 context 向上查找最近的 Scaffold,获取它的状态对象 ScaffoldState。
// 调用 openDrawer() 方法,手动打开 Scaffold 的 drawer(左侧抽屉菜单)。
// 这是一个页面结构操作。
Scaffold.of(context).openDrawer();
// 应用消息管理
// ScaffoldMessenger 是 Flutter 用来管理 SnackBar 的组件(比旧的 Scaffold.of(context).showSnackBar() 更灵活)。
// 它会从当前 context 找到最近的 ScaffoldMessenger,然后显示一个 SnackBar
// SnackBar 是一个底部弹出的提示条,这里显示 "Hello!",会在 2 秒后自动消失。
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Hello!'),
duration: Duration(seconds: 2),
),
);
},
child: const Text('应用消息管理'),
),
);
},
),
),
);
}
}

线性布局
在 Flutter 中,线性布局通常指用 Row
和 Column
这两个 Widget 来实现的布局方式。它们都是 线性排列的容器,可以把子 Widget 按 水平或垂直方向依次排列。
Row
= 水平方向的线性布局=https://api.flutter.dev/flutter/widgets/Row-class.htmlColumn
= 垂直方向的线性布局=https://api.flutter.dev/flutter/widgets/Column-class.html

Widget | 作用 | 方向 |
---|---|---|
Row |
水平排列子 Widget | 左到右(LTR)或右到左(RTL) |
Column |
垂直排列子 Widget | 上到下 |
定义一个线性布局
以下是Row的源码,Column的源码也类似
Row({
Key key,
// * 子元素集合
List<Widget> children = const <Widget>[],
// 主轴方向上的对齐方式(Row的主轴是横向轴)
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
// 在主轴方向(Row的主轴是横向轴)占有空间的值,默认是max
MainAxisSize mainAxisSize = MainAxisSize.max,
// 在交叉轴方向(Row是纵向轴)的对齐方式,Row的高度等于子元素中最高的子元素高度
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
// 水平方向子元素的排列方向:从左到右排列还是反向
TextDirection textDirection,
// 表示纵轴(垂直)的对齐排列方向,默认是VerticalDirection.down,表示从上到下。这个参数一般用于Column组件里
VerticalDirection verticalDirection = VerticalDirection.down,
// 字符对齐基线方式
TextBaseline textBaseline,
})
常用属性
主轴(Main Axis)
mainAxisAlignment
:沿 主轴(Row → 水平,Column → 垂直)对齐方式
常用值:
start
:靠起点对齐end
:靠终点对齐center
:居中spaceBetween
:两端对齐,间距平均分配spaceAround
:间距平均分配,首尾留一半间距spaceEvenly
:间距平均分配,首尾和中间一致
enum MainAxisAlignment {
// 按照主轴起点对齐,例如:按照靠近最左侧子元素对齐
start,
// 将子元素放置在主轴的末尾,按照末尾对齐
end,
// 子元素放置在主轴中心对齐
center,
// 将主轴方向上的空白区域均分,使得子元素之间的空白区域相等,首尾子元素都靠近首尾,没有间隙。有点类似于两端对齐
spaceBetween,
// 将主轴方向上的空白区域均分,使得子元素之间的空白区域相等,但是首尾子元素的空白区域为1/2
spaceAround,
// 将主轴方向上的空白区域均分,使得子元素之间的空白区域相等,包括首尾子元素
spaceEvenly,
}
交叉轴(Cross Axis)
crossAxisAlignment
:沿 交叉轴(Row → 垂直,Column → 水平)对齐方式
常用值:
start
/end
/center
/stretch
(填满交叉轴)
enum CrossAxisAlignment {
// 子元素在交叉轴上起点处展示
start,
// 子元素在交叉轴上末尾处展示
end,
// 子元素在交叉轴上居中展示
center,
// 让子元素填满交叉轴方向
stretch,
// 在交叉轴方向,使得子元素按照baseline对齐
baseline,
}
mainAxisSize
mainAxisSize
:控制主轴占用空间大小MainAxisSize.max
→ 占满父容器MainAxisSize.min
→ 只包裹子 Widget 所需大小
enum MainAxisSize {
// 根据传入的布局约束条件,最大化主轴方向占用可用空间,也就是尽可能充满可用宽度
max,
// 与max相反,是最小化占用主轴方向的可用空间
min,
}
代码示例
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(title: 'Flutter Demo', home: const MyHomePage());
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.amber,
child: Column(
// MainAxisAlignment.center : 纵向居中
mainAxisAlignment: MainAxisAlignment.center,
// CrossAxisAlignment.stretch : 横向拉伸
crossAxisAlignment: CrossAxisAlignment.stretch,
// MainAxisSize.min : 纵向压缩
mainAxisSize: MainAxisSize.min,
children: const [
FlutterLogo(size: 24),
FlutterLogo(size: 48),
FlutterLogo(size: 128),
FlutterLogo(size: 200),
],
),
),
);
}
}

Box Model 盒模型
Flutter 的盒模型组成
一个 Widget(尤其是 Container
)通常可以看作一个 矩形盒子,它由 四层 组成,从里到外:

Child(子 Widget):实际显示的内容
Padding(内边距):Child的 Widget 与边框(Border)的间距
Border(边框):盒子外层的边框线,可以设置边框厚度
Margin(外边距):盒子与外部其他 Widget 的间距
常见属于盒模型的 Widget
Widget 类型 | 盒模型元素支持情况 | 说明 |
---|---|---|
Container |
child + padding + border + margin | 最典型的盒子模型 Widget,可以控制内边距、外边距、边框、背景色等 |
Padding |
padding | 专门用来增加内边距 |
Align / Center |
child + alignment | 可以对 child 进行对齐,但不直接提供 margin/padding |
DecoratedBox |
decoration(可含边框/背景色) | 提供边框和背景装饰,但没有内边距或外边距,需要配合 Padding/Container |
SizedBox |
width/height | 用于控制子 Widget 尺寸,相当于“约束盒子” |
Card |
margin + child + decoration | Material 风格卡片,有默认外边距、圆角、阴影 |
ConstrainedBox |
constraints | 用来给 child 设置最小/最大尺寸 |
不是典型盒子模型的 Widget
Text
、Icon
、Image
等 渲染内容 Widget
- 它们本身是内容,不带 padding/margin/border
- 需要用
Container
或Padding
包裹才能形成完整盒子
Row
、Column
、Stack
等 布局容器
- 它们是父容器,用来排列子 Widget,本身不一定有 margin/border/padding
- 可以通过包裹 Container 来形成盒子效果
代码示例
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(title: 'Flutter Demo', home: const BoxPage());
}
}
class BoxPage extends StatelessWidget {
const BoxPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.blue,
body: Center(
child: Container(
// color: Colors.amber,
// Margin(外边距)
margin: const EdgeInsets.all(50),
// Padding(内边距)
padding: const EdgeInsets.all(20),
// 装饰样式
decoration: BoxDecoration(
// 背景色
color: Colors.amber,
// 边框
border: Border.all(color: Colors.red, width: 10),
),
// Content(内容)
child: Container(width: 200, height: 400, color: Colors.green),
),
),
);
}
}

容器Container
在 Flutter 中,Container
是最常用、最基础的布局 Widget之一,它本质上是一个 可绘制的矩形盒子,可以用来控制 尺寸、边距、内边距、边框、背景色、形状和对齐方式。
Container 的定义:
Container({
Key key,
// 容器子Widget对齐方式
this.alignment,
// 容器内部padding
this.padding,
// 背景色
Color color,
// 背景装饰: 可设置背景色、边框、圆角、渐变、阴影等
Decoration decoration,
// 前景装饰
this.foregroundDecoration,
// 容器的宽度
double width,
// 容器的高度
double height,
// 容器大小的限制条件
BoxConstraints constraints,
// 容器外部margin
this.margin,
// 变换,如旋转
this.transform,
// 容器内子Widget
this.child,
})
BoxDecoration 装饰
在 Flutter 中,BoxDecoration
是一个用来 美化容器(Container)或其他可绘制盒子 Widget 的类,它属于 装饰(decoration) 的概念。
BoxDecoration
= 容器的“外观样式”,控制背景、边框、圆角、阴影、渐变等视觉效果,而不是布局或尺寸。
const BoxDecoration({
// 背景色
this.color,
// 背景图片
this.image,
// 边框样式
this.border,
// 边框圆角
this.borderRadius,
// 阴影
this.boxShadow,
// 渐变
this.gradient,
// 背景混合模式
this.backgroundBlendMode,
// 形状
this.shape = BoxShape.rectangle,
})
示例
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(title: 'Flutter Demo', home: const MyPage());
}
}
const img1 = "https://linresource.uk/img/13399635147767705.jpg";
class MyPage extends StatelessWidget {
const MyPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
// 约束父容器
constraints: const BoxConstraints.expand(height: 400.0),
// 外边距
// EdgeInsets.only(top: 40): 仅设置上边距为 40.0 像素
margin: const EdgeInsets.only(top: 40),
// 内边距
// EdgeInsets.all(30.0): 所有方向的内边距都设置为 30.0 像素
padding: EdgeInsets.all(30.0),
// 背景色
// color: Colors.teal.shade700,
// 子Widget: Alignment.centerLeft 表示左对齐且居中
// Alignment.center 表示居中对齐
alignment: Alignment.center,
// 背景装饰
decoration: BoxDecoration(
// 背景色
color: Colors.blueAccent,
// 圆角
borderRadius: BorderRadius.all(Radius.circular(20.0)),
// 渐变
// gradient: RadialGradient(
// colors: [Colors.red, Colors.orange],
// center: Alignment.topLeft,
// radius: .98,
// ),
// 阴影
boxShadow: [
BoxShadow(
blurRadius: 10,
offset: Offset(0, 50),
color: Colors.blue,
),
],
// 背景图
image: DecorationImage(image: NetworkImage(img1), fit: BoxFit.cover),
// 背景混合模式
backgroundBlendMode: BlendMode.color,
// 形状
// shape: BoxShape.circle,
),
// 前景装饰
foregroundDecoration: BoxDecoration(
// 前景色:withOpacity 已经过时
// color: Colors.amber.withOpacity(0.5),
// color: Colors.amber.withValues(alpha: 0.5),
color: Colors.amber.withAlpha(128),
),
// Container旋转
transform: Matrix4.rotationZ(0.1),
// 子Widget元素
child: Text(
'Hello World',
style: Theme.of(
context,
).textTheme.headlineMedium?.copyWith(color: Colors.white),
),
),
);
}
}

设置内外边距的方法
Container(
padding: EdgeInsets.all(16), // 所有方向 16 像素
child: Text('内边距示例'),
)
方法 | 说明 |
---|---|
EdgeInsets.all(double value) |
四个方向(上、下、左、右)相同值 |
EdgeInsets.symmetric({double vertical, double horizontal}) |
垂直方向和水平方向分别设置 |
EdgeInsets.only({left, top, right, bottom}) |
单独设置每个方向 |
EdgeInsets.fromLTRB(left, top, right, bottom) |
另一种单独设置四个方向的方法 |
Colors组件的两个方法
withAlpha()
设置透明度
withAlpha(int a)
用 0~255 的值 设置透明度:
Color color = Colors.amber.withAlpha(128); // 半透明
0
→ 完全透明
255
→ 完全不透明
withValues()
(Flutter 3.13+ 新 API)
Color withValues({
double? alpha,
double? red,
double? green,
double? blue,
ColorSpace? colorSpace,
})
参数 | 类型 | 说明 |
---|---|---|
alpha |
double? |
透明度,0.0 = 完全透明,1.0 = 不透明 |
red |
double? |
红色分量,0.0 ~ 1.0 |
green |
double? |
绿色分量,0.0 ~ 1.0 |
blue |
double? |
蓝色分量,0.0 ~ 1.0 |
colorSpace |
ColorSpace? |
颜色空间(可选,默认 sRGB) |
弹性布局 Flex
什么是弹性布局
在软件开发和 UI 布局中,**弹性布局(Flexible Layout / Flex Layout)**指的是:子元素可以根据可用空间动态伸缩,占用父容器剩余空间或按比例分配空间,而不是固定尺寸。
换句话说,弹性布局的核心就是 空间可以“弹性”分配,不必每个子元素都写死宽高。
在 Flutter 中,弹性布局(Flex) 是一种 按比例分配空间的线性布局,可以理解为 “增强版的 Row/Column”,它允许子 Widget 按比例(flex 值)来分配父容器的剩余空间。
Row
和 Column
本质上就是 Flex
的封装,Flex 的方向不同而已。
Flex 的定义
和前面的Row
和 Column
类似。
Flex({
Key? key,
required Axis direction, // 主轴方向: Axis.horizontal / Axis.vertical
MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start,
MainAxisSize mainAxisSize = MainAxisSize.max,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextDirection? textDirection,
VerticalDirection verticalDirection = VerticalDirection.down,
TextBaseline? textBaseline,
List<Widget> children = const <Widget>[],
})
属性 | 说明 |
---|---|
direction |
主轴方向:水平(Row)或垂直(Column) |
mainAxisAlignment |
主轴对齐方式 |
crossAxisAlignment |
交叉轴对齐方式 |
children |
子 Widget 列表 |
mainAxisSize |
主轴占用空间大小(min/max) |
Expanded
Expanded 只能放在 Flex、Column、Row 中使用
Expanded的作用:
- 强制子 Widget 占满剩余空间
flex
决定占剩余空间的比例
强制子 Widget 占满剩余空间
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(title: 'Flutter Demo', home: const FlexPage());
}
}
class FlexPage extends StatelessWidget {
const FlexPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
// 尽管设置了宽度,但由于使用了 Expanded,仍然会占据剩余空间
Expanded(child: Container(color: Colors.amber, width: 100)),
Container(color: Colors.blue, width: 100, height: 100),
],
),
);
}
}

class FlexPage extends StatelessWidget {
const FlexPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
// 尽管设置了宽度,但由于使用了 Expanded,仍然会占据剩余空间
Expanded(child: Container(color: Colors.amber, width: 10)),
Expanded(child: Container(color: Colors.blue, width: 600)),
],
),
);
}
}
被Expanded
修饰的两个容器的width
被平均分别,尽管两个容器都设置了width
的大小

flex
决定占剩余空间的比例
class FlexPage extends StatelessWidget {
const FlexPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Row(
children: [
// 尽管设置了宽度,但由于使用了 Expanded,仍然会占据剩余空间
Expanded(flex: 2, child: Container(color: Colors.amber, width: 10)),
Expanded(flex: 1, child: Container(color: Colors.blue, width: 600)),
],
),
);
}
}
水平方向总
空间被 2:1
比例分配给黄色和蓝色容器

Flexible
子 Widget 可按比例占用空间,但允许自身缩小或包裹内容
fit
可选择 FlexFit.tight
(填满)或 FlexFit.loose
(包裹内容)
没有Flexible按照设置的width
和height
布局
class FlexPage extends StatelessWidget {
const FlexPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.grey,
height: 500,
child: Column(
children: [
Container(color: Colors.amber, height: 100),
Container(color: Colors.blue, height: 100),
],
),
),
);
}
}

使用Flexible:
class FlexPage extends StatelessWidget {
const FlexPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.grey,
height: 500,
child: Column(
children: [
Flexible(child: Container(color: Colors.amber)),
Flexible(child: Container(color: Colors.blue)),
],
),
),
);
}
使用Flexible之后,两个容器共同分享了剩余的300的空间,平均每一个是250的高度。
另外,这里我的两个我都没有设置height
,这是因为,如果我Flexible
不会自动忽略子组件的固定高度height: 100
,而是让子组件 在剩余空间内,按需要伸缩。如果给给 Container
明确写了 height: 100
,所以它们不会去填满剩余的 300 高度。所以我在这里去掉了height
设置。

fit
配置
fit
在 Flutter 的弹性布局里其实就是控制 “给子组件的空间是紧的还是松的”。
FlexFit.loose
(默认值):
- 父组件把空间分配好后,你可以决定自己的尺寸(如果设置了固定高度,就用固定高度;没设置就会尽量扩展)
FlexFit.tight
:
- 父组件把分配到的剩余空间按比例分好,然后强制子组件填满这块地盘(如果设置了高度和宽度也是无效的)。
Expanded
就是Flexible(flex: 1, fit: FlexFit.tight)
的简写。
class FlexPage extends StatelessWidget {
const FlexPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.grey,
height: 500,
child: Column(
children: [
Flexible(
fit: FlexFit.tight,
child: Container(color: Colors.amber, height: 100),
),
Flexible(
fit: FlexFit.tight,
child: Container(color: Colors.blue, height: 100),
),
],
),
),
);
}
}
使用fix分配空间
class FlexPage extends StatelessWidget {
const FlexPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.grey,
height: 500,
child: Column(
children: [
// Container(color: Colors.amber),
// Container(color: Colors.blue),
// Container(color: Colors.amber, height: 100),
// Container(color: Colors.blue, height: 100),
// Flexible(child: Container(color: Colors.amber, height: 100)),
// Flexible(child: Container(color: Colors.blue, height: 100)),
Flexible(
flex: 1,
fit: FlexFit.tight,
child: Container(color: Colors.amber, height: 100),
),
Flexible(
flex: 2,
fit: FlexFit.tight,
child: Container(color: Colors.blue, height: 100),
),
],
),
),
);
}
}
500的空间按照1:2
的比例分配

Spacer
Spacer
其实就是 一个专门用来“占空”并推开其它组件的工具,它本质上就是 Expanded
的一个简化版。
在 Flutter 的 Row
或 Column
中,Spacer
会:
- 占据可用的剩余空间
- 不能设置宽度/高度
- 可通过
flex
参数控制占用比例
内部实现等同于:
Expanded(
flex: x, // 默认 1
child: SizedBox.shrink(), // 一个啥也没有的空盒子
)
Spacer
主要用来在弹性布局中调整组件之间的间距,而不需要手动加 Expanded + Container
。
class FlexPage extends StatelessWidget {
const FlexPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.grey,
height: 500,
child: Column(
children: [
Flexible(
flex: 3,
fit: FlexFit.tight,
child: Container(color: Colors.amber, height: 100),
),
const Spacer(), // 默认flex=1
Flexible(
flex: 3,
fit: FlexFit.tight,
child: Container(color: Colors.blue, height: 100),
),
const Spacer(flex: 2), // flex=2
],
),
),
);
}
}

层叠布局 Stack
基本作用
Stack
会把多个子组件 叠放在一起(Z 轴方向的布局),和 Row
/ Column
不同的是:
Row
/Column
是沿 X / Y 轴排列Stack
是沿 Z 轴 叠放
以把 Stack
想象成 Photoshop 里的图层系统,后添加的元素会叠在前面的上面。
在 Flutter 的 Stack
中,默认的排列(叠放)顺序就是按照 children
列表里的代码顺序来决定的:
- 前面写的子组件(列表中靠前的)会先绘制,所以它会在下面。
- 后面写的子组件会后绘制,所以它会盖在上面。
Stack的定义
Stack({
Key key,
// 对齐方式,默认是左上角(topStart)
this.alignment = AlignmentDirectional.topStart,
// 对齐方向
this.textDirection,
// 定义如何设置无定位子元素尺寸,默认为loose
this.fit = StackFit.loose,
// 对超出 Stack 显示空间的部分如何剪裁
this.clipBehavior = Clip.hardEdge,
// 子元素
List<Widget> children = const <Widget>[],
})
基本语法
class StackPage extends StatelessWidget {
const StackPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
// 居中对齐
alignment: Alignment.center,
// 子元素层叠放
children: [
// 三个色块
Container(width: 100, height: 100, color: Colors.green),
Container(width: 300, height: 300, color: Colors.amber),
Container(width: 200, height: 200, color: Colors.blue),
],
),
);
}
}
由于绿色色块更靠前,所有先绘制,又由于绿色色块小,会被红色色块盖住,所有不会显示。

Stack
的大小默认由最大子元素(300×300 的那个容器)决定。

Positioned绝对定位
class StackPage extends StatelessWidget {
const StackPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
// 居中对齐
alignment: Alignment.center,
// 子元素溢出, none 不裁剪
clipBehavior: Clip.none,
// 子元素层叠放
children: [
// 三个色块
Container(width: 100, height: 100, color: Colors.green),
Container(width: 300, height: 300, color: Colors.amber),
Container(width: 200, height: 200, color: Colors.blue),
// 绝对定位
const Positioned(left: 0, bottom: -50, child: FlutterLogo(size: 100)),
],
),
);
}
}

clipBehavior
:决定超出 Stack
范围的内容是否裁剪:
Clip.none
:不裁剪Clip.hardEdge / Clip.antiAlias
:裁剪掉溢出的部分
Positioned
是用来给子组件绝对定位的,可以用 left/right/top/bottom
任意组合控制位置。
const Positioned({
Key key,
this.left, // 上下左右位置
this.top,
this.right,
this.bottom,
this.width, // 宽高
this.height,
@required Widget child,
})
流式布局 Wrap
什么是流式布局
Row
/Column
的子组件超出主轴空间时会溢出报错(出现黄黑相间的“溢出警告条”)。Wrap
会自动换行(或换列),像文字一样流动,所以叫“流式布局”。- 常用于标签列表、照片墙、按钮组等场景。
Wrap的定义
Wrap({
// 主轴方向: 水平(Axis.horizontal)或垂直(Axis.vertical) 默认水平方向
this.direction = Axis.horizontal,
//主轴方向的对齐方式(每一行内部怎么排)
this.alignment = WrapAlignment.start,
// 主同一行(列)中,子组件之间的间距
this.spacing = 0.0,
// 纵向(换行方向)的对齐方式
this.runAlignment = WrapAlignment.start,
// 行与行之间(或列与列之间)的间距
this.runSpacing = 0.0,
// 交叉轴方向的对齐方式
this.crossAxisAlignment = WrapCrossAlignment.start,
// textDirection / verticalDirection 控制排列方向(从左到右/从右到左,从上到下/从下到上)
this.textDirection,
this.verticalDirection = VerticalDirection.down,
List<Widget> children = const <Widget>[],
})
参数 | 作用 |
---|---|
direction | 主轴方向,水平(Axis.horizontal )或垂直(Axis.vertical ) |
spacing | 同一行(列)中,子组件之间的间距 |
runSpacing | 行与行之间(或列与列之间)的间距 |
alignment | 主轴方向的对齐方式(每一行内部怎么排) |
runAlignment | 纵向(换行方向)的对齐方式 |
crossAxisAlignment | 交叉轴方向的对齐方式 |
textDirection / verticalDirection | 控制排列方向(从左到右/从右到左,从上到下/从下到上) |
代码示例
class WrapPage extends StatelessWidget {
const WrapPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: SizedBox(
height: 600,
child: Wrap(
// 主轴方向子widget的间距
spacing: 10,
// 交叉轴方向的间距
runSpacing: 30,
// 主轴方向的对齐方式
alignment: WrapAlignment.end,
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Container(width: 70, height: 100, color: Colors.green),
Container(width: 10, height: 100, color: Colors.blue),
Container(width: 70, height: 100, color: Colors.red),
Container(width: 70, height: 80, color: Colors.yellow),
Container(width: 70, height: 80, color: Colors.orange),
Container(width: 10, height: 80, color: Colors.purple),
Container(width: 70, height: 80, color: Colors.brown),
Container(width: 70, height: 100, color: Colors.grey),
Container(width: 170, height: 100, color: Colors.black),
Container(width: 70, height: 10, color: Colors.pink),
Container(width: 20, height: 100, color: Colors.cyan),
Container(width: 70, height: 10, color: Colors.lime),
],
),
),
);
}
}

runAlignment 和 crossAxisAlignment 的区别
crossAxisAlignment
:
控制 每一行内 元素在交叉轴上的对齐方式。
类似于
Row
的crossAxisAlignment
。生效范围:单行内部的元素。
例子(
Axis.horizontal
):所有元素都贴在行顶部对齐。在一行内。crossAxisAlignment: start ┌─────────────── 容器 ──────────────┐ [█ ] [██ ] [███ ]
runAlignment
:
控制 整行之间 在交叉轴方向的对齐方式。
影响的是多行之间的分布,不是单行内的对齐。
类似于
Column
的mainAxisAlignment
(因为换行后每一行就像一个 “子Column”)。例子(
Axis.horizontal
):交叉轴方向(垂直)靠下+--------------------+ ← 空白 [AAA AAA AAA] ← 第一行 [BBB BBB BBB] ← 第二行 [CCC CCC] ← 第三行 +--------------------+
runAlignment
成立的前提是,Wrap
的大小,要大于Wrap
内所有元素的和
SizedBox
SizedBox
主要是创建一个固定尺寸的容器
SizedBox(width: 300, height: 300)
表示它的宽度固定为 300,高度固定为 300。
区别于 Container
SizedBox
专注于固定宽高和占位,本身没有背景色、边框等修饰属性。Container
功能更多(可以设置背景色、边框、内外边距等)。
SizedBox
也可以只指定宽度或高度,用来占据特定的空间
SizedBox(height: 20); // 相当于竖直方向加一个间距
SizedBox(width: 10); // 横向间距
特殊构造函数
SizedBox.shrink()
:尺寸为 0×0,通常用来“占位但不显示”。SizedBox.expand()
:尽可能填满父组件给的约束空间。
约束传递(constraints passing)
Flutter 布局的三步规则:
- 父传约束(constraints)给子
- 子在约束范围内确定自己的大小(size)
- 父根据子大小和对齐方式来摆放(position)
SizedBox(height: 600)
的行为
SizedBox(height: 600)
会生成一个BoxConstraints
,相当于:minHeight = 600 maxHeight = 600
也就是固定高度。
这个约束会被直接传递给它的子组件,比如:
SizedBox(height: 600, child: Wrap(...), );
→
Wrap
收到的约束就是 “必须正好 600 高度”
→Wrap
再把这个约束传给它的孩子们(但会根据自己的布局规则调整)。
Container
的行为
- 如果你给
Container
设置了height
/width
,它的表现和SizedBox
是一样的:直接在约束中锁定对应尺寸。 - 如果没设置尺寸,
Container
会尽可能去匹配父给的约束,可能会变成Expanded
一样的拉伸行为,或是和内容一样大(取决于父的约束)。
wrap的默认大小
Wrap
在 Flutter 中的默认大小有两个要点:
- 主轴方向(
direction
)默认是水平的- 主轴方向的大小会自适应内容宽度,直到撑满父容器的约束为止。
- 如果父容器给了无限空间(比如
SingleChildScrollView
横向包裹),那它会根据子元素的总宽度来定。
- 交叉轴方向(垂直方向,如果
direction
是水平)默认也是自适应内容- 它会计算出所有行的总高度(每行的高度 +
runSpacing
),正好包裹所有行。
- 它会计算出所有行的总高度(每行的高度 +
总结:
- 如果没有父类的约束,那么Wrap的大小就是子元素的总和
- 如果有父类的约束,那么Wrap的大小就是父约束指定的大小
对齐定位 Align
Align
是什么
Align
是一个用于 对子组件进行对齐和定位 的布局组件。它的作用是:
- 根据
alignment
参数,将 子组件 在自身范围内按指定位置摆放。 - 可以配合
widthFactor
和heightFactor
决定Align
自己的大小。
Align的定义
Align({
Key key,
// 需要一个AlignmentGeometry类型的值
// AlignmentGeometry 是一个抽象类,
// 它有两个常用的子类:Alignment和 FractionalOffset
this.alignment = Alignment.center,
// 两个缩放因子
// 会分别乘以子元素的宽、高,最终的结果就是 Align 组件的宽高
this.widthFactor,
this.heightFactor,
Widget child,
})
alignment
:控制子组件在 Align
内部的位置,常用值如下
值 | 含义 |
---|---|
Alignment.center |
居中 |
Alignment.topLeft |
左上角 |
Alignment.topRight |
右上角 |
Alignment.bottomLeft |
左下角 |
Alignment.bottomRight |
右下角 |
Alignment(0.0, -0.5) |
自定义位置(横向居中,纵向靠上) |
widthFactor
/ heightFactor
- 默认情况下,
Align
会尽量 占满父容器。 - 如果指定
widthFactor
或heightFactor
,Align
的大小会等于子组件大小
×对应 factor
。 - 常用场景:让
Align
自己的尺寸收缩到子组件的大小附近。
举例说明
class AlignPage extends StatelessWidget {
const AlignPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Align(
alignment: Alignment.bottomLeft,
child: Container(width: 100, height: 100, color: Colors.amber),
),
);
}
}
默认情况下,Align
会尽量 占满父容器

class AlignPage extends StatelessWidget {
const AlignPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Align(
widthFactor: 2,
heightFactor: 2,
alignment: Alignment.bottomLeft,
child: Container(width: 100, height: 100, color: Colors.amber),
),
);
}
}
如果指定 widthFactor
或 heightFactor
,Align
的大小会等于 子组件大小
× 对应 factor
Align
=子组件大小 * (2*2)

Alignment
Alignment
是 一个二维坐标系上的位置描述类(Alignment extends AlignmentGeometry
)。
用来指定 子组件在父组件内部的位置。
最常用在 Align
、Stack
、FractionalOffset
等需要控制位置的地方。
Alignment
的坐标是 相对父容器 的:
- x 方向:-1.0(最左) → 0.0(中间) → 1.0(最右)
- y 方向:-1.0(最上) → 0.0(中间) → 1.0(最下)

(-1,-1) (0,-1) (1,-1)
topLeft topCenter topRight
(-1, 0) (0, 0) (1, 0)
centerLeft center centerRight
(-1, 1) (0, 1) (1, 1)
bottomLeft bottomCenter bottomRight
以下是Alignment
定义的常量:
/// The top left corner.
static const Alignment topLeft = Alignment(-1.0, -1.0);
/// The center point along the top edge.
static const Alignment topCenter = Alignment(0.0, -1.0);
/// The top right corner.
static const Alignment topRight = Alignment(1.0, -1.0);
/// The center point along the left edge.
static const Alignment centerLeft = Alignment(-1.0, 0.0);
/// The center point, both horizontally and vertically.
static const Alignment center = Alignment(0.0, 0.0);
/// The center point along the right edge.
static const Alignment centerRight = Alignment(1.0, 0.0);
/// The bottom left corner.
static const Alignment bottomLeft = Alignment(-1.0, 1.0);
/// The center point along the bottom edge.
static const Alignment bottomCenter = Alignment(0.0, 1.0);
/// The bottom right corner.
static const Alignment bottomRight = Alignment(1.0, 1.0);
我们也可以自定义位置:Alignment(x, y)
class AlignPage extends StatelessWidget {
const AlignPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Align(
widthFactor: 2,
heightFactor: 2,
alignment: Alignment(0.5, -0.5), // 自定义位置
child: Container(width: 100, height: 100, color: Colors.amber),
),
);
}
}

FractionalOffset
FractionalOffset
,它其实就是 Alignment
的一个特殊实现版本,用比例来表示子组件在父组件中的位置。
FractionalOffset
这种方式是固定从左上角出发
FractionalOffset(double dx, double dy)
dx
: 水平方向比例,0.0 = 左边,1.0 = 右边dy
: 垂直方向比例,0.0 = 顶部,1.0 = 底部

class AlignPage extends StatelessWidget {
const AlignPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Align(
widthFactor: 2,
heightFactor: 2,
alignment: FractionalOffset(0, 0),
child: Container(width: 100, height: 100, color: Colors.amber),
),
);
}
}
Center
Center 是集成了 Align 对象,默认 alignment=Alignment.center
Center 定义, 少了一个 alignment
参数
class Center extends Align {
/// Creates a widget that centers its child.
const Center({ Key? key, double? widthFactor, double? heightFactor, Widget? child })
: super(key: key, widthFactor: widthFactor, heightFactor: heightFactor, child: child);
}
然后 Align 定义, 默认了 this.alignment = Alignment.center
,
class Align extends SingleChildRenderObjectWidget {
/// Creates an alignment widget.
///
/// The alignment defaults to [Alignment.center].
const Align({
Key? key,
this.alignment = Alignment.center, // 默认使用了
this.widthFactor,
this.heightFactor,
Widget? child,
}) : assert(alignment != null),
assert(widthFactor == null || widthFactor >= 0.0),
assert(heightFactor == null || heightFactor >= 0.0),
super(key: key, child: child);