Flutter 第三阶段:基础 Widget 全面指南

一、Widget 核心概念

1.1 Widget 是什么

在 Flutter 中,一切皆 Widget。Widget 是 Flutter UI 的最小构建单元,描述了界面的某一部分"应该长什么样"。

需要特别注意的是:Widget 本身是不可变的配置描述,而非真实渲染的 UI 元素。每当状态发生变化,Flutter 不会修改原有 Widget,而是重新创建新的 Widget 树,由框架负责高效地做差异化更新。

dart 复制代码
// Widget 是 UI 配置的声明
Text('Hello Flutter')
// 等价于一个描述"显示文字 Hello Flutter"的数据结构

这种设计带来了几个优势:

  • 不可变性保证了线程安全
  • 重建代价极低(Widget 是轻量级对象)
  • 声明式 UI 让代码更加可预测

1.2 StatelessWidget vs StatefulWidget

StatelessWidget --- 无状态组件

适合纯展示型 UI,内部没有可变数据。一旦构建完成,显示内容不会改变(除非父级传入新的参数)。

dart 复制代码
class MyLabel extends StatelessWidget {
  final String text;
  const MyLabel({super.key, required this.text});

  @override
  Widget build(BuildContext context) {
    return Text(
      text,
      style: const TextStyle(fontSize: 16),
    );
  }
}

生命周期 :父节点 build → build() → 渲染。没有额外生命周期回调。

StatefulWidget --- 有状态组件

当 Widget 需要持有随时间变化的数据时,使用 StatefulWidget。它由两个类共同组成:

dart 复制代码
// Widget 类:仍然是不可变的
class Counter extends StatefulWidget {
  const Counter({super.key});

  @override
  State<Counter> createState() => _CounterState();
}

// State 类:持有可变状态
class _CounterState extends State<Counter> {
  int _count = 0;

  void _increment() {
    setState(() {       // 告知框架状态已变化,触发重建
      _count++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $_count'),
        ElevatedButton(
          onPressed: _increment,
          child: const Text('加一'),
        ),
      ],
    );
  }
}

State 生命周期(重要!):

复制代码
initState()          ← 初始化,仅调用一次
     ↓
didChangeDependencies()   ← 依赖(如 InheritedWidget)变化时调用
     ↓
build()              ← 每次 setState / 父重建都会调用
     ↓
didUpdateWidget()    ← 父传入新 widget 实例时调用
     ↓
deactivate()         ← 从树中移除时
     ↓
dispose()            ← 彻底销毁,释放资源(Controller、Timer 等)

选择原则

场景 选择
纯展示、无交互 StatelessWidget
有用户输入、动画、网络数据 StatefulWidget
多组件共享状态 Provider / Riverpod / Bloc

1.3 Widget 树 / Element 树 / RenderObject 树

Flutter 运行时维护三棵并行的树,理解它们是深入 Flutter 的关键。

复制代码
Widget 树(配置层)
    ↕ 映射
Element 树(生命周期管理层)
    ↕ 映射
RenderObject 树(布局与绘制层)
Widget 树

开发者直接书写的部分。轻量、可频繁重建。描述"想要什么"。

Element 树

Flutter 框架自动维护。每个 Widget 对应一个 Element,Element 持有对 Widget 和 RenderObject 的引用。负责管理生命周期,并做新旧 Widget 的 diff(通过 key 和 runtimeType 判断是否复用)。

RenderObject 树

真正执行布局(layout)和绘制(paint)的对象。代价较高,Flutter 会尽量复用。

复制代码
Column Widget → ColumnElement → RenderFlex
  Text Widget →   TextElement → RenderParagraph
  Icon Widget →   IconElement → RenderBox

实际意义 :当你调用 setState(),Flutter 重建 Widget 树,然后通过 Element 树进行 diff,只对真正变化的部分更新 RenderObject。这就是 Flutter 高性能的核心所在。

1.4 BuildContext 理解

BuildContext 是 Element 在 Widget 树中位置的句柄。每个 build(BuildContext context) 中的 context 代表当前 Widget 在树中的位置。

dart 复制代码
@override
Widget build(BuildContext context) {
  // 通过 context 向上查找最近的 Theme
  final theme = Theme.of(context);

  // 通过 context 获取屏幕尺寸
  final size = MediaQuery.of(context).size;

  // 通过 context 进行路由跳转
  Navigator.of(context).push(...);

  return Container(color: theme.primaryColor);
}

常见误区 :不要在 initState() 里直接使用 context(此时 Widget 还未挂载完成),应使用 didChangeDependencies() 或者用 WidgetsBinding.instance.addPostFrameCallback 延迟执行。

二、基础 UI 组件

2.1 文本:Text / RichText

Text

最常用的文本组件,支持单一样式。

dart 复制代码
Text(
  '欢迎使用 Flutter',
  style: TextStyle(
    fontSize: 24,
    fontWeight: FontWeight.bold,
    color: Colors.deepPurple,
    letterSpacing: 1.2,
    height: 1.5,           // 行高倍数
  ),
  textAlign: TextAlign.center,
  maxLines: 2,
  overflow: TextOverflow.ellipsis,  // 超出截断并显示省略号
)
RichText

当同一段文字需要多种样式时,使用 RichText + TextSpan

dart 复制代码
RichText(
  text: TextSpan(
    style: const TextStyle(fontSize: 16, color: Colors.black),
    children: [
      const TextSpan(text: '价格:'),
      TextSpan(
        text: '¥99.00',
        style: const TextStyle(
          color: Colors.red,
          fontWeight: FontWeight.bold,
          fontSize: 20,
        ),
      ),
      const TextSpan(text: ' 起'),
    ],
  ),
)

实用技巧Text.rich()RichText 的便捷构造器,自动继承 DefaultTextStyle,推荐在一般场景使用。

2.2 图片:Image / AssetImage / NetworkImage

dart 复制代码
// 加载网络图片
Image.network(
  'https://example.com/photo.jpg',
  width: 200,
  height: 150,
  fit: BoxFit.cover,          // 裁剪填满
  loadingBuilder: (context, child, progress) {
    if (progress == null) return child;
    return const CircularProgressIndicator();
  },
  errorBuilder: (context, error, stackTrace) {
    return const Icon(Icons.broken_image);
  },
)

// 加载本地资源图片(需在 pubspec.yaml 中声明)
Image.asset(
  'assets/images/logo.png',
  width: 100,
)

// 圆形头像(配合 ClipOval)
ClipOval(
  child: Image.network(
    'https://example.com/avatar.jpg',
    width: 60,
    height: 60,
    fit: BoxFit.cover,
  ),
)

BoxFit 速查

效果
cover 等比缩放,铺满容器,可能裁剪
contain 等比缩放,完整显示,可能留白
fill 拉伸填满,不保持比例
fitWidth 宽度填满,高度等比
fitHeight 高度填满,宽度等比
none 原始尺寸,超出裁剪

2.3 按钮组件

ElevatedButton --- 凸起按钮(主操作)
dart 复制代码
ElevatedButton(
  onPressed: () => print('点击了'),
  style: ElevatedButton.styleFrom(
    backgroundColor: Colors.deepPurple,
    foregroundColor: Colors.white,
    padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
    shape: RoundedRectangleBorder(
      borderRadius: BorderRadius.circular(8),
    ),
  ),
  child: const Text('立即购买'),
)
TextButton --- 文字按钮(次级操作)
dart 复制代码
TextButton(
  onPressed: () {},
  child: const Text('查看详情'),
)
IconButton --- 图标按钮
dart 复制代码
IconButton(
  icon: const Icon(Icons.favorite_border),
  onPressed: () {},
  tooltip: '收藏',
  iconSize: 28,
  color: Colors.pink,
)
GestureDetector --- 手势识别器(万能)

当系统按钮满足不了需求时,用 GestureDetector 给任意 Widget 添加手势。

dart 复制代码
GestureDetector(
  onTap: () => print('单击'),
  onDoubleTap: () => print('双击'),
  onLongPress: () => print('长按'),
  onPanUpdate: (details) {
    // 拖动,details.delta 是偏移量
  },
  child: Container(
    width: 100,
    height: 100,
    color: Colors.blue,
    child: const Center(child: Text('点我')),
  ),
)

InkWell vs GestureDetectorInkWell 带有 Material 水波纹效果,适合 Material 风格 UI;GestureDetector 无任何视觉反馈,适合自定义 UI。

2.4 输入框:TextField / TextFormField

TextField --- 基础输入框
dart 复制代码
final _controller = TextEditingController();

TextField(
  controller: _controller,
  decoration: InputDecoration(
    labelText: '用户名',
    hintText: '请输入用户名',
    prefixIcon: const Icon(Icons.person),
    suffixIcon: IconButton(
      icon: const Icon(Icons.clear),
      onPressed: () => _controller.clear(),
    ),
    border: OutlineInputBorder(
      borderRadius: BorderRadius.circular(8),
    ),
  ),
  keyboardType: TextInputType.emailAddress,
  obscureText: false,        // 密码输入时设为 true
  maxLength: 20,
  onChanged: (value) => print(value),
  onSubmitted: (value) => print('提交:$value'),
)
TextFormField --- 表单输入框(带验证)
dart 复制代码
final _formKey = GlobalKey<FormState>();

Form(
  key: _formKey,
  child: Column(
    children: [
      TextFormField(
        decoration: const InputDecoration(labelText: '邮箱'),
        validator: (value) {
          if (value == null || value.isEmpty) return '邮箱不能为空';
          if (!value.contains('@')) return '请输入有效邮箱';
          return null;  // null 表示验证通过
        },
      ),
      ElevatedButton(
        onPressed: () {
          if (_formKey.currentState!.validate()) {
            // 验证通过,提交表单
          }
        },
        child: const Text('提交'),
      ),
    ],
  ),
)

FocusNode 管理焦点

dart 复制代码
final _focusNode = FocusNode();

// 主动获取焦点
_focusNode.requestFocus();

// 失去焦点(收起键盘)
_focusNode.unfocus();

// 记得在 dispose 中释放
@override
void dispose() {
  _focusNode.dispose();
  _controller.dispose();
  super.dispose();
}

2.5 图标:Icon

dart 复制代码
// 使用 Material Icons
Icon(
  Icons.star,
  size: 32,
  color: Colors.amber,
)

// 带语义的图标(无障碍)
Icon(
  Icons.favorite,
  semanticLabel: '收藏',
)

// 自定义图标字体
Icon(
  IconData(0xe001, fontFamily: 'MyIcons'),
)

三、布局 Widget

布局是 Flutter 开发最核心的技能之一。Flutter 的布局协议是父节点向子节点传递约束(constraints),子节点在约束范围内决定自身尺寸。

3.1 线性布局:Row / Column

dart 复制代码
// Row:水平排列
Row(
  mainAxisAlignment: MainAxisAlignment.spaceBetween,  // 主轴对齐(水平)
  crossAxisAlignment: CrossAxisAlignment.center,       // 交叉轴对齐(垂直)
  children: [
    const Icon(Icons.star),
    const Text('评分'),
    const Text('4.8'),
  ],
)

// Column:垂直排列
Column(
  mainAxisSize: MainAxisSize.min,    // 收紧高度,不占满父容器
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    const Text('标题', style: TextStyle(fontSize: 18)),
    const SizedBox(height: 8),
    const Text('副标题'),
  ],
)

对齐方式速查

MainAxisAlignment 效果
start 靠起始端
end 靠结束端
center 居中
spaceBetween 两端对齐,间隔均匀
spaceAround 每个元素两侧间隔相等
spaceEvenly 所有间隔相等(含两端)

3.2 层叠布局:Stack / Positioned

Stack 允许子 Widget 堆叠在一起,后面的子 Widget 会覆盖前面的。

dart 复制代码
Stack(
  alignment: Alignment.center,  // 未定位子 Widget 的默认对齐
  children: [
    // 背景图
    Image.network('https://example.com/bg.jpg', fit: BoxFit.cover),

    // 半透明遮罩
    Container(color: Colors.black.withOpacity(0.3)),

    // 定位的文字
    Positioned(
      bottom: 16,
      left: 16,
      right: 16,
      child: const Text(
        '封面标题',
        style: TextStyle(color: Colors.white, fontSize: 24),
      ),
    ),

    // 右上角角标
    Positioned(
      top: 8,
      right: 8,
      child: Container(
        padding: const EdgeInsets.all(4),
        decoration: BoxDecoration(
          color: Colors.red,
          borderRadius: BorderRadius.circular(4),
        ),
        child: const Text('HOT', style: TextStyle(color: Colors.white, fontSize: 10)),
      ),
    ),
  ],
)

3.3 弹性布局:Expanded / Flexible

Row / Column 中,用 ExpandedFlexible 按比例分配剩余空间。

dart 复制代码
Row(
  children: [
    // 固定宽度
    const Icon(Icons.label, size: 20),
    const SizedBox(width: 8),

    // 占据剩余所有空间
    Expanded(
      child: Text('这是一段可能很长的标题文字,会自动换行'),
    ),

    // 按 flex 比例分配
    Flexible(
      flex: 1,
      child: Container(color: Colors.blue, height: 40),
    ),
    Flexible(
      flex: 2,      // 占剩余空间的 2/3
      child: Container(color: Colors.green, height: 40),
    ),
  ],
)

Expanded vs Flexible

  • Expanded = Flexible(fit: FlexFit.tight),强制填满分配的空间
  • Flexible 默认 fit: FlexFit.loose,子 Widget 可以比分配空间小

3.4 容器类 Widget

Container --- 万能容器
dart 复制代码
Container(
  width: 200,
  height: 100,
  margin: const EdgeInsets.all(16),             // 外边距
  padding: const EdgeInsets.symmetric(          // 内边距
    horizontal: 16, vertical: 8,
  ),
  alignment: Alignment.center,                  // 子 Widget 对齐方式
  decoration: BoxDecoration(
    color: Colors.white,
    borderRadius: BorderRadius.circular(12),
    boxShadow: [
      BoxShadow(
        color: Colors.black.withOpacity(0.1),
        blurRadius: 8,
        offset: const Offset(0, 2),
      ),
    ],
  ),
  child: const Text('卡片内容'),
)

注意Container 同时设置 colordecoration 会报错,两者选其一。颜色设置在 decoration 里。

SizedBox --- 尺寸盒子
dart 复制代码
// 固定尺寸
SizedBox(width: 100, height: 50, child: myWidget)

// 常用于间距
const SizedBox(height: 16)   // 垂直间距
const SizedBox(width: 8)     // 水平间距

// 强制填满父容器
SizedBox.expand(child: myWidget)
Padding / Center / Align
dart 复制代码
// 单纯添加内边距(比 Container 轻量)
Padding(
  padding: const EdgeInsets.all(16),
  child: myWidget,
)

// 居中
Center(child: myWidget)

// 自定义对齐
Align(
  alignment: Alignment.bottomRight,
  child: myWidget,
)

3.5 滚动组件

SingleChildScrollView --- 单子滚动

适合内容偶尔超出屏幕的页面,如表单页。

dart 复制代码
SingleChildScrollView(
  padding: const EdgeInsets.all(16),
  child: Column(
    children: [
      // 表单字段...
    ],
  ),
)
ListView --- 列表
dart 复制代码
// 方式一:直接传 children(适合少量固定条目)
ListView(
  children: [
    ListTile(title: const Text('Item 1')),
    ListTile(title: const Text('Item 2')),
  ],
)

// 方式二:builder(适合大量数据,懒加载)
ListView.builder(
  itemCount: 100,
  itemExtent: 60,         // 固定行高,性能更好
  itemBuilder: (context, index) {
    return ListTile(
      leading: CircleAvatar(child: Text('$index')),
      title: Text('Item $index'),
      subtitle: const Text('副标题'),
      trailing: const Icon(Icons.chevron_right),
      onTap: () {},
    );
  },
)

// 方式三:separated(自带分割线)
ListView.separated(
  itemCount: items.length,
  separatorBuilder: (context, index) => const Divider(height: 1),
  itemBuilder: (context, index) => ListTile(title: Text(items[index])),
)
GridView --- 网格
dart 复制代码
// 固定列数
GridView.count(
  crossAxisCount: 2,         // 列数
  mainAxisSpacing: 12,       // 纵向间距
  crossAxisSpacing: 12,      // 横向间距
  childAspectRatio: 1.2,     // 宽高比
  padding: const EdgeInsets.all(16),
  children: List.generate(20, (i) => Card(child: Center(child: Text('$i')))),
)

// 自适应列数(响应式)
GridView.builder(
  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 160,   // 每列最大宽度
    mainAxisSpacing: 12,
    crossAxisSpacing: 12,
    childAspectRatio: 0.8,
  ),
  itemCount: products.length,
  itemBuilder: (context, index) => ProductCard(product: products[index]),
)

四、装饰与样式

4.1 BoxDecoration --- 盒子装饰

BoxDecorationContainerdecoration 属性,提供丰富的视觉效果。

dart 复制代码
Container(
  decoration: BoxDecoration(
    // 背景色
    color: Colors.white,

    // 或渐变背景(与 color 互斥)
    gradient: const LinearGradient(
      begin: Alignment.topLeft,
      end: Alignment.bottomRight,
      colors: [Color(0xFF6750A4), Color(0xFF03DAC6)],
    ),

    // 圆角
    borderRadius: BorderRadius.circular(16),
    // 也可单独设置某个角
    // borderRadius: BorderRadius.only(topLeft: Radius.circular(16)),

    // 边框
    border: Border.all(
      color: Colors.purple.withOpacity(0.3),
      width: 1.5,
    ),

    // 阴影(可设多个)
    boxShadow: [
      BoxShadow(
        color: Colors.black.withOpacity(0.08),
        blurRadius: 16,
        spreadRadius: 0,
        offset: const Offset(0, 4),
      ),
    ],

    // 背景图片
    image: const DecorationImage(
      image: NetworkImage('https://example.com/bg.jpg'),
      fit: BoxFit.cover,
    ),
  ),
)

实用渐变类型

dart 复制代码
// 线性渐变
LinearGradient(
  colors: [Colors.blue, Colors.purple],
  stops: [0.0, 1.0],        // 可选:指定颜色断点
)

// 径向渐变
RadialGradient(
  center: Alignment.center,
  radius: 0.8,
  colors: [Colors.yellow, Colors.orange],
)

// 扇形渐变
SweepGradient(
  colors: [Colors.red, Colors.green, Colors.blue, Colors.red],
)

4.2 TextStyle --- 文字样式

dart 复制代码
TextStyle(
  fontSize: 16,                          // 字号
  fontWeight: FontWeight.w600,           // 字重(w100~w900)
  fontStyle: FontStyle.italic,           // 斜体
  color: Colors.black87,                 // 颜色
  letterSpacing: 0.5,                    // 字间距
  wordSpacing: 2.0,                      // 词间距
  height: 1.6,                           // 行高(倍数)
  decoration: TextDecoration.underline,  // 下划线
  decorationColor: Colors.red,
  decorationStyle: TextDecorationStyle.dashed,
  shadows: [
    Shadow(
      color: Colors.black26,
      blurRadius: 4,
      offset: Offset(1, 1),
    ),
  ],
  overflow: TextOverflow.ellipsis,
)

样式继承与合并

dart 复制代码
// copyWith 在现有样式基础上修改部分属性
final baseStyle = const TextStyle(fontSize: 14, color: Colors.grey);
final titleStyle = baseStyle.copyWith(fontSize: 18, fontWeight: FontWeight.bold);

// merge 合并两个样式
final merged = baseStyle.merge(const TextStyle(color: Colors.black));

4.3 ThemeData --- 全局主题配置

MaterialApptheme 属性中配置全局主题,避免到处硬编码颜色和样式。

dart 复制代码
MaterialApp(
  theme: ThemeData(
    // 颜色方案(Flutter 3.x 推荐用 ColorScheme)
    colorScheme: ColorScheme.fromSeed(
      seedColor: const Color(0xFF6750A4),
      brightness: Brightness.light,
    ),
    useMaterial3: true,

    // 文字主题
    textTheme: const TextTheme(
      displayLarge: TextStyle(fontSize: 57, fontWeight: FontWeight.w400),
      headlineMedium: TextStyle(fontSize: 28, fontWeight: FontWeight.w500),
      bodyLarge: TextStyle(fontSize: 16, height: 1.6),
      bodyMedium: TextStyle(fontSize: 14, color: Colors.black87),
      labelSmall: TextStyle(fontSize: 11, letterSpacing: 0.5),
    ),

    // 按钮主题
    elevatedButtonTheme: ElevatedButtonThemeData(
      style: ElevatedButton.styleFrom(
        padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
      ),
    ),

    // 输入框主题
    inputDecorationTheme: InputDecorationTheme(
      border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)),
      contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
    ),

    // 卡片主题
    cardTheme: CardTheme(
      elevation: 2,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
      margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
    ),

    // AppBar 主题
    appBarTheme: const AppBarTheme(
      centerTitle: true,
      elevation: 0,
    ),
  ),

  // 深色主题
  darkTheme: ThemeData(
    colorScheme: ColorScheme.fromSeed(
      seedColor: const Color(0xFF6750A4),
      brightness: Brightness.dark,
    ),
    useMaterial3: true,
  ),

  themeMode: ThemeMode.system,   // 跟随系统
  home: const MyHomePage(),
)

在代码中使用主题

dart 复制代码
@override
Widget build(BuildContext context) {
  final theme = Theme.of(context);
  final colorScheme = theme.colorScheme;
  final textTheme = theme.textTheme;

  return Container(
    color: colorScheme.surface,
    child: Text(
      '标题',
      style: textTheme.headlineMedium,
    ),
  );
}

局部主题覆盖

dart 复制代码
// 只修改局部区域的主题,不影响全局
Theme(
  data: Theme.of(context).copyWith(
    iconTheme: const IconThemeData(color: Colors.red, size: 24),
  ),
  child: Row(
    children: const [
      Icon(Icons.star),      // 红色
      Icon(Icons.favorite),  // 红色
    ],
  ),
)

五、综合实战示例

下面通过一个商品卡片组件,综合运用本阶段所学知识:

dart 复制代码
class ProductCard extends StatefulWidget {
  final String title;
  final String imageUrl;
  final double price;
  final double rating;

  const ProductCard({
    super.key,
    required this.title,
    required this.imageUrl,
    required this.price,
    required this.rating,
  });

  @override
  State<ProductCard> createState() => _ProductCardState();
}

class _ProductCardState extends State<ProductCard> {
  bool _isFavorite = false;

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return Container(
      decoration: BoxDecoration(
        color: theme.colorScheme.surface,
        borderRadius: BorderRadius.circular(16),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.08),
            blurRadius: 12,
            offset: const Offset(0, 4),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          // 图片区域(Stack 实现收藏按钮覆盖)
          Stack(
            children: [
              ClipRRect(
                borderRadius: const BorderRadius.vertical(
                  top: Radius.circular(16),
                ),
                child: Image.network(
                  widget.imageUrl,
                  height: 160,
                  width: double.infinity,
                  fit: BoxFit.cover,
                ),
              ),
              Positioned(
                top: 8,
                right: 8,
                child: GestureDetector(
                  onTap: () => setState(() => _isFavorite = !_isFavorite),
                  child: Container(
                    padding: const EdgeInsets.all(6),
                    decoration: BoxDecoration(
                      color: Colors.white.withOpacity(0.9),
                      shape: BoxShape.circle,
                    ),
                    child: Icon(
                      _isFavorite ? Icons.favorite : Icons.favorite_border,
                      color: _isFavorite ? Colors.red : Colors.grey,
                      size: 20,
                    ),
                  ),
                ),
              ),
            ],
          ),

          // 信息区域
          Padding(
            padding: const EdgeInsets.all(12),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  widget.title,
                  style: theme.textTheme.titleMedium?.copyWith(
                    fontWeight: FontWeight.w600,
                  ),
                  maxLines: 2,
                  overflow: TextOverflow.ellipsis,
                ),
                const SizedBox(height: 8),

                // 评分行
                Row(
                  children: [
                    const Icon(Icons.star, color: Colors.amber, size: 16),
                    const SizedBox(width: 4),
                    Text(
                      widget.rating.toStringAsFixed(1),
                      style: theme.textTheme.bodySmall,
                    ),
                    const Spacer(),

                    // 价格
                    RichText(
                      text: TextSpan(
                        children: [
                          TextSpan(
                            text: '¥',
                            style: TextStyle(
                              fontSize: 12,
                              color: theme.colorScheme.error,
                            ),
                          ),
                          TextSpan(
                            text: widget.price.toStringAsFixed(0),
                            style: TextStyle(
                              fontSize: 20,
                              fontWeight: FontWeight.bold,
                              color: theme.colorScheme.error,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),

                const SizedBox(height: 12),
                SizedBox(
                  width: double.infinity,
                  child: ElevatedButton(
                    onPressed: () {},
                    child: const Text('加入购物车'),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

六、常见问题与最佳实践

性能陷阱

  1. 避免在 build() 中创建对象TextStyle()BoxDecoration() 等应提取为 const 或移到 class 级别。
  2. ListView 大数据用 builderListView(children: [...]) 会一次性渲染所有 Widget;ListView.builder 是懒加载。
  3. const Widget :凡是不依赖运行时数据的 Widget,加 const 关键字,可复用实例,减少重建。

Widget 选择原则

  • 需要背景色 + 无子 Widget → ColoredBox(比 Container 轻量)
  • 只需内边距 → Padding(比 Container 轻量)
  • 只需居中 → Center(比 Align(alignment: Alignment.center) 语义更清晰)
  • 文本间距 → SizedBox 优于 Padding(语义更明确)

调试工具

dart 复制代码
// 调试布局,显示边界线
debugPaintSizeEnabled = true;

// 打印 Widget 树
debugDumpApp();

// 性能叠加层(帧耗时)
// 在 MaterialApp 中开启
showPerformanceOverlay: true,

总结

本阶段涵盖了 Flutter 基础 Widget 的核心内容:

  • 理解三树架构是优化性能、排查问题的理论基础
  • StatelessWidget / StatefulWidget 的正确选择直接影响代码架构
  • 布局 Widget 的组合是构建复杂 UI 的关键,Row/Column/Stack/Expanded 是最常用的四剑客
  • BoxDecoration + TextStyle + ThemeData 三者配合,实现统一、优雅的视觉设计

下一阶段将进入状态管理路由导航,届时会大量运用本阶段的 Widget 知识作为基础。建议在继续之前,动手实现 2~3 个完整的页面(如登录页、商品列表页、个人中心页),巩固所学内容。

相关推荐
计算机安禾2 小时前
【数据结构与算法】第31篇:排序概述与插入排序
c语言·开发语言·数据结构·学习·算法·重构·排序算法
xyx-3v2 小时前
C++构造函数、析构函数与拷贝控制深度解析
开发语言·c++
Larry_Yanan2 小时前
Qt+OpenCV(一)环境搭建
开发语言·c++·qt·opencv·学习
品克缤2 小时前
Vue3 + Router 页面切换时滚动条闪烁问题记录
前端·javascript·css·vue.js
独特的螺狮粉2 小时前
开源鸿蒙跨平台Flutter开发:微波射频阻抗匹配系统-极坐标史密斯圆图与天线信号渲染架构
开发语言·flutter·华为·架构·开源·harmonyos
冰暮流星2 小时前
javascript之dom方法访问内容
开发语言·前端·javascript
小雨天気.2 小时前
Flutter 框架跨平台鸿蒙开发 - 选择困难终结者应用
flutter·华为·生活·harmonyos·鸿蒙
竹林8182 小时前
React + wagmi 实战:从零构建一个能“读”能“写”的 DeFi 前端,我踩了这些坑
前端·javascript
我命由我123452 小时前
在 React 项目中,配置了 setupProxy.js 文件,无法正常访问 http://localhost:3000
开发语言·前端·javascript·react.js·前端框架·ecmascript·js