懂 Vue、React 就懂 Flutter 交互事件 - 上篇

说到事件就不得不聊到 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步

  1. 继承 StatefulWidget,通过override createState 创建一个状态
  2. 继承 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 不仅可以识别触摸事件,还可以在用户触摸屏幕时显示水波纹效果。这提供了一种视觉反馈,让用户知道他们的操作已经被接收。因此用哪个取决于你是否需要在用户触摸屏幕时提供视觉反馈。

相关推荐
热爱编程的小曾25 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin37 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行2 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox
m0_593758102 小时前
firefox 136.0.4版本离线安装MarkDown插件
前端·firefox