Flutter使用约束传递模型:父组件告诉子组件自己的约束,子组件在约束内决定自己的大小,然后告诉父组件。
// ✅ 验证通过:Flutter 3.22
/// Row核心属性
Row(
mainAxisAlignment, // 主轴(水平)对齐
crossAxisAlignment, // 交叉轴(垂直)对齐
mainAxisSize, // 主轴大小
textDirection, // 文本方向(LTR/RTL)
verticalDirection, // 垂直方向(down/up)
children, // 子组件列表
)
// mainAxisAlignment 选项:
// start ←[A][B][C] 靠左
// center ← [A][B][C] → 居中
// end [A][B][C]→ 靠右
// spaceBetween [A] [B] [C] 两端对齐
// spaceAround [A] [B] [C] 等间距(含两端半间距)
// spaceEvenly [A] [B] [C] 完全等间距
// crossAxisAlignment 选项:
// start 顶部对齐
// center 居中对齐
// end 底部对齐
// stretch 拉伸填满
// baseline 基线对齐(需指定textBaseline)
class RowDemos extends StatelessWidget {
const RowDemos({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 基本Row
const Text('基本水平排列:'),
Row(
children: [
Container(width: 50, height: 50, color: Colors.red),
Container(width: 50, height: 50, color: Colors.green),
Container(width: 50, height: 50, color: Colors.blue),
],
),
const SizedBox(height: 16),
// 居中对齐
const Text('居中排列:'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(width: 50, height: 50, color: Colors.red),
Container(width: 50, height: 50, color: Colors.green),
Container(width: 50, height: 50, color: Colors.blue),
],
),
const SizedBox(height: 16),
// 等间距
const Text('等间距排列:'),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Container(width: 50, height: 50, color: Colors.red),
Container(width: 50, height: 50, color: Colors.green),
Container(width: 50, height: 50, color: Colors.blue),
],
),
const SizedBox(height: 16),
// 交叉轴对齐
const Text('交叉轴对齐:'),
SizedBox(
height: 100,
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Container(width: 50, height: 30, color: Colors.red),
Container(width: 50, height: 60, color: Colors.green),
Container(width: 50, height: 45, color: Colors.blue),
],
),
),
const SizedBox(height: 16),
// Expanded — 占据剩余空间
const Text('Expanded分配空间:'),
Row(
children: [
Container(width: 50, height: 50, color: Colors.red),
Expanded(
child: Container(height: 50, color: Colors.green),
),
Container(width: 50, height: 50, color: Colors.blue),
],
),
const SizedBox(height: 16),
// Flexible — 灵活分配
const Text('Flexible按比例分配:'),
Row(
children: [
Flexible(flex: 1, child: Container(height: 50, color: Colors.red)),
Flexible(flex: 2, child: Container(height: 50, color: Colors.green)),
Flexible(flex: 1, child: Container(height: 50, color: Colors.blue)),
],
),
],
);
}
}
// ✅ 验证通过
/// Column与Row完全对称,只是方向不同
/// 主轴=垂直,交叉轴=水平
class ColumnDemos extends StatelessWidget {
const ColumnDemos({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 基本Column
const Text('基本垂直排列:'),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(width: 150, height: 40, color: Colors.red),
Container(width: 100, height: 40, color: Colors.green),
Container(width: 50, height: 40, color: Colors.blue),
],
),
// 交叉轴对齐(水平)
const Text('交叉轴stretch:'),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
Container(height: 40, color: Colors.red),
Container(height: 40, color: Colors.green),
Container(height: 40, color: Colors.blue),
],
),
// 嵌套Row+Column
const Text('嵌套布局:'),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: const [
Text('左侧'),
Text('右侧'),
],
),
Row(
children: [
Expanded(child: ElevatedButton(onPressed: () {}, child: const Text('取消'))),
const SizedBox(width: 16),
Expanded(child: FilledButton(onPressed: () {}, child: const Text('确认'))),
],
),
],
),
],
);
}
}
/// ⚠️ 常见错误:Column内容溢出
/// 当Column的子组件总高度超过可用空间时,会报overflow错误
// ❌ 错误示例
Column(
children: [
Container(height: 300), // 太多子组件...
Container(height: 300),
Container(height: 300), // 溢出!
],
)
// ✅ 解决方案1:用Expanded包裹可变高度的子组件
Column(
children: [
Container(height: 100), // 固定高度
Expanded( // 占据剩余空间
child: ListView(...), // 内部可滚动
),
],
)
// ✅ 解决方案2:用SingleChildScrollView包裹整个Column
SingleChildScrollView(
child: Column(
children: [
Container(height: 300),
Container(height: 300),
Container(height: 300), // 不会溢出
],
),
)
// ✅ 验证通过
/// Stack允许子组件重叠,类似CSS的position:absolute
Stack(
alignment, // 对齐方式
textDirection, // 文本方向
fit, // 非Positioned子组件的适配方式
clipBehavior, // 裁剪行为
children, // 子组件(后面的覆盖前面的)
)
class StackDemos extends StatelessWidget {
const StackDemos({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
// 基本Stack
const Text('基本层叠:'),
SizedBox(
width: 200,
height: 200,
child: Stack(
children: [
Container(
width: 200,
height: 200,
color: Colors.blue.withOpacity(0.3),
),
Container(
width: 120,
height: 120,
color: Colors.green.withOpacity(0.5),
),
Container(
width: 60,
height: 60,
color: Colors.red.withOpacity(0.7),
),
],
),
),
const SizedBox(height: 16),
// Positioned — 精确定位
const Text('Positioned定位:'),
SizedBox(
width: 200,
height: 200,
child: Stack(
children: [
Container(color: Colors.grey[300]),
// 左上角
const Positioned(
top: 10,
left: 10,
child: Icon(Icons.star, color: Colors.amber),
),
// 右下角
const Positioned(
bottom: 10,
right: 10,
child: Icon(Icons.favorite, color: Colors.red),
),
// 居中
const Positioned(
top: 0,
left: 0,
right: 0,
bottom: 0,
child: Center(child: Text('居中文字')),
),
],
),
),
// 实际应用:图片上的徽章
const Text('图片徽章:'),
SizedBox(
width: 150,
height: 150,
child: Stack(
clipBehavior: Clip.none, // 允许溢出
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
color: Colors.purple[100],
),
),
const Center(
child: Icon(Icons.person, size: 60, color: Colors.purple),
),
Positioned(
top: -5,
right: -5,
child: Container(
padding: const EdgeInsets.all(6),
decoration: const BoxDecoration(
color: Colors.red,
shape: BoxShape.circle,
),
child: const Text('3', style: TextStyle(color: Colors.white, fontSize: 12)),
),
),
],
),
),
],
);
}
}
// ✅ 验证通过
/// SizedBox — 固定大小间距
const SizedBox(width: 16) // 水平间距
const SizedBox(height: 8) // 垂直间距
const SizedBox(width: 16, height: 16) // 同时指定
/// Spacer — 在Row/Column中占据弹性空间
Row(
children: [
const Text('左'),
const Spacer(), // 推开中间空间
const Text('右'),
],
)
// 等同于:
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [const Text('左'), const Text('右')],
)
/// Divider — 水平分隔线
const Divider(height: 1)
/// VerticalDivider — 垂直分隔线
const VerticalDivider(width: 1)
/// Padding — 内边距
Padding(
padding: const EdgeInsets.all(16),
child: child,
)
// EdgeInsets变体
EdgeInsets.all(16) // 四边相同
EdgeInsets.symmetric(h: 16, v: 8) // 水平/垂直
EdgeInsets.only(left: 16, top: 8) // 指定边
EdgeInsets.fromLTRB(16, 8, 16, 8) // 左上右下
// ✅ 验证通过:Flutter 3.22 + Dart 3.4
// 文件:lib/dashboard_layout.dart
import 'package:flutter/material.dart';
void main() => runApp(const DashboardApp());
class DashboardApp extends StatelessWidget {
const DashboardApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '仪表盘',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const DashboardPage(),
);
}
}
// 统计卡片
class StatCard extends StatelessWidget {
final String title;
final String value;
final IconData icon;
final Color color;
final String change;
const StatCard({
required this.title,
required this.value,
required this.icon,
required this.color,
this.change = '',
super.key,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(title, style: Theme.of(context).textTheme.bodySmall),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: color.withOpacity(0.15),
borderRadius: BorderRadius.circular(8),
),
child: Icon(icon, color: color, size: 20),
),
],
),
const SizedBox(height: 12),
Text(value, style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
)),
if (change.isNotEmpty) ...[
const SizedBox(height: 4),
Text(
change,
style: TextStyle(
color: change.startsWith('+') ? Colors.green : Colors.red,
fontSize: 12,
),
),
],
],
),
),
);
}
}
// 活动项
class ActivityItem extends StatelessWidget {
final String title;
final String subtitle;
final IconData icon;
final Color color;
final String time;
const ActivityItem({
required this.title,
required this.subtitle,
required this.icon,
required this.color,
required this.time,
super.key,
});
@override
Widget build(BuildContext context) {
return ListTile(
leading: CircleAvatar(
backgroundColor: color.withOpacity(0.15),
child: Icon(icon, color: color, size: 20),
),
title: Text(title),
subtitle: Text(subtitle, style: TextStyle(color: Colors.grey[600], fontSize: 12)),
trailing: Text(time, style: TextStyle(color: Colors.grey[500], fontSize: 12)),
);
}
}
// 主页面
class DashboardPage extends StatelessWidget {
const DashboardPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('仪表盘'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
actions: [
IconButton(onPressed: () {}, icon: const Icon(Icons.notifications_outlined)),
const SizedBox(width: 8),
const CircleAvatar(radius: 16, child: Icon(Icons.person, size: 18)),
const SizedBox(width: 16),
],
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 欢迎信息
Text('欢迎回来 👋', style: Theme.of(context).textTheme.headlineSmall),
const SizedBox(height: 4),
Text('这是你的项目概览', style: TextStyle(color: Colors.grey[600])),
const SizedBox(height: 24),
// 统计卡片 — 2x2网格
LayoutBuilder(
builder: (context, constraints) {
// 根据宽度决定列数
var columns = constraints.maxWidth > 600 ? 4 : 2;
return Wrap(
spacing: 12,
runSpacing: 12,
children: [
SizedBox(
width: (constraints.maxWidth - 12 * (columns - 1)) / columns,
child: const StatCard(
title: '总收入',
value: '¥128,430',
icon: Icons.trending_up,
color: Colors.green,
change: '+12.5%',
),
),
SizedBox(
width: (constraints.maxWidth - 12 * (columns - 1)) / columns,
child: const StatCard(
title: '用户数',
value: '3,842',
icon: Icons.people,
color: Colors.blue,
change: '+8.2%',
),
),
SizedBox(
width: (constraints.maxWidth - 12 * (columns - 1)) / columns,
child: const StatCard(
title: '订单量',
value: '1,256',
icon: Icons.shopping_cart,
color: Colors.orange,
change: '-3.1%',
),
),
SizedBox(
width: (constraints.maxWidth - 12 * (columns - 1)) / columns,
child: const StatCard(
title: '转化率',
value: '32.7%',
icon: Icons.speed,
color: Colors.purple,
change: '+2.4%',
),
),
],
);
},
),
const SizedBox(height: 24),
// 最近活动
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('最近活动', style: Theme.of(context).textTheme.titleMedium),
TextButton(onPressed: () {}, child: const Text('查看全部')),
],
),
Card(
child: Column(
children: const [
ActivityItem(
title: '新用户注册',
subtitle: 'Alice 加入了平台',
icon: Icons.person_add,
color: Colors.green,
time: '2分钟前',
),
Divider(height: 1),
ActivityItem(
title: '新订单',
subtitle: '订单 #1024 已创建',
icon: Icons.shopping_bag,
color: Colors.blue,
time: '15分钟前',
),
Divider(height: 1),
ActivityItem(
title: '支付完成',
subtitle: '¥2,980 已收到',
icon: Icons.payment,
color: Colors.green,
time: '1小时前',
),
Divider(height: 1),
ActivityItem(
title: '系统告警',
subtitle: 'CPU使用率超过90%',
icon: Icons.warning,
color: Colors.orange,
time: '2小时前',
),
],
),
),
const SizedBox(height: 24),
// 快捷操作
Text('快捷操作', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: FilledButton.icon(
onPressed: () {},
icon: const Icon(Icons.add),
label: const Text('新建项目'),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.upload),
label: const Text('导入数据'),
),
),
],
),
],
),
),
);
}
}
使用LayoutBuilder + Row实现响应式网格:
实现聊天气泡布局:
使用Stack实现扑克牌叠放效果:5张牌,每张偏移20px。
下一课:容器与装饰——Container、BoxDecoration、Card等视觉装饰组件。