第2章:第一个Flutter应用 —— 2.2 Widget简介

2.2 Widget简介

📚 核心知识点

  1. Widget的概念和特点
  2. Flutter的三棵树结构
  3. StatelessWidget 和 StatefulWidget
  4. BuildContext的作用
  5. Flutter组件库介绍

💡 核心概念

1. Widget是什么?

Widget是Flutter中对用户界面的描述,是不可变的(immutable)。

关键理解:

  • Widget != 真正的UI元素
  • Widget只是配置信息(像建筑图纸)
  • 真正的渲染由RenderObject完成
scala 复制代码
// Widget只是描述
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Text('Hello');  // ← 这只是描述,不是真正的文本元素
  }
}

2. Flutter的三棵树

graph LR A[Widget树
配置信息] --> B[Element树
生命周期] B --> C[RenderObject树
实际渲染] style A fill:#E1F5FE,stroke:#01579B style B fill:#F3E5F5,stroke:#4A148C style C fill:#E8F5E9,stroke:#1B5E20
作用 特点
Widget树 UI的配置信息 不可变,频繁重建
Element树 Widget的实例化,管理生命周期 相对稳定,持有Widget和RenderObject
RenderObject树 实际的布局和绘制 负责测量、布局、绘制

工作流程:

  1. 创建Widget树(配置)
  2. Flutter根据Widget树创建Element树
  3. Element树创建和管理RenderObject树
  4. RenderObject树进行实际渲染

3. StatelessWidget vs StatefulWidget

StatelessWidget - 无状态组件

scala 复制代码
class WelcomeText extends StatelessWidget {
  final String name;
  
  const WelcomeText({required this.name});
  
  @override
  Widget build(BuildContext context) {
    return Text('欢迎, $name');
  }
}

特点:

  • ✅ 无内部状态
  • ✅ 创建后不可变
  • ✅ 只有build()方法
  • ✅ 性能更好(无状态管理开销)

使用场景:

  • 静态文本
  • 图标
  • 固定布局
  • 不需要交互的UI

StatefulWidget - 有状态组件

scala 复制代码
class Counter extends StatefulWidget {
  @override
  State<Counter> createState() => _CounterState();
}
​
class _CounterState extends State<Counter> {
  int count = 0;
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Text('Count: $count'),
        ElevatedButton(
          onPressed: () => setState(() => count++),
          child: Text('增加'),
        ),
      ],
    );
  }
}

特点:

  • ✅ 有内部状态
  • ✅ 可以通过setState()更新
  • ✅ 有完整的生命周期
  • ✅ Widget和State分离

使用场景:

  • 表单输入
  • 动画
  • 用户交互
  • 数据会变化的UI

4. BuildContext的作用

BuildContext代表Widget在树中的位置

graph TD A[MaterialApp] --> B[Scaffold] B --> C[Column] C --> D[Text] C --> E[Button] D -.BuildContext.-> C E -.BuildContext.-> C C -.BuildContext.-> B style A fill:#E1F5FE style B fill:#F3E5F5 style C fill:#E8F5E9 style D fill:#FFF3E0 style E fill:#FFF3E0

用途:

scss 复制代码
// 1. 获取主题
final theme = Theme.of(context);
​
// 2. 获取屏幕信息
final size = MediaQuery.of(context).size;
​
// 3. 导航
Navigator.of(context).push(...);
​
// 4. 显示SnackBar
ScaffoldMessenger.of(context).showSnackBar(...);
​
// 5. 查找父级Widget
final scaffold = Scaffold.of(context);

5. 获取State对象

有时候我们需要在Widget树中获取某个StatefulWidget的State对象,有两种方法:

方法1:通过Context查找

scss 复制代码
// 查找最近的ScaffoldState
ScaffoldState? scaffoldState = context.findAncestorStateOfType<ScaffoldState>();
​
if (scaffoldState != null) {
  // 使用State对象
  scaffoldState.openDrawer();
}

特点:

  • ✅ 自动向上查找Widget树
  • ✅ 不需要提前准备
  • ❌ 只能查找祖先Widget的State
  • ❌ 可能返回null

方法2:通过GlobalKey

scala 复制代码
class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}
​
class ParentWidget extends StatelessWidget {
  // 创建GlobalKey
  final GlobalKey<_MyWidgetState> _key = GlobalKey<_MyWidgetState>();
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        MyWidget(key: _key),  // ← 传递GlobalKey
        
        ElevatedButton(
          onPressed: () {
            // 通过GlobalKey获取State
            _key.currentState?.someMethod();
          },
          child: Text('调用子组件方法'),
        ),
      ],
    );
  }
}

特点:

  • ✅ 可以跨层级访问
  • ✅ 类型安全
  • ✅ 可以在任何地方使用
  • ❌ 需要提前创建GlobalKey
  • ❌ 使用不当可能影响性能

使用场景对比:

场景 推荐方法
获取父级State Context查找
操作子组件 GlobalKey
表单验证 GlobalKey
临时使用 Context查找

6. Widget接口

每个Widget都需要实现的核心方法:

csharp 复制代码
abstract class Widget {
  /// 创建Element
  Element createElement();
  
  /// 用于优化,判断是否需要重建
  Key? get key;
}

StatelessWidget实现:

scala 复制代码
abstract class StatelessWidget extends Widget {
  Widget build(BuildContext context);
  
  @override
  StatelessElement createElement() => StatelessElement(this);
}

StatefulWidget实现:

scala 复制代码
abstract class StatefulWidget extends Widget {
  State createState();
  
  @override
  StatefulElement createElement() => StatefulElement(this);
}

📚 Flutter组件库

1. 基础组件库

arduino 复制代码
import 'package:flutter/widgets.dart';

常用组件:

  • Text - 文本显示
  • Row / Column - 线性布局
  • Stack - 层叠布局
  • Container - 容器

2. Material组件库(Android风格)

arduino 复制代码
import 'package:flutter/material.dart';

常用组件:

  • MaterialApp - 应用根组件
  • Scaffold - 页面脚手架
  • AppBar - 顶部栏
  • FloatingActionButton - 悬浮按钮
  • Card - 卡片
  • Dialog - 对话框

3. Cupertino组件库(iOS风格)

arduino 复制代码
import 'package:flutter/cupertino.dart';

常用组件:

  • CupertinoApp - 应用根组件
  • CupertinoPageScaffold - 页面脚手架
  • CupertinoNavigationBar - 导航栏
  • CupertinoButton - 按钮
  • CupertinoAlertDialog - 警告对话框

示例:

less 复制代码
class CupertinoDemo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold(
      navigationBar: const CupertinoNavigationBar(
        middle: Text("iOS风格"),
      ),
      child: Center(
        child: CupertinoButton(
          color: CupertinoColors.activeBlue,
          child: const Text("按钮"),
          onPressed: () {},
        ),
      ),
    );
  }
}

🎯 核心总结

  1. Widget是不可变的UI描述,不是真正的UI元素
  2. 三棵树:Widget树(配置)→ Element树(管理)→ RenderObject树(渲染)
  3. StatelessWidget:无状态,简单高效
  4. StatefulWidget:有状态,通过setState()更新
  5. BuildContext:Widget在树中的位置,用于访问父级和服务
  6. 组件库:基础库、Material风格、Cupertino风格

📝 常见问题

Q1: 为什么Widget要设计成不可变的?

A:

  • 性能优化:不可变对象可以安全共享和复用
  • 简化对比:只需对比引用即可判断是否改变
  • 函数式编程:符合声明式UI范式

Q2: Element是做什么的?

A: Element是Widget的实例化,主要作用:

  • 持有Widget和RenderObject的引用
  • 管理Widget的生命周期
  • 在Widget树变化时执行diff算法
  • 决定是否需要重建RenderObject

Q3: 什么时候用StatelessWidget,什么时候用StatefulWidget?

A:

scss 复制代码
// 简单判断规则
有变化的数据 → StatefulWidget
没有变化的数据 → StatelessWidget
​
// 示例
Text('固定文本') → StatelessWidget
TextField() → StatefulWidget (输入会变化)
Icon(Icons.star) → StatelessWidget
Checkbox(value: checked) → StatefulWidget (选中状态会变化)

Q4: 可以在StatelessWidget中定义变量吗?

A: 可以,但这些变量是构造参数,创建后不可变:

scala 复制代码
class UserCard extends StatelessWidget {
  final String name;  // ✅ 可以,但不可变
  final int age;
  
  const UserCard({required this.name, required this.age});
  
  @override
  Widget build(BuildContext context) {
    // this.name = 'new';  // ❌ 错误!不能修改
    return Text('$name, $age岁');
  }
}

Q5: Material和Cupertino可以混用吗?

A: 可以!它们只是不同的UI风格:

less 复制代码
MaterialApp(
  home: Scaffold(
    body: CupertinoButton(  // ✅ Material中使用Cupertino组件
      child: Text('iOS风格按钮'),
      onPressed: () {},
    ),
  ),
)

🎓 跟着做练习

练习1:创建一个静态个人名片 ⭐

目标: 使用StatelessWidget创建一个显示个人信息的卡片

完整代码:

less 复制代码
class ProfileCard extends StatelessWidget {
  final String name;
  final String title;
  final String email;
​
  const ProfileCard({
    required this.name,
    required this.title,
    required this.email,
  });
​
  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(name, style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)),
            Text(title, style: TextStyle(color: Colors.grey)),
            SizedBox(height: 8),
            Row(
              children: [
                Icon(Icons.email, size: 16),
                SizedBox(width: 4),
                Text(email),
              ],
            ),
          ],
        ),
      ),
    );
  }
}
​
// 使用
ProfileCard(
  name: '张三',
  title: 'Flutter开发工程师',
  email: 'zhangsan@example.com',
)

练习2:创建一个喜欢按钮 ⭐⭐

目标: 使用StatefulWidget创建一个可以点击切换状态的喜欢按钮

完整代码:

scala 复制代码
class LikeButton extends StatefulWidget {
  @override
  State<LikeButton> createState() => _LikeButtonState();
}
​
class _LikeButtonState extends State<LikeButton> {
  bool _isLiked = false;
  int _likeCount = 0;
​
  void _toggleLike() {
    setState(() {
      _isLiked = !_isLiked;
      _likeCount += _isLiked ? 1 : -1;
    });
  }
​
  @override
  Widget build(BuildContext context) {
    return Row(
      mainAxisSize: MainAxisSize.min,
      children: [
        IconButton(
          icon: Icon(
            _isLiked ? Icons.favorite : Icons.favorite_border,
            color: _isLiked ? Colors.red : Colors.grey,
          ),
          onPressed: _toggleLike,
        ),
        Text('$_likeCount'),
      ],
    );
  }
}

练习3:使用Context获取主题色 ⭐⭐

目标: 创建一个组件,根据当前主题色显示不同样式

完整代码:

less 复制代码
class ThemedCard extends StatelessWidget {
  final String title;
  final String content;
​
  const ThemedCard({required this.title, required this.content});
​
  @override
  Widget build(BuildContext context) {
    // 从Context获取主题
    final theme = Theme.of(context);
    final primaryColor = theme.colorScheme.primary;
    
    return Card(
      color: primaryColor.withValues(alpha: 0.1),
      child: Padding(
        padding: EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              title,
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
                color: primaryColor,  // ← 使用主题色
              ),
            ),
            SizedBox(height: 8),
            Text(content),
          ],
        ),
      ),
    );
  }
}

💡 练习建议

  1. 先运行示例:看看每个组件的效果
  2. 复制代码尝试:在示例基础上修改
  3. 理解原理:思考为什么这样设计
  4. 举一反三:尝试创建类似的组件
相关推荐
恋猫de小郭4 小时前
Android 禁止侧载将正式实施,需要等待 24 小时冷静期
android·flutter·harmonyos
FFF-X4 小时前
解决 Flutter Gradle 下载报错:修改默认 distributionUrl
flutter
程序员Ctrl喵1 天前
异步编程:Event Loop 与 Isolate 的深层博弈
开发语言·flutter
前端不太难1 天前
Flutter 如何设计可长期维护的模块边界?
flutter
小蜜蜂嗡嗡1 天前
flutter列表中实现置顶动画
flutter
始持1 天前
第十二讲 风格与主题统一
前端·flutter
始持1 天前
第十一讲 界面导航与路由管理
flutter·vibecoding
始持1 天前
第十三讲 异步操作与异步构建
前端·flutter
新镜1 天前
【Flutter】 视频视频源横向、竖向问题
flutter
黄林晴1 天前
Compose Multiplatform 1.10 发布:统一 Preview、Navigation 3、Hot Reload 三箭齐发
android·flutter