掌握Widget,你就掌握了构建FlutterUI世界的基本法则。
你好,欢迎回到《Flutter入门到精通》专栏。在上一讲中,我们剖析了Flutter项目的结构,并体验了热重载的神奇。你可能已经注意到,代码中充满了各种各样的 Widget。没错,在Flutter的世界里,一切皆是Widget。
本讲将带你深入理解Widget的概念,区分无状态与有状态Widget,并亲手构建你的第一个自定义Widget。
一、什么是Widget?UI的构建块
1.1 Widget的本质
简单来说,Widget是您用于声明和构建用户界面的蓝图或配置。
它并不代表最终绘制在屏幕上的UI元素,而是一个描述。Flutter框架会根据这些Widget的配置信息,创建出相应的、高效的渲染对象,最终由Skia引擎绘制到屏幕上。
一个生动的比喻:
-
Widget 就像建筑图纸。它描述了房子的结构、房间的布局、门窗的样式。
-
Flutter框架 就像施工队,它读取图纸(Widget),调用建筑材料(渲染对象),最终建起真正的房子(用户界面)。
-
当你想改变一扇窗(UI)时,你不需要自己去拆墙,你只需要修改图纸(更新Widget),施工队(Flutter框架)就会高效地帮你完成更新。
1.2 Widget树:UI的层次结构
Flutter的UI是通过嵌套Widget来构建的,形成一个Widget树。
让我们回顾一下上一讲计数器应用中的 build 方法:
dart
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title), // AppBar 包含一个 Text
),
body: Center( // Center 包含一个 Column
child: Column( // Column 包含两个 Text
children: <Widget>[
const Text('You have pushed the button this many times:'),
Text('$_counter'),
],
),
),
floatingActionButton: FloatingActionButton( // 一个独立的Widget
onPressed: _incrementCounter,
child: const Icon(Icons.add), // FloatingActionButton 包含一个 Icon
),
);
}
这段代码对应的Widget树可以简化为:
text
Scaffold
├── AppBar
│ └── Text
├── Center
│ └── Column
│ ├── Text
│ └── Text
└── FloatingActionButton
└── Icon
理解Widget树是理解Flutter布局和渲染的关键。
二、StatelessWidget vs StatefulWidget:核心区别
这是Flutter中最重要的概念之一。所有Widget都基于这两种类型之一。
2.1 StatelessWidget(无状态Widget)
-
定义 : 一旦创建,其内部状态就不可变的Widget。它就像一张静态图片。
-
特点:
-
它依赖于父Widget传递过来的初始配置信息和自身不变的固有属性。
-
它不存储任何会随时间变化的数据。
-
它只有一个必需的
build方法。
-
-
常见例子 :
Text,Icon,Image,AppBar等。
创建一个简单的StatelessWidget:
让我们创建一个显示问候语的Widget。
dart
// 定义一个名为 GreetingText 的无状态Widget
class GreetingText extends StatelessWidget {
// 构造函数:接收一个不可变的最终属性 `name`
const GreetingText({super.key, required this.name});
// 声明一个 final 成员变量,意味着它一旦被赋值就不能再改变
final String name;
// 必须实现的 build 方法,描述如何构建这个Widget
@override
Widget build(BuildContext context) {
return Text(
'Hello, $name!', // 使用传入的 name 属性
style: const TextStyle(fontSize: 20),
);
}
}
如何使用它?
在你之前 MyHomePage 的 body 中,可以这样使用:
dart
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GreetingText(name: 'Flutter Developer'), // 在这里使用!
Text('$_counter'),
],
),
),
2.2 StatefulWidget(有状态Widget)
-
定义 : 在其生命周期内,持有可变状态的Widget。它就像一个可以播放的视频播放器,其播放进度、音量等状态会变化。
-
特点:
-
它由两个类组成:一个不可变的
StatefulWidget子类和一个可变的State子类。 -
状态(数据)存储在
State对象中 ,而不是StatefulWidget本身。 -
当状态改变时,调用
setState()来通知框架重建UI。
-
-
常见例子 : 带有表单输入的页面、动画、计数器(我们之前的
MyHomePage就是)。
剖析StatefulWidget的结构:
计数器应用中的 MyHomePage 就是一个完美的例子。我们来分解它:
dart
// 1. StatefulWidget 部分:本身是不可变的,主要负责接收和传递初始配置
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title; // 不可变的属性
// 这个方法创建并返回管理状态的 State 对象
@override
State<MyHomePage> createState() => _MyHomePageState();
}
// 2. State 部分:这里是状态和逻辑存在的地方
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0; // 可变的状态!存储在这里
void _incrementCounter() {
// 修改状态,并通知框架“状态已改变,请重建UI!”
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
// build 方法在 State 类中!
// 它可以访问可变状态 (_counter) 和来自Widget的不可变属性 (widget.title)
return Scaffold(
appBar: AppBar(title: Text(widget.title)), // 使用 widget.[属性名] 访问
body: Center(child: Text('$_counter')), // 使用状态 _counter
floatingActionButton: FloatingActionButton(onPressed: _incrementCounter),
);
}
}
关键点总结:
| 特性 | StatelessWidget | StatefulWidget |
|---|---|---|
| 状态 | 不可变 | 可变 (存储在单独的 State 对象中) |
| 组成 | 一个类 | 两个类:StatefulWidget + State |
| 性能 | 较轻量 | 相对较重(因为需要管理状态生命周期) |
| 使用场景 | 静态内容、展示型UI | 交互式UI、动态数据、动画 |
三、实践:构建一个交互式自定义Widget
现在,让我们综合所学,创建一个全新的、包含交互的自定义StatefulWidget------一个简单的"喜欢"按钮。
-
在
lib文件夹下,新建一个Dart文件,比如like_button.dart。 -
在文件中写入以下代码:
dart
import 'package:flutter/material.dart';
class LikeButton extends StatefulWidget {
// 构造函数:可以接收一个初始的喜欢状态,默认为false
const LikeButton({super.key, this.isLiked = false});
final bool isLiked;
@override
State<LikeButton> createState() => _LikeButtonState();
}
class _LikeButtonState extends State<LikeButton> {
// 状态:这个按钮当前是否被“喜欢”
bool _isLiked = false;
// 初始化状态:我们可以用从Widget传过来的初始值来设置状态
@override
void initState() {
super.initState();
_isLiked = widget.isLiked;
}
// 处理点击事件的方法
void _toggleLike() {
setState(() {
_isLiked = !_isLiked; // 切换喜欢状态
});
}
@override
Widget build(BuildContext context) {
return GestureDetector( // 一个可以检测手势的Widget
onTap: _toggleLike, // 当被点击时,调用 _toggleLike 方法
child: Row(
mainAxisSize: MainAxisSize.min, // 让Row只占用所需的空间
children: [
Icon(
_isLiked ? Icons.favorite : Icons.favorite_border, // 根据状态选择图标
color: _isLiked ? Colors.red : Colors.grey, // 根据状态选择颜色
),
const SizedBox(width: 5), // 添加一些间距
Text(_isLiked ? 'Liked' : 'Like'), // 根据状态显示文本
],
),
);
}
}
- 在
main.dart中引入这个文件,并在MyHomePage的body中使用它:
dart
// 在文件顶部导入
import 'like_button.dart';
// ... 在 MyHomePage 的 body 的 Column 的 children 中添加 ...
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('Check out this new feature:'),
LikeButton(), // 使用默认值
const SizedBox(height: 20), // 添加垂直间距
LikeButton(isLiked: true), // 使用初始值为true
const SizedBox(height: 20),
Text('Counter: $_counter'),
],
),
),
现在运行你的应用,你应该能看到两个"Like"按钮,点击它们可以独立地在"Like"和"Liked"状态之间切换!
结语
恭喜!你已经迈出了成为Flutter开发者的最关键一步。你不仅理解了Widget是Flutter UI的基石 ,还掌握了无状态与有状态Widget的核心区别与创建方法,并成功构建了属于自己的交互式组件。
记住这个简单的决策流程:如果你的UI部分需要根据数据变化而改变,就使用StatefulWidget;如果它只是静态展示,就使用StatelessWidget。
在下一讲中,我们将暂时放下抽象概念,开始学习具体的、强大的布局与基础Widget ,如 Container, Row, Column, Stack 等,让你能够构建出更加复杂和精美的界面。