Container是Flutter中最常用的容器组件,它是一个组合组件——内部组合了Padding、DecoratedBox、ConstrainedBox、Align等。
// ✅ 验证通过:Flutter 3.22
/// Container组合结构
Container(
padding, → Padding
decoration, → DecoratedBox
foregroundDecoration,
constraints, → ConstrainedBox
margin, → Padding(外边距)
transform, → Transform
alignment, → Align
width, height, → ConstrainedBox/SizedBox
color, → ColoredBox (deprecated in favor of decoration)
child,
)
// 基本用法
Container(
width: 200,
height: 100,
padding: const EdgeInsets.all(16),
margin: const EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: const Offset(0, 4),
),
],
),
child: const Center(child: Text('卡片内容')),
)
// Container的行为规则:
// 1. 没有child时 → 尽可能大(受constraints限制)
// 2. 有child时 → 跟随child大小(除非指定width/height)
// 3. padding是装饰内部的空间
// 4. margin是容器外部的空间
// ⚠️ 常见陷阱:同时设置color和decoration
// ❌ 编译错误
Container(
color: Colors.red,
decoration: BoxDecoration(color: Colors.blue), // 冲突!
)
// ✅ 只用decoration
Container(
decoration: BoxDecoration(color: Colors.blue),
)
// ✅ 只用color(简单场景)
Container(color: Colors.red)
// ✅ 验证通过
/// BoxDecoration——强大的装饰引擎
// 1. 背景色与渐变
BoxDecoration(
// 纯色
color: Colors.blue,
// 线性渐变
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [Colors.blue, Colors.purple],
stops: [0.0, 1.0], // 可选:颜色停止点
),
// 径向渐变
gradient: RadialGradient(
center: Alignment.center,
radius: 0.8,
colors: [Colors.yellow, Colors.orange, Colors.red],
),
// 扫描渐变(圆锥渐变)
gradient: SweepGradient(
center: Alignment.center,
colors: [Colors.red, Colors.yellow, Colors.green, Colors.blue, Colors.red],
),
)
// 2. 边框与圆角
BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16), // 统一圆角
// 或分别指定
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(20),
bottomRight: Radius.circular(20),
),
border: Border.all(
color: Colors.grey,
width: 2,
),
// 分别指定边
border: const Border(
top: BorderSide(color: Colors.red, width: 3),
bottom: BorderSide(color: Colors.blue, width: 1),
),
)
// 3. 阴影
BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 10, // 模糊半径
spreadRadius: 0, // 扩散半径
offset: const Offset(0, 4), // 偏移
blurStyle: BlurStyle.normal,
),
// 可以叠加多层阴影
BoxShadow(
color: Colors.blue.withOpacity(0.1),
blurRadius: 20,
spreadRadius: -5,
offset: const Offset(0, 8),
),
],
)
// 4. 背景图片
BoxDecoration(
image: DecorationImage(
image: NetworkImage('https://example.com/bg.jpg'),
fit: BoxFit.cover, // 填充模式
alignment: Alignment.center, // 对齐
colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.3),
BlendMode.darken, // 暗化滤镜
),
repeat: ImageRepeat.noRepeat,
),
)
// 5. 形状
BoxDecoration(
color: Colors.blue,
shape: BoxShape.circle, // 圆形(不能与borderRadius同时使用)
)
// 6. 混合模式
BoxDecoration(
color: Colors.blue,
backgroundBlendMode: BlendMode.multiply,
gradient: LinearGradient(colors: [Colors.red, Colors.yellow]),
)
// ✅ 验证通过
/// SizedBox — 固定尺寸容器
const SizedBox(width: 100, height: 50, child: Text('固定大小'))
const SizedBox.shrink() // 0x0的盒子(占位但不显示)
/// ConstrainedBox — 约束容器
ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 100,
maxWidth: 300,
minHeight: 50,
maxHeight: 200,
),
child: Text('受约束的内容'),
)
/// UnconstrainedBox — 不继承父约束
UnconstrainedBox(
alignment: Alignment.centerLeft,
child: Text('不受父约束限制'),
)
/// LimitedBox — 无界时的限制
LimitedBox(
maxHeight: 200,
maxWidth: 200,
child: ListView(...), // 在无界空间中限制大小
)
/// FittedBox — 缩放适配
FittedBox(
fit: BoxFit.contain, // contain/cover/fill/none
alignment: Alignment.center,
child: Text('可能被缩放的内容'),
)
/// AspectRatio — 宽高比容器
AspectRatio(
aspectRatio: 16 / 9, // 16:9宽高比
child: Image.asset('video_thumbnail.jpg'),
)
/// FractionallySizedBox — 相对父容器百分比
FractionallySizedBox(
widthFactor: 0.8, // 父宽度的80%
heightFactor: 0.5, // 父高度的50%
child: Container(color: Colors.blue),
)
/// IntrinsicWidth/Height — 根据子组件自适应
IntrinsicWidth(
child: Column(
children: [
ElevatedButton(onPressed: () {}, child: const Text('短')),
ElevatedButton(onPressed: () {}, child: const Text('比较长的按钮')),
// 所有按钮宽度一致(取最宽的那个)
],
),
)
/// Transform — 变换
Transform.rotate(
angle: 0.1, // 弧度
child: const Text('旋转'),
)
Transform.scale(
scale: 1.5,
child: const Text('放大'),
)
Transform.translate(
offset: const Offset(20, 10),
child: const Text('平移'),
)
/// Clip — 裁剪
ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.asset('photo.jpg'),
)
ClipOval( // 圆形裁剪
child: Image.asset('avatar.jpg'),
)
ClipPath( // 自定义路径裁剪
clipper: MyCustomClipper(),
child: Container(color: Colors.blue),
)
/// Material — Material Design层
Material(
elevation: 4,
borderRadius: BorderRadius.circular(12),
color: Colors.white,
child: InkWell(
onTap: () {},
borderRadius: BorderRadius.circular(12),
child: const Padding(
padding: EdgeInsets.all(16),
child: Text('带涟漪效果的Material'),
),
),
)
// ✅ 验证通过:Flutter 3.22 + Dart 3.4
// 文件:lib/ui_components.dart
import 'package:flutter/material.dart';
void main() => runApp(const ComponentLibApp());
class ComponentLibApp extends StatelessWidget {
const ComponentLibApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'UI组件库',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const ComponentShowcase(),
);
}
}
/// AppCard — 统一卡片样式
class AppCard extends StatelessWidget {
final Widget child;
final EdgeInsetsGeometry? padding;
final EdgeInsetsGeometry? margin;
final Color? color;
final double borderRadius;
final double elevation;
final VoidCallback? onTap;
const AppCard({
required this.child,
this.padding = const EdgeInsets.all(16),
this.margin,
this.color,
this.borderRadius = 16,
this.elevation = 2,
this.onTap,
super.key,
});
@override
Widget build(BuildContext context) {
var card = Container(
margin: margin ?? const EdgeInsets.only(bottom: 12),
decoration: BoxDecoration(
color: color ?? Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(borderRadius),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.06),
blurRadius: elevation * 4,
offset: Offset(0, elevation),
),
],
),
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(borderRadius),
child: InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(borderRadius),
child: Padding(
padding: padding!,
child: child,
),
),
),
);
return card;
}
}
/// GradientCard — 渐变卡片
class GradientCard extends StatelessWidget {
final Widget child;
final List<Color> colors;
final Alignment begin;
final Alignment end;
final double borderRadius;
final EdgeInsetsGeometry padding;
const GradientCard({
required this.child,
required this.colors,
this.begin = Alignment.topLeft,
this.end = Alignment.bottomRight,
this.borderRadius = 16,
this.padding = const EdgeInsets.all(20),
super.key,
});
@override
Widget build(BuildContext context) {
return Container(
padding: padding,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: begin,
end: end,
colors: colors,
),
borderRadius: BorderRadius.circular(borderRadius),
boxShadow: [
BoxShadow(
color: colors.last.withOpacity(0.3),
blurRadius: 12,
offset: const Offset(0, 6),
),
],
),
child: child,
);
}
}
/// Avatar — 头像组件
class AppAvatar extends StatelessWidget {
final String? imageUrl;
final String? initials;
final double radius;
final Color? backgroundColor;
final Color? textColor;
final IconData? icon;
const AppAvatar({
this.imageUrl,
this.initials,
this.radius = 24,
this.backgroundColor,
this.textColor,
this.icon,
super.key,
});
@override
Widget build(BuildContext context) {
var bgColor = backgroundColor ?? Theme.of(context).colorScheme.primaryContainer;
var fgColor = textColor ?? Theme.of(context).colorScheme.onPrimaryContainer;
Widget child;
if (imageUrl != null) {
child = ClipOval(
child: Image.network(
imageUrl!,
width: radius * 2,
height: radius * 2,
fit: BoxFit.cover,
errorBuilder: (_, __, ___) => _buildFallback(bgColor, fgColor),
),
);
} else {
child = _buildFallback(bgColor, fgColor);
}
return CircleAvatar(
radius: radius,
backgroundColor: bgColor,
child: child,
);
}
Widget _buildFallback(Color bg, Color fg) {
if (initials != null) {
return Text(
initials!.toUpperCase(),
style: TextStyle(
color: fg,
fontSize: radius * 0.8,
fontWeight: FontWeight.bold,
),
);
}
return Icon(icon ?? Icons.person, color: fg, size: radius);
}
}
/// Tag — 标签组件
class AppTag extends StatelessWidget {
final String label;
final Color? color;
final IconData? icon;
final VoidCallback? onTap;
final bool outlined;
const AppTag({
required this.label,
this.color,
this.icon,
this.onTap,
this.outlined = false,
super.key,
});
@override
Widget build(BuildContext context) {
var tagColor = color ?? Theme.of(context).colorScheme.primary;
return GestureDetector(
onTap: onTap,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
decoration: BoxDecoration(
color: outlined ? null : tagColor.withOpacity(0.15),
borderRadius: BorderRadius.circular(20),
border: outlined ? Border.all(color: tagColor, width: 1.5) : null,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
Icon(icon, size: 14, color: tagColor),
const SizedBox(width: 4),
],
Text(
label,
style: TextStyle(
color: tagColor,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
],
),
),
);
}
}
/// ProgressBar — 进度条
class AppProgressBar extends StatelessWidget {
final double progress; // 0.0 - 1.0
final Color? color;
final double height;
final double borderRadius;
const AppProgressBar({
required this.progress,
this.color,
this.height = 8,
this.borderRadius = 4,
super.key,
});
@override
Widget build(BuildContext context) {
var barColor = color ?? Theme.of(context).colorScheme.primary;
return ClipRRect(
borderRadius: BorderRadius.circular(borderRadius),
child: LinearProgressIndicator(
value: progress.clamp(0.0, 1.0),
minHeight: height,
backgroundColor: barColor.withOpacity(0.15),
valueColor: AlwaysStoppedAnimation(barColor),
),
);
}
}
/// Showcase页面
class ComponentShowcase extends StatelessWidget {
const ComponentShowcase({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('UI组件展示'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('卡片组件', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
const AppCard(
child: Text('基础卡片 — 带阴影和圆角'),
),
GradientCard(
colors: [Colors.purple, Colors.blue],
child: const Text('渐变卡片', style: TextStyle(color: Colors.white, fontSize: 18)),
),
const SizedBox(height: 24),
Text('头像组件', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
const Row(
children: [
AppAvatar(initials: 'ZS', radius: 24),
SizedBox(width: 12),
AppAvatar(initials: 'FL', radius: 30, backgroundColor: Colors.blue),
SizedBox(width: 12),
AppAvatar(icon: Icons.add, radius: 20),
SizedBox(width: 12),
AppAvatar(initials: 'AB', radius: 36, backgroundColor: Colors.orange),
],
),
const SizedBox(height: 24),
Text('标签组件', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
Wrap(
spacing: 8,
runSpacing: 8,
children: const [
AppTag(label: 'Flutter', color: Colors.blue),
AppTag(label: 'Dart', color: Colors.cyan),
AppTag(label: '重要', color: Colors.red, icon: Icons.priority_high),
AppTag(label: '新版', color: Colors.green, outlined: true),
AppTag(label: 'Beta', color: Colors.orange, outlined: true),
],
),
const SizedBox(height: 24),
Text('进度条', style: Theme.of(context).textTheme.titleLarge),
const SizedBox(height: 12),
const AppProgressBar(progress: 0.3, color: Colors.blue),
const SizedBox(height: 8),
const AppProgressBar(progress: 0.7, color: Colors.green),
const SizedBox(height: 8),
const AppProgressBar(progress: 0.95, color: Colors.orange),
],
),
),
);
}
}
实现毛玻璃效果卡片:
// 提示:BackdropFilter + ImageFilter.blur
ClipRRect(
borderRadius: BorderRadius.circular(20),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
child: Container(
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.2),
border: Border.all(color: Colors.white.withOpacity(0.3)),
),
),
),
)
实现新拟态风格按钮:内外双阴影创造凸起/凹陷效果。
使用CustomClipper实现波浪形底部裁剪:
class WaveClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
// 实现波浪路径
}
@override
bool shouldReclip(covariant WaveClipper old) => false;
}
下一课:滚动列表——ListView、GridView、CustomScrollView和Sliver。