说到事件就不得不聊到 Flutter 中的核心概念 Stateful Widget 有状态组件,它代表了可以在其生命周期中改变状态。什么叫做状态发生改变呢? 比如有用户的交互行为,点击或者滑动,再比如从服务端接收到了新的数据。当这些状态发生改变时,Flutter 就会调用 build 方法来重新构建这些有状态组件,来确保小组件的状态和 UI 是同步的。
举个🌰,我们写一个 Switcher
先定义一个有状态小组件,通过继承 StatefulWidget
, 然后 override createState
方法来创建一个状态
dart
class MySwitch extends StatefulWidget {
@override
_MySwitchState createState() => _MySwitchState();
}
接下来就要写我们本节的重点:事件交互!也就是当用户点击 Switch 切换状态,UI 也要跟着变
来实现这个状态,以及状态相关的 UI
dart
class _MySwitchState extends State<MySwitch> {
bool _switchValue = false;
@override
Widget build(BuildContext context) {
return Switch(
value: _switchValue,
onChanged: (value) {
setState(() {
_switchValue = value;
});
},
);
}
}
我们用了一个 Switch
组件,当用户点击 Switch 时会触发事件 onChanged
, 然后我们在这个函数中通过调用setState()方法来改变_switchValue的值。setState()方法会触发build()方法的重新执行,从而更新Switch的UI。也就是说 setState 用于通知 Flutter 框架,有状态的Widget(StatefulWidget)的状态发生了变化,这样Flutter就会更新用户界面。
需要注意的是,不是所有的数据的改变都需要调用setState()。只有当数据的改变会导致UI的更新时,我们才需要调用setState()。通常这些状态都会被定义在 State 类中,做 UI 一定要做好状态管理,否则根本分不清UI在什么情况下变,什么情况下不变
那稍微来总结一下如何构建一个有状态组件,只需要2步
- 继承 StatefulWidget,通过override createState 创建一个状态
- 继承 State, 给 Flutter 提供个 build 方法, 为了当我们状态在 setState 变化时,Flutter 通知重新构建组件
父组件管理状态
刚才这个例子中,我们是自己管理 Switch 的状态, 但是在复杂应用中,我们的 Switch 状态可能会被其他组件所依赖,我们怎么传递给其他组件呢? 需要通过一个"中介",这个中介就是父组件,这就类似于租房,我们是房东,我们需要将房子钥匙委托给中介,让它来保管和带租客看房。
让我们改造一下,创建一个允许用户切换夜间模式的 Switcher
父组件里面设置一个 _NeightValue
状态,子组件里面用 Switch 来修改这个值,当 _NeightValue
变化,我们就将主题设置成夜晚模式。 这样的话这个子组件改成一个 Stateless 组件就行,也就是 React 中的受控组件。
先来写父组件, 我们用到 MaterialApp 中提供的 theme 属性来实现暗黑主题
dart
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _isNightMode = false;
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: _isNightMode ? ThemeData.dark() : ThemeData.light(),
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Night Mode Switch Example'),
),
body: Center(
child: MySwitch(
switchValue: _isNightMode,
onChanged: (value) {
setState(() {
_isNightMode = value;
});
},
),
),
),
);
}
}
Line 18 我们调用子组件,这里注意我们传递给他 2 个参数, 这 2 个参数在子组件里我们要声明一下
dart
class MySwitch extends StatelessWidget {
final bool switchValue;
final ValueChanged<bool> onChanged;
MySwitch({required this.switchValue, required this.onChanged});
@override
Widget build(BuildContext context) {
return Switch(
value: switchValue,
onChanged: onChanged,
);
}
}
在Flutter中,是否在父组件中管理状态取决于具体的使用场景。对于一些简单的情况,直接在Widget内部管理状态就可以了。但是,当状态变得复杂,特别是多个组件需要共享状态时,通常推荐在父组件或者提供了状态管理功能的组件(如Provider)中管理状态, 可以参考本系列另一篇关于状态管理的文章。这样可以更好地组织代码,使状态管理变得更易于理解和维护。
手势
有些组件是自带事件的,有些组件需要我们自己绑定事件,比如 Text
组件,我们如果实现点击这个 Text 更改 Text 的文本呢? 最简单的方式就是使用手势 GestureDetector
, 只需要将它包裹在你希望添加手势识别的部件周围,然后传入相应的回调函数。
dart
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Flutter Night Mode Switch Example'),
),
body: Center(
child: GestureDetector(
onTap: (){
print('something happend');
},
child: Text("Click me"),
)
),
),
);
}
GestureDetector是Flutter中一个非常关键的部件,它可以对触摸事件进行识别。你可以用它来识别各种类型的手势,包括点击、双击、长按、拖动等等。
只是 print 一下很无聊,我们来使用 SnackBar
组件来做一个点击反馈, SnackBar 会在屏幕的底部显示一条消息,并在一段时间后自动消失。
dart
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Snackbar 示例'),
),
body: Center(
child: Builder(
builder: (context) => GestureDetector(
child: Text('Click'),
onTap: () {
final snackBar = SnackBar(
content: Text('click!'),
);
ScaffoldMessenger.of(context).showSnackBar(snackBar);
},
),
),
),
),
);
}
}
Line 17 ScaffoldMessenger 是 Flutter 中的一个类,它提供了一个框架用于显示SnackBar。ScaffoldMessenger 可以包含多个 Scaffold 的上下文,它可以用来控制在这些 Scaffold 中显示SnackBar 通过调用ScaffoldMessenger的showSnackBar方法。
除了 GestureDetector 外,还可以通过 InkWell
添加事件,使用方法一模一样,InkWell 和GestureDetector 都是 Flutter 中用于处理触摸事件的 Widget,但它们之间有一些关键区别。GestureDetector 更为基础,它可以识别各种手势,如点击、双击、长按、拖动等,并且它不包含任何视觉表示。换句话说,GestureDetector 本身不会改变显示的视图,它只是识别和响应手势。
相比之下,InkWell 不仅可以识别触摸事件,还可以在用户触摸屏幕时显示水波纹效果。这提供了一种视觉反馈,让用户知道他们的操作已经被接收。因此用哪个取决于你是否需要在用户触摸屏幕时提供视觉反馈。