第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. 举一反三:尝试创建类似的组件
相关推荐
Bryce李小白2 小时前
Flutter provide框架内部实现原理刨析
flutter
CN-cheng2 小时前
Flutter项目在HarmonyOS(鸿蒙)运行报错问题总结
flutter·华为·harmonyos·flutter运行到鸿蒙
Larry_zhang双栖4 小时前
Flutter Android Kotlin 插件编译错误完整解决方案
android·flutter·kotlin
安卓开发者9 小时前
第1讲:为什么是Flutter?跨平台开发的现状与未来
flutter
芝麻开门-新起点19 小时前
Flutter 项目全流程指南:编译、调试与发布
flutter
星释19 小时前
鸿蒙Flutter三方库适配指南:11.插件发布上线及使用
flutter·华为·harmonyos
jingling55519 小时前
Flutter | 基础环境配置和创建flutter项目
前端·flutter