前言:
这一讲是对样式的讲解,可能多点布局的高阶组件,但用处不大。
在前面两讲中都有涉猎,对大局熟悉没有影响,跳过也没事,之后用到什么AI一下就都知道了。反正都在函数的入参里,知道是哪个widget,就很好找,很好理解,现在看了用少了也记不住。
之所以有这讲,也是考虑到,第一部分UI基石体系的全面性,做一个样式层面的end。
一、本章核心目标
本章聚焦 Flutter 中精细化控制UI布局与视觉表现的核心技术,解决基础布局无法满足的「精准间距控制、弹性空间分配、约束适配、视觉装饰」等问题。
通过掌握 Padding/Margin/Border、Flex/Expanded、BoxConstraints、LayoutBuilder 这些核心工具,你能从「能用」的布局升级为「好用、好看、适配全场景」的专业级UI实现,让界面既符合设计规范,又能自适应不同屏幕尺寸。
暂时无法在飞书文档外展示此内容
核心原理解读
- 约束是布局的「规则上限」:父组件通过
BoxConstraints告诉子组件「你最大/最小能多大」(紧约束:固定尺寸;松约束:范围尺寸); - 装饰是「视觉包装」:Padding/Margin/Border 不改变组件的核心尺寸计算逻辑,仅在组件绘制时增加「内边距/外边距/边框」的视觉区域;
- 弹性布局是「空间分配策略」:Flex(Row/Column)+ Expanded 解决「剩余空间如何分配」的问题,让多个子组件按比例占用空间;
- LayoutBuilder 是「约束感知工具」:能主动获取父组件的约束,实现「不同约束下展示不同布局」的自适应效果。
二、核心技术点
1. Padding、Margin、Border(装饰与间距)
核心属性
| 组件/属性 | 作用 | 关键参数 |
|---|---|---|
| Padding | 内边距(组件内容与边界的间距) | padding: EdgeInsets(all/symmetric/only) |
| Margin(Container属性) | 外边距(组件与其他组件的间距) | margin: EdgeInsets(同Padding) |
| Border | 边框装饰 | border: Border.all(width, color)、borderRadius |
案例代码
import 'package:flutter/material.dart';
void main() => runApp(const PaddingMarginBorderDemo());
class PaddingMarginBorderDemo extends StatelessWidget {
const PaddingMarginBorderDemo({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('间距与边框装饰')),
body: Center(
child: Container(
// 外边距:与父组件(Center)的间距
margin: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
// 内边距:内容与Container边界的间距
padding: const EdgeInsets.all(15),
// 边框+圆角装饰
decoration: BoxDecoration(
border: Border.all(width: 2, color: Colors.blue),
borderRadius: BorderRadius.circular(10),
),
child: const Text(
'精细化UI装饰',
style: TextStyle(fontSize: 20),
),
),
),
),
);
}
}
注意事项
Padding是独立组件,margin是Container的属性(本质是给Container套了一层Padding);Border必须配合BoxDecoration使用,不能直接作为组件属性;EdgeInsets.only()可精准控制单方向间距(如left: 10, top: 5),适配非对称布局;- 圆角边框(
borderRadius)与BoxDecoration的color/image兼容,但不能与BoxShape.circle同时使用。
2. Flex、Expanded(弹性空间分配)
核心属性
| 组件/属性 | 作用 | 关键参数 |
|---|---|---|
| Flex/Row/Column | 弹性布局容器(Row=水平Flex,Column=垂直Flex) | direction(主轴方向)、mainAxisAlignment(主轴对齐)、crossAxisAlignment(交叉轴对齐) |
| Expanded | 抢占Flex容器的剩余空间 | flex: int(空间占比,默认1) |
案例代码
import 'package:flutter/material.dart';
void main() => runApp(const FlexExpandedDemo());
class FlexExpandedDemo extends StatelessWidget {
const FlexExpandedDemo({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('弹性空间分配')),
body: Padding(
padding: const EdgeInsets.all(20),
// Row = 水平方向的Flex
child: Row(
// 主轴(水平)对齐方式:均分剩余空间
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// 不占剩余空间,仅包裹内容
const Text('固定宽度'),
// 占剩余空间的1份
Expanded(
flex: 1,
child: Container(
height: 40,
color: Colors.red,
child: const Center(child: Text('占1份')),
),
),
// 占剩余空间的2份
Expanded(
flex: 2,
child: Container(
height: 40,
color: Colors.blue,
child: const Center(child: Text('占2份')),
),
),
],
),
),
),
);
}
}
注意事项
Expanded只能作为Flex/Row/Column的直接子组件,否则会报错;flex是「比例」而非「固定尺寸」,比如 flex:1 和 flex:2 会按 1:2 分配剩余空间;- 避免在
Expanded中嵌套无限宽/高的组件(如未设置width的TextField),会导致布局溢出。
3. BoxConstraints(紧/松约束)
核心概念
| 约束类型 | 定义 | 示例场景 |
|---|---|---|
| 紧约束 | 固定宽高(min=max=具体值) | Container(width: 100, height: 50) |
| 松约束 | 宽高有范围(min≠max) | Container(width: double.infinity, height: 50)(宽度占满父容器) |
案例代码
import 'package:flutter/material.dart';
void main() => runApp(const BoxConstraintsDemo());
class BoxConstraintsDemo extends StatelessWidget {
const BoxConstraintsDemo({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('紧/松约束')),
body: Center(
child: Container(
// 父容器:松约束(宽度最大300,高度固定80)
constraints: BoxConstraints(
maxWidth: 100,
minHeight: 80,
maxHeight: 100,
),
color: Colors.red[200],
child: Container(
// 子容器:紧约束(固定宽高)
constraints: BoxConstraints.tight(Size(3000, 60)),
color: Colors.green,
child: const Center(child: Text('紧约束子组件')),
),
),
),
),
);
}
}
注意事项
BoxConstraints可通过constraints属性直接设置给Container,优先级高于直接设置的width/height;- 子组件的约束不能超过父组件的约束(比如父maxWidth=100,子width=3000会被限制为100);
BoxConstraints.expand()是松约束的常用写法,等价于width: double.infinity, height: double.infinity(占满父容器)。
4. LayoutBuilder(获取父约束)
核心作用
LayoutBuilder 能捕获父组件传递的约束(BoxConstraints),根据约束动态调整布局(比如「窄屏垂直排列、宽屏水平排列」)。
案例代码
import 'package:flutter/material.dart';
void main() => runApp(const LayoutBuilderDemo());
class LayoutBuilderDemo extends StatelessWidget {
const LayoutBuilderDemo({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('LayoutBuilder 约束感知')),
body: Padding(
padding: const EdgeInsets.all(10),
// LayoutBuilder捕获父组件的约束
child: LayoutBuilder(
builder: (context, constraints) {
// 获取父容器的最大宽度
double parentWidth = constraints.maxWidth;
// 宽屏(>600):水平排列;窄屏:垂直排列
bool isWideScreen = parentWidth > 600;
return Container(
color: Colors.grey[100],
// 根据屏幕宽度切换布局方向
child: isWideScreen
? const Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [Text('宽屏布局'), Text('水平排列')],
)
: const Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [Text('窄屏布局'), Text('垂直排列')],
),
);
},
),
),
),
);
}
}
注意事项
LayoutBuilder的builder函数会在父约束变化时重新执行(如屏幕旋转),适合做自适应布局;- 不要在
builder中执行耗时操作,会影响布局性能; constraints参数是父组件传递的约束,而非当前组件的最终尺寸。
四、综合应用案例
需求场景
实现一个「自适应屏幕的商品卡片」:
- 卡片有圆角边框、内边距、外边距;
- 宽屏(>600)时水平排列(图片+信息),窄屏时垂直排列;
- 信息区域弹性分配剩余空间,价格文字占比更大;
- 卡片尺寸受父约束限制(最大宽度500,最小高度200)。
注意点:先通过BoxConstraints定尺寸边界,再用Padding/Margin/Border做装饰,最后结合Flex/Expanded/LayoutBuilder实现弹性+自适应布局。
完整代码
import 'package:flutter/material.dart';
void main() => runApp(const ProductCardDemo());
class ProductCardDemo extends StatelessWidget {
const ProductCardDemo({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primarySwatch: Colors.blue),
home: Scaffold(
appBar: AppBar(title: const Text('综合布局装饰案例')),
body: Center(
// 父容器:给卡片设置最大宽度约束
child: Container(
constraints: const BoxConstraints(maxWidth: 500),
padding: const EdgeInsets.all(15), // 页面内边距
child: _buildProductCard(),
),
),
),
);
}
// 商品卡片核心组件
Widget _buildProductCard() {
return LayoutBuilder(
builder: (context, constraints) {
// 1. 获取父约束,判断屏幕/容器宽度
bool isWide = constraints.maxWidth > 600;
double imageSize = isWide ? 180 : double.infinity; // 宽屏图片固定宽,窄屏占满
// 2. 卡片容器:装饰(边框、圆角)+ 外边距 + 最小高度约束
return Container(
margin: const EdgeInsets.only(bottom: 10), // 卡片外边距
constraints: const BoxConstraints(minHeight: 200), // 紧约束-最小高度
decoration: BoxDecoration(
border: Border.all(width: 1, color: Colors.grey[300]!),
borderRadius: BorderRadius.circular(12), // 圆角边框
boxShadow: const [BoxShadow(color: Colors.black12, blurRadius: 4)],
),
// 3. 弹性布局:宽屏Row,窄屏Column
child: isWide ? Row(children: _buildCardChildren(imageSize)) : Column(children: _buildCardChildren(imageSize)),
);
},
);
}
// 卡片子组件(图片+信息区域)
List<Widget> _buildCardChildren(double imageSize) {
return [
// 商品图片:内边距 + 固定尺寸
Padding(
padding: const EdgeInsets.all(10), // 图片内边距
child: Container(
width: imageSize,
height: 180,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8),
image: const DecorationImage(
image: NetworkImage('https://picsum.photos/200/200'),
fit: BoxFit.cover,
),
),
),
),
// 信息区域:弹性分配剩余空间
Expanded(
child: Padding(
padding: const EdgeInsets.all(15), // 信息区域内边距
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Flutter 进阶布局实战商品',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text('精细化UI布局 + 自适应屏幕', style: TextStyle(color: Colors.grey)),
const SizedBox(height: 12),
// 价格区域:弹性占比
Row(
children: [
Expanded(
flex: 2, // 价格占2份
child: Text(
'¥99.00',
style: TextStyle(fontSize: 20, color: Colors.red[600], fontWeight: FontWeight.bold),
),
),
Expanded(
flex: 1, // 销量占1份
child: Text(
'销量 1000+',
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
textAlign: TextAlign.right,
),
),
],
),
],
),
),
),
];
}
}
效果说明
- 当父容器宽度>600时:卡片水平排列,图片固定180宽度,信息区域占剩余空间,价格文字占比2/3,销量占1/3;
- 当父容器宽度≤600时:卡片垂直排列,图片占满宽度,信息区域垂直展示;
- 卡片整体有圆角边框、阴影、内外边距,尺寸受约束限制(最大宽度500,最小高度200),适配不同屏幕。