在Fluter的世界一切都是Widget,所以这个玩意,是空气,是金钱,是上帝,是Flutter生活之必须。
每个写Flutter的都天天敲打他,他都豆豆还辛苦,吃饭睡觉打Widget。
那么,今天。还是要无聊的说一下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元素,非常适用于静态内容的展示和简单交互。
什么是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渲染,不需要维护任何状态。这使得开发者可以专注于处理按钮点击事件的逻辑,而无需关心组件的状态管理。
生命周期和适用场景
由于Stateless Widget没有状态,因此它没有明确的生命周期。它的构建方法build()
在每次需要渲染时都会被调用。
Stateless Widget适用于以下场景:
- 静态内容展示:例如文本、图像、图标等静态UI元素。
- 简单交互:例如按钮点击事件、路由跳转等简单交互逻辑。
- 子组件:作为其他有状态小部件的子组件,用于构建复杂的UI结构。
在这些场景中,Stateless Widget的性能较好,代码结构清晰,易于理解和维护。
在日常开发中的典型使用
以下是一些日常开发中常见的使用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
什么是State?
首先,我们需要理解什么是State(状态)。在Flutter中,State就是数据。这些数据可以影响UI,当数据改变时,UI就会重建,反映新的信息。一个Widget的State,就是它持有的数据,以及这些数据如何影响UI的方式。
对于Stateful Widget来说,它们的状态就是我们要管理的核心。每个Stateful Widget都有一个与之关联的State对象。此对象在Widget的整个生命周期中都存在。
生命周期
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();
}
}
获取State对象
有两种主要的方法可以获取State对象:
-
context.findAncestorStateOfType<_ExampleWidgetState>()
: 从当前的context开始向上遍历,直到找到对应类型的State对象。
_ExampleWidgetState state = context.findAncestorStateOfType<_ExampleWidgetState>();
-
GlobalKey
: 创建一个全局键,然后在我们需要的时候,使用这个键来获取状态。
GlobalKey<_ExampleWidgetState> globalKey = GlobalKey();
// 你可以使用这个globalKey创建你的Widget
ExampleWidget(key: globalKey);
// 然后,你可以在任何地方使用这个globalKey来获取状态
_ExampleWidgetState state = globalKey.currentState;
请注意,过度使用GlobalKey
可能会导致性能问题。所以在可能的情况下,尽量避免使用它。
如何创建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在给定其当前配置和状态时应如何显示。
使用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的重新构建,因此每次点击按钮,计数器就会增加,同时在屏幕上显示的数字也会更新。
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}'),
),
],
);
}
}
自定义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树中的特定数据。
BuildContext如何工作?
让我们用一个例子来了解BuildContext如何工作的。假设我们有以下的Widget树:
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
上添加一个新的路由。
使用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树中有效地传递数据。
什么是InheritedWidget?
在Flutter中,Widgets是不可变的。这意味着一旦你创建了一个Widget,就不能再更改它。这在许多情况下非常有用,但是当你需要在Widget树中的多个地方共享同一份数据时,这就可能成为一个问题。
这就是InheritedWidget发挥作用的地方。InheritedWidget是一个特殊类型的Widget,它可以在它的子Widget树中共享数据。
如何使用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
中定义的颜色。
结论
InheritedWidget 是一个强大的工具,让我们可以在 Widget 树中有效地共享数据。理解和使用InheritedWidget可以让你更好地管理你的应用的状态,提高你的代码的可读性和可维护性。