Flutter初识(5):Widget详解
一、Widget的分类
在Flutter中,Widget主要分为两种:Stateless Widget
和Stateful Widget。
无状态 Stateless Widget
Stateless Widget 是简单的 Widget,它描述了一种在给定配置下的固定视图。一旦创建,Stateless Widget 的 UI 就不会发生变化。例如,一个图标(Icon)就是一个 Stateless Widget。
有状态 Stateful Widget
相比之下,Stateful Widget 可以在生命周期内进行变化。这是通过与一个独立的 State 对象进行交互来实现的。当 State 对象改变时,Stateful Widget 的 UI 就会更新。例如,一个复选框(Checkbox)就是一个 Stateful Widget。
二、有状态 Stateful Widget本质也是不可变的
在 Flutter 中,Widget 是一切的基础。无论你是要创建一个按钮,一个文字,还是一个滑动列表,都是通过 Widget 来实现的。它们都是不可变的,并且具有较短的生命周期。换句话说,你不能直接改变一个 Widget,而是应该通过创建一个新的 Widget 来更新 UI。
每一个 Widget 都有一个build
方法,这是其核心部分。build
方法描述了 Widget 如何根据给定的配置和状态构建自己的 UI。
你不能直接改变一个 Widget,而是应该通过创建一个新的 Widget来更新 UI。 对于这句话,是否有疑惑呢?
改变一个 Stateful Widget 时,实际上是指改变与该 Widget 关联的 State 对象
在 Flutter 中,Widget 本身确实是不可变的,包括 Stateful Widget。当我们说 "改变Widget" 时,实际上是指创建一个新的 Widget 实例,并可能会与新的状态对象关联。
对于 Stateful Widget,虽然 Widget 本身是不可变的,但它与一个 State 对象关联,这个 State 对象可以在 Widget 的生命周期中改变。当我们说"改变一个 Stateful Widget"时,实际上是指改变与该 Widget 关联的 State 对象。
所以,当我们要更新UI时,实际上是通过改变 State 对象,然后创建新的 Widget 来实现的。这是 Flutter 框架的工作原理,也是其性能优化的一个重要方面。
举个例子,假设你有一个带有计数器的 Stateful Widget。当用户点击按钮时,你可能会更新 State 对象(也就是计数器的值)。Flutter 框架将会创建一个新的 Widget,并根据新的 State 对象(新的计数器值)来构建 UI。这个过程中,Stateful Widget 的 build 方法就起到了关键作用,它描述了如何根据新的 State 对象来构建 UI。
三、Stateless Widget 无状态
在 Flutter 开发中,Stateless Widget(无状态小部件)是一种非常重要且常用的组件。它提供了一种简单的方式来构建不需要维护状态的UI元素,非常适用于静态内容的展示和简单交互。
3.1 什么是Stateless Widget?
Stateless Widget 是 Flutter 框架中的一个概念,它代表了一个不可变的UI组件。这意味着 Stateless Widget 在创建后不会发生任何改变,其内部的属性和状态都是不可变的。由于不需要维护状态,Stateless Widget 通常被用于展示静态内容,例如文本、图像等。
与 Stateful Widget(有状态小部件)相比,Stateless Widget 更加简单且易于使用。它没有生命周期方法,不需要处理状态变化,只需要根据传入的属性进行渲染,因此具有更高的性能。
下面我们通过多个代码例子来详细说明 Stateless Widget 的使用方法和场景。
代码例子1:简单的文本展示
import 'package:flutter/material.dart';
class MyTextWidget extends StatelessWidget {
final String text;
MyTextWidget(this.text);
@override
Widget build(BuildContext context) {
return Text(
text,
style: TextStyle(fontSize: 16.0),
);
}
}
上述代码展示了一个简单的文本展示的 Stateless Widget。它接收一个字符串作为参数,然后使用 Text 小部件将该字符串以 16 号字体大小展示出来。
在这个例子中,Stateless Widget 非常适合用于展示静态的文本内容,因为它不需要维护任何状态。
代码例子2:按钮点击事件
import 'package:flutter/material.dart';
class MyButtonWidget extends StatelessWidget {
final VoidCallback onPressed;
MyButtonWidget(this.onPressed);
@override
Widget build(BuildContext context) {
return RaisedButton(
onPressed: onPressed,
child: Text('Click me'),
);
}
}
上述代码展示了一个简单的按钮点击事件的 Stateless Widget。它接收一个回调函数作为参数,并将该回调函数绑定到 RaisedButton 小部件的 onPressed 属性上。
Stateless Widget 在这种场景下非常适用,因为它只需要根据传入的回调函数进行 UI 渲染,不需要维护任何状态。这使得开发者可以专注于处理按钮点击事件的逻辑,而无需关心组件的状态管理。
3.2 生命周期和适用场景
由于 Stateless Widget 没有状态,因此它没有明确的生命周期。它的构建方法build()
在每次需要渲染时都会被调用。
Stateless Widget 适用于以下场景:
- 静态内容展示:例如文本、图像、图标等静态 UI 元素。
- 简单交互:例如按钮点击事件、路由跳转等简单交互逻辑。
- 子组件:作为其他有状态小部件的子组件,用于构建复杂的 UI 结构。
在这些场景中,Stateless Widget 的性能较好,代码结构清晰,易于理解和维护。
3.3在日常开发中的典型使用
以下是一些日常开发中常见的使用 Stateless Widget 的例子:
例子1:静态文本展示
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('My App'),
),
body: Center(
child: MyTextWidget('Hello, Flutter!'),
),
),
);
}
}
在这个例子中,我们使用 Stateless Widget MyTextWidget
来展示一个静态文本。
例子2:登录页面
class LoginPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Login'),
),
body: Container(
padding: EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
decoration: InputDecoration(
labelText: 'Username',
),
),
TextField(
decoration: InputDecoration(
labelText: 'Password',
),
obscureText: true,
),
RaisedButton(
onPressed: () {
// 处理登录逻辑
},
child: Text('Login'),
),
],
),
),
);
}
}
在这个例子中,我们使用 Stateless Widget 构建了一个简单的登录页面。其中包含了两个文本输入框和一个登录按钮,通过 Stateless Widget 的方式来组织UI结构,使得代码更加清晰易读。
通过以上例子,我们可以看到 Stateless Widget 在日常开发中的典型使用方式,它简单、高效,并能提升代码的可读性和可维护性。
四、Stateful Widget
4.1 什么是State?
首先,我们需要理解什么是 State(状态)。在 Flutter 中,State 就是数据。这些数据可以影响 UI,当数据改变时,UI 就会重建,反映新的信息。一个 Widget 的 State,就是它持有的数据,以及这些数据如何影响UI的方式。
对于 Stateful Widget 来说,它们的状态就是我们要管理的核心。每个 Stateful Widget 都有一个与之关联的 State 对象。此对象在 Widget 的整个生命周期中都存在。
4.2 生命周期
Stateful Widget 的生命周期主要包括以下几个步骤:
createState()
: 这是在插入widget时调用的。它返回一个新的 State 对象。initState()
: 在创建 State 对象后,此方法会被立即调用。这是初始化数据或启动动画的地方。build()
: 这是 Flutter 构建 UI 的主要方式。当你的数据发生改变时,Flutter 会调用 build 方法,然后根据新的数据重建 UI。dispose()
: 当 Widget 被从树中移除时,dispose 方法被调用,这是一个清理资源的好地方。
下面是一个简单的例子:
class ExampleWidget extends StatefulWidget {
@override
_ExampleWidgetState createState() => _ExampleWidgetState();
}
class _ExampleWidgetState extends State<ExampleWidget> {
int counter = 0; // 这就是我们的state,一个简单的计数器
@override
void initState() {
super.initState();
// 初始化状态
counter = 0;
}
@override
Widget build(BuildContext context) {
// 每次我们的计数器改变,build方法都会被调用,UI会被重建
return FlatButton(
child: Text('我被按了 $counter 次'),
onPressed: () {
setState(() {
// 改变状态
counter++;
});
},
);
}
@override
void dispose() {
// 清理工作
super.dispose();
}
}
4.3 获取State对象
有两种主要的方法可以获取State对象:
(1)context.findAncestorStateOfType<_ExampleWidgetState>()
: 从当前的context开始向上遍历,直到找到对应类型的State对象。
_ExampleWidgetState state = context.findAncestorStateOfType<_ExampleWidgetState>();
(2)GlobalKey
: 创建一个全局键,然后在我们需要的时候,使用这个键来获取状态。
GlobalKey<_ExampleWidgetState> globalKey = GlobalKey();
// 你可以使用这个globalKey创建你的Widget
ExampleWidget(key: globalKey);
// 然后,你可以在任何地方使用这个globalKey来获取状态
_ExampleWidgetState state = globalKey.currentState;
请注意,过度使用GlobalKey
可能会导致性能问题。所以在可能的情况下,尽量避免使用它。
4.4 如何创建Stateful Widget
要创建一个 Stateful Widget,你需要定义两个类:一个扩展StatefulWidget
,另一个扩展State
。下面的代码显示了一个基本的 Stateful Widget 结构:
class MyStatefulWidget extends StatefulWidget {
@override
_MyStatefulWidgetState createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
// 这里定义你的状态
@override
Widget build(BuildContext context) {
// 这里返回你的Widget
}
}
MyStatefulWidget
是 Widget 本身,而_MyStatefulWidgetState
类则是存储 Widget 状态的地方。
createState
函数是StatefulWidget
中必须实现的方法,它返回一个State
对象,该对象在 Widget 的生命周期中持久存在。
build
函数则是在State
类中必须实现的,它描述了 Widget 在给定其当前配置和状态时应如何显示。
4.5 使用Stateful Widget
我们可以通过实现一个简单的计数器来了解如何使用 Stateful Widget。我们在 State 类中创建一个变量作为计数器,然后在每次点击按钮时更新它
class CounterWidget extends StatefulWidget {
@override
_CounterWidgetState createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _counter = 0; // 这就是我们的状态——一个简单的计数器
void _incrementCounter() {
setState(() { // 使用setState来告知Flutter状态已经改变,需要重建UI
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('计数器示例'),
),
body: Center(
child: Text(
'你已经点击了 $_counter 次',
style: TextStyle(fontSize: 24),
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: '增加',
child: Icon(Icons.add),
),
);
}
}
在这个例子中,我们使用setState
方法来更新状态。setState
将触发 UI 的重新构建,因此每次点击按钮,计数器就会增加,同时在屏幕上显示的数字也会更新。
4.6 Stateful Widget的优点
Stateful Widget 是 Flutter 的核心,它们允许我们创建交互式的应用程序。它们的优点主要体现在以下几个方面:
- 持久性:Stateful Widget能够在多次调用build方法之间保持状态,这让我们可以在用户与应用程序的交互过程中保持持久性的信息。
- 交互性:Stateful Widget能够响应用户的操作,如点击按钮、滑动屏幕等,然后根据这些操作来更新UI。
- 动画:Stateful Widget是创建动画的基础,因为动画需要随着时间的推移改变状态,并显示这些改变。
下面是一个实际的例子,一个简单的购物车应用程序:
class ShoppingCart extends StatefulWidget {
@override
_ShoppingCartState createState() => _ShoppingCartState();
}
class _ShoppingCartState extends State<ShoppingCart> {
List<String> cartItems = []; // 购物车中的商品
void addItem(String item) {
setState(() {
cartItems.add(item);
});
}
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
// 显示购物车中的商品
Expanded(
child: ListView.builder(
itemCount: cartItems.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(cartItems[index]),
);
},
),
),
// 添加商品的按钮
RaisedButton(
child: Text('添加商品'),
onPressed: () => addItem('商品 ${cartItems.length + 1}'),
),
],
);
}
}
4.7 自定义Stateful Widget
自定义 Stateful Widget 的过程非常简单。你只需要继承StatefulWidget
类,并实现一个返回新的 State 对象的createState
方法。下面是一个简单的计数器示例:
class CustomCounter extends StatefulWidget {
@override
_CustomCounterState createState() => _CustomCounterState();
}
class _CustomCounterState extends State<CustomCounter> {
int counter = 0;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
Text('你已经点击了 $counter 次'),
RaisedButton(
child: Text('点击我'),
onPressed: () {
setState(() {
counter++;
});
},
),
],
);
}
}
五、Stateful Widget和Stateless Widget的对比
Stateful Widget 和 Stateless Widget 是 Flutter 的两个基础类,它们都是用于创建自定义 Widget 的。主要区别在于 Stateful Widget 能够保持状态,而 Stateless Widget 不能。
一个 Stateful Widget 可以在用户与其交互时(如点击按钮,滑动屏幕)或者当它的内部状态改变时(如动画)重绘自己。另一方面,Stateless Widget 一旦创建,其所有配置都是最终的,它不会在其生命周期中发生改变。这意味着,如果 Widget 的外观在其生命周期中需要改变,你可能需要使用 Stateful Widget。
六、什么是BuildContext?
在 Flutter 中,所有的 UI 都是由 Widget 构成的。这些 Widget 按照特定的顺序和结构组织在一起,形成了一个 Widget 树。
BuildContext 是一个代表 Widget 在 Widget 树中的位置的引用。每个 Widget 都有一个 BuildContext,这个上下文在 Widget 的生命周期内保持不变。
BuildContext 可以让你访问和操作Widget树中的特定数据。
6.1 BuildContext如何工作?
让我们用一个例子来了解 BuildContext 如何工作的。假设我们有以下的 Widget 树:
dart 代码解读复制代码MaterialApp
└── Scaffold
├── AppBar
└── Column
├── Text
└── RaisedButton
在这个例子中,每个 Widget 都有一个 BuildContext,代表它在这个树中的位置。你可以把 BuildContext 想象成一个指向 Widget 的指针,它可以让你访问这个 Widget 的父 Widget、子 Widget、主题等等。
例如,你可能已经使用过这样的代码来导航到新的页面:
Navigator.of(context).push(MaterialPageRoute(builder: (context) => NewPage()));
在这段代码中,Navigator.of(context)
会向上遍历Widget树,找到最近的Navigator
Widget。然后,push
方法会在这个Navigator
上添加一个新的路由。
6.2 使用BuildContext
现在我们已经知道了 BuildContext 是什么以及它如何工作,那么我们应该如何使用它呢?
在大多数情况下,你会在build
方法中使用 BuildContext。这个 BuildContext 代表的是正在构建的Widget在Widget树中的位置。你可以用它来访问你的应用中的许多信息,比如主题、MediaQuery 等等。
下面是一个例子:
Widget build(BuildContext context) {
// 你可以通过BuildContext来访问主题数据
final theme = Theme.of(context);
return Container(
color: theme.primaryColor,
);
}
在上面的例子中,我们使用 BuildContext 来访问主题数据,并将容器的颜色设置为主题的主色。
七、InheritedWidget
InheritedWidget是一个非常强大的构件,可以让你在Widget树中有效地传递数据。
7.1 什么是InheritedWidget?
在 Flutter 中,Widgets 是不可变的。这意味着一旦你创建了一个 Widget,就不能再更改它。这在许多情况下非常有用,但是当你需要在 Widget 树中的多个地方共享同一份数据时,这就可能成为一个问题。
这就是 InheritedWidget 发挥作用的地方。InheritedWidget 是一个特殊类型的Widget,它可以在它的子 Widget 树中共享数据。
7.2 如何使用InheritedWidget?
让我们通过一个例子来看看如何使用 InheritedWidget。假设我们在应用中有一个主题颜色,我们希望在应用的多个地方使用这个颜色。
首先,我们创建一个 InheritedWidget:
class ThemeColor extends InheritedWidget {
final Color color;
ThemeColor({
Key key,
@required this.color,
@required Widget child,
}) : super(key: key, child: child);
@override
bool updateShouldNotify(ThemeColor old) => color != old.color;
static ThemeColor of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ThemeColor>();
}
}
在这个 InheritedWidget中,我们有一个color
属性来存储主题颜色。我们还提供了一个静态的of
方法来让子Widget可以方便地访问这个 InheritedWidget。
我们可以像下面这样使用这个 InheritedWidget:
void main() {
runApp(
ThemeColor(
color: Colors.blue,
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
final themeColor = ThemeColor.of(context).color;
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: themeColor,
title: Text('InheritedWidget示例'),
),
body: Center(
child: Text(
'Hello, Flutter!',
style: TextStyle(color: themeColor),
),
),
),
);
}
}
在这个例子中,我们创建了一个ThemeColor
,并将MyApp
设置为它的子Widget。然后,在MyApp
中,我们可以使用ThemeColor.of(context).color
来获取我们在ThemeColor
中定义的颜色。
7.3 结论
InheritedWidget 是一个强大的工具,让我们可以在 Widget 树中有效地共享数据。理解和使用 InheritedWidget 可以让你更好地管理你的应用的状态,提高你的代码的可读性和可维护性。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库