Flutter状态管理

news2024/11/27 4:55:57

前言

状态管理是什么?简单的来说,就是当某个状态发生变化的时候,告知该状态的监听者,让状态所监听的属性随之而改变,达到UI层随着数据层变化而变化的效果。在Flutter中的状态(State)是一个组件的UI数据模型,是组件渲染时的依据。

在Flutter中一切皆Widget,不论是StatelessWidget还是StatefulWidget,它们都是直接继承了Widget类。而在页面实现的过程中,我们几乎离不开它们,Widget本身是不可变的,一旦初始化后,其属性就不再改变。

StatelessWidget和StatefulWidget其实没有本质的区别,它们的所有属性都是不可变的,所以无法更新,除非重新new一个新的Widget去替换它们。与之不同的是,StatefulWidget拥有一个可变的State,即它们的区别就在于这个可变的State。

@immutable
abstract class Widget extends DiagnosticableTree {
  /// Initializes [key] for subclasses.
  const Widget({ this.key });

  final Key? key;

  @protected
  @factory
  Element createElement();

  ...
  }

Flutter有三颗树,Widget/Element/RenderObject,Widget是整个UI界面的配置,其中 Element 是通过Widget生成的,主要作用是维护UI元素的树形结构,并将Widget和RenderObject进行关联。RenderObject 其主要作用是负责界面的绘制和布局,是属于底层系统,Flutter开发者一般不需要直接操作该树。Flutter 最原始的定义组件的方式就是通过定义RenderObject 来实现,而StatelessWidget 和 StatefulWidget 只是提供的两个帮助类。

为了近一步了解Widget,下面我们用三种方式来实现一个简单相同的自定义文本组件,代码实现:

import 'package:flutter/material.dart';

/// 方式一:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class TextWidget extends Widget {
  final String text;

  const TextWidget(this.text, {super.key});

  @override
  StatelessElement createElement() => StatelessElement(_build());

  _build() {
    return InkWell(
      child: Padding(padding: const EdgeInsets.all(16), child: Text(text)),
      onTap: () {
        print('点击文本:$text');
      },
    );
  }
}

/// 方式二:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class TextWidget2 extends StatelessWidget {
  final String text;

  const TextWidget2(this.text, {super.key});

  @override
  Widget build(BuildContext context) {
    return InkWell(
      child: Padding(padding: const EdgeInsets.all(16), child: Text(text)),
      onTap: () {
        print('点击文本:$text');
      },
    );
  }
}

/// 方式三:>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class TextWidget3 extends StatefulWidget {
  final String text;

  const TextWidget3(this.text, {super.key});

  @override
  State<StatefulWidget> createState() => _TextWidget3State();
}

class _TextWidget3State extends State<TextWidget3> {
  int _count = 0;

  @override
  Widget build(BuildContext context) {
    return InkWell(
      child: Padding(
          padding: const EdgeInsets.all(16),
          child: Text(widget.text + _count.toString())),
      onTap: () {
        _count++;
        if (_count > 9) {
          _count = 0;
        }
        print('点击文本:${widget.text}');
        setState(() {});
      },
    );
  }
}

/// 通过 RenderObject 自定义 Widget >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
class TextWidget4 extends LeafRenderObjectWidget {
  final String text;

  const TextWidget4(this.text, {super.key});

  @override
  RenderObject createRenderObject(BuildContext context) {
    return RenderCustomObject(text);
  }

  @override
  void updateRenderObject(BuildContext context, RenderBox renderObject) {
    super.updateRenderObject(context, renderObject);
  }
}

class RenderCustomObject extends RenderBox {
  final String text;

  RenderCustomObject(this.text);

  @override
  bool get sizedByParent => true;

  @override
  void performResize() {
    size = constraints.biggest;
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    TextPainter textPainter = TextPainter(textDirection: TextDirection.ltr);
    textPainter.text = TextSpan(
      text: text,
      style: const TextStyle(fontSize: 16, color: Colors.black),
    );
    textPainter.layout();
    textPainter.paint(context.canvas,
        Offset((size.width - textPainter.width) / 2, offset.dy));
  }
}

通过以上代码,我们可以发现不论是哪种方式,都能实现同样的功能,它们最终都会创建一个独立的Element节点,不同的是StatefulWidget会提供一个createState()的方法给开发者创建它可变的状态,然后开发者通过调用setState触发build函数重建

状态管理

StatefulWidget的状态应该被谁管理?Widget本身?父 Widget ?都会?还是另一个对象?答案是取决于实际情况!以下是管理状态的最常见的方法:

  • Widget 管理自己的状态
  • Widget 管理子 Widget 状态
  • 混合管理(父 Widget 和子 Widget 都管理状态)

Widget管理自己的状态

以自定义隐私条款类为例子,_AgreeTermsWidgetState类:

  • 管理AgreeTermsWidget的状态。
  • 定义_check变量来确定勾选隐私条款的布尔值。
  • 实现勾选点击onTap()函数,该函数在点击时更新_check,并调用setState()更新UI。
  • 实现widget的所有交互式行为。
  • 调用者不需要关心被调用者的UI状态,通过回调的形式把结果回传给调用者即可。
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

/// 同意隐私条款
class AgreeTermsWidget extends StatefulWidget {
  final Function(bool)? callBack;

  const AgreeTermsWidget({Key? key, this.callBack}) : super(key: key);

  @override
  State<StatefulWidget> createState() => _AgreeTermsWidgetState();
}

class _AgreeTermsWidgetState extends State<AgreeTermsWidget> {
  // 是否勾选
  bool _check = false;

  @override
  Widget build(BuildContext context) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        InkWell(
            child:
            Icon(_check ? Icons.check_box : Icons.check_box_outline_blank),
            onTap: () {
              setState(() {
                _check = !_check;
                widget.callBack?.call(_check);
              });
            }),
        const SizedBox(width: 5),
        Expanded(
            child: RichText(
                text: TextSpan(
                    text: '我已仔細閱讀並明瞭',
                    style: const TextStyle(color: Colors.grey),
                    children: <TextSpan>[
                      _highLightTextSpan('隱私權聲明', 'https://www.xxx.com/index'),
                      const TextSpan(
                        text: '、',
                        style: TextStyle(color: Colors.grey),
                      ),
                      _highLightTextSpan('服務條款', 'https://www.xxx.com/index'),
                      const TextSpan(
                        text: '、',
                        style: TextStyle(color: Colors.grey),
                      ),
                      _highLightTextSpan('免責聲明', 'https://www.xxx.com/index'),
                      const TextSpan(
                        text: '、',
                        style: TextStyle(color: Colors.grey),
                      ),
                      _highLightTextSpan('版權保護政策', 'https://www.xxx.com/index'),
                      const TextSpan(
                        text: '等所載內容及其意義,茲同意該等條款規定,並願遵守網站現今、嗣後規範的各種規則',
                        style: TextStyle(color: Colors.grey),
                      )
                    ]))),
      ],
    );
  }

  /// 高亮文本
  TextSpan _highLightTextSpan(String title, String jumpUrl) {
    final TapGestureRecognizer recognizer = TapGestureRecognizer();
    recognizer.onTap = () {
      // 点击实现
    };
    return TextSpan(
        text: '「$title」',
        style: const TextStyle(
            color: Colors.blue, decoration: TextDecoration.underline),
        recognizer: recognizer);
  }
}

Widget管理子Widget的状态

以自定义隐私条款类为例子,AgreeTermsWidget类:

  • 继承StatelessWidget类,自身无需管理状态,所有状态都由其父组件处理。
  • 当检测到点击时,它会通知父组件,父Widget接收到通知后,可以调用setState()更新UI。
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

/// 同意隐私条款
class AgreeTermsWidget extends StatelessWidget {
  final bool check;
  final Function(bool) callBack;

  const AgreeTermsWidget({Key? key, this.check = false, required this.callBack})
      : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        InkWell(
            child:
            Icon(check ? Icons.check_box : Icons.check_box_outline_blank),
            onTap: () {
              callBack.call(!check);
            }),
        const SizedBox(width: 5),
        Expanded(
            child: RichText(
                text: TextSpan(
                    text: '我已仔細閱讀並明瞭',
                    style: const TextStyle(color: Colors.grey),
                    children: <TextSpan>[
                      _highLightTextSpan('隱私權聲明', 'https://www.xxx.com/index'),
                      const TextSpan(
                        text: '、',
                        style: TextStyle(color: Colors.grey),
                      ),
                      _highLightTextSpan('服務條款', 'https://www.xxx.com/index'),
                      const TextSpan(
                        text: '、',
                        style: TextStyle(color: Colors.grey),
                      ),
                      _highLightTextSpan('免責聲明', 'https://www.xxx.com/index'),
                      const TextSpan(
                        text: '、',
                        style: TextStyle(color: Colors.grey),
                      ),
                      _highLightTextSpan('版權保護政策', 'https://www.xxx.com/index'),
                      const TextSpan(
                        text: '等所載內容及其意義,茲同意該等條款規定,並願遵守網站現今、嗣後規範的各種規則',
                        style: TextStyle(color: Colors.grey),
                      )
                    ]))),
      ],
    );
  }

  /// 高亮文本
  TextSpan _highLightTextSpan(String title, String jumpUrl) {
    final TapGestureRecognizer recognizer = TapGestureRecognizer();
    recognizer.onTap = () {
      // 点击实现
    };
    return TextSpan(
        text: '「$title」',
        style: const TextStyle(
            color: Colors.blue, decoration: TextDecoration.underline),
        recognizer: recognizer);
  }
}

Widget混合状态管理

以自定义隐私条款类为例子,AgreeTermsWidget类:

  • 继承StatefulWidget类,内部状态由自身管理,外部状态由父组件管理
  • 当检测到点击复选框时,它会通知父组件,父Widget接收到通知后,可以调用setState()更新UI。
  • 当点击具体隐私条款时,它会在内部去调用setState()更新UI,刷新隐私条款文案的字体颜色。
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';

/// 同意隐私条款
class AgreeTermsWidget extends StatefulWidget {
  final bool check;
  final Function(bool) callBack;

  const AgreeTermsWidget(
      {Key? key, required this.check, required this.callBack})
      : super(key: key);

  @override
  State<StatefulWidget> createState() => _AgreeTermsWidgetState();
}

class _AgreeTermsWidgetState extends State<AgreeTermsWidget> {
  @override
  Widget build(BuildContext context) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        InkWell(
            child: Icon(
                widget.check ? Icons.check_box : Icons.check_box_outline_blank),
            onTap: () {
              widget.callBack.call(!widget.check);
            }),
        const SizedBox(width: 5),
        Expanded(
            child: RichText(
                text: TextSpan(
                    text: '我已仔細閱讀並明瞭',
                    style: const TextStyle(color: Colors.grey),
                    children: <TextSpan>[
              _highLightTextSpan(0, '隱私權聲明', 'https://www.xxx.com/index'),
              const TextSpan(
                text: '、',
                style: TextStyle(color: Colors.grey),
              ),
              _highLightTextSpan(1, '服務條款', 'https://www.xxx.com/index'),
              const TextSpan(
                text: '、',
                style: TextStyle(color: Colors.grey),
              ),
              _highLightTextSpan(2, '免責聲明', 'https://www.xxx.com/index'),
              const TextSpan(
                text: '、',
                style: TextStyle(color: Colors.grey),
              ),
              _highLightTextSpan(3, '版權保護政策', 'https://www.xxx.com/index'),
              const TextSpan(
                text: '等所載內容及其意義,茲同意該等條款規定,並願遵守網站現今、嗣後規範的各種規則',
                style: TextStyle(color: Colors.grey),
              )
            ]))),
      ],
    );
  }

  int _index = -1;

  // 高亮颜色
  Color _color = Colors.blue;

  /// 更新颜色
  _updateColor(Color color) {
    setState(() {
      _color = color;
    });
  }

  /// 高亮文本
  TextSpan _highLightTextSpan(int index, String title, String jumpUrl) {
    final TapGestureRecognizer recognizer = TapGestureRecognizer();
    recognizer.onTapDown = (details) {
      _index = index;
      _updateColor(Colors.orange);
    };
    recognizer.onTapUp = (details) {
      _updateColor(Colors.blue);
    };
    recognizer.onTapCancel = () {
      _updateColor(Colors.blue);
    };
    recognizer.onTap = () {
      // 点击实现
    };
    return TextSpan(
        text: '「$title」',
        style: TextStyle(
            color: _index == index ? _color : Colors.blue,
            decoration: TextDecoration.underline),
        recognizer: recognizer);
  }
}

如何决定使用哪种管理方法?下面是官方给出的一些原则可以帮助你做决定:

  • 如果状态是用户数据,如复选框的选中状态、滑块的位置,则该状态最好由父 Widget 管理。
  • 如果状态是有关界面外观效果的,例如颜色、动画,那么状态最好由 Widget 本身来管理。
  • 如果某一个状态是不同 Widget 共享的则最好由它们共同的父 Widget 管理。

局部状态管理

Flutter局部状态管理是通过注册Element依赖实现,为此构造了InheritedWidget和InheritedModel来实现数据状态管理。InheritedWidget中内部包括了一个泛型的data类型,用于接受数据,它本身是一个StatefulWidget,用于包装child并为child提供数据,这样InheritedWidget下所有的子节点就能访问它的data。

InheritedWidget

继承自widget类,它提供了一种在 widget 树中从上到下共享数据的方式,我们可以自己实现一个of方法,这样方便调用,要从构建上下文中获取特定类型的继承小部件的最近实例,请使用:BuildContext.dependOnInheritedWidgetOfExactType

import 'package:flutter/material.dart';

// 一个通用的InheritedWidget,保存需要跨组件共享的状态
class InheritedProvider<T> extends InheritedWidget {
  const InheritedProvider({
    super.key,
    required this.data,
    required Widget child,
  }) : super(child: child);

  final T data;

  // 自定义of方法,方便子树中的widget获取共享数据
  static InheritedProvider? of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<InheritedProvider>();
  }

  @override
  bool updateShouldNotify(InheritedProvider<T> oldWidget) {
    // 数据发生变化时,返回true,表示通知子树中依赖data的Widget重新build
    return oldWidget.data != data;
  }
}

setState

Flutter/Dart内置支持setState,但是它的作用对象必须是一个有状态的组件,当调用setState后,会重新触发build函数,所在的Widget就会进行重新渲染,这样就实现了页面更新

Global Key通信

实现Global Key通信,跨Widget访问状态,进行内部自身刷新

  GlobalKey key = GlobalKey();
  key: key,
  key.currentState.();

ValueListenableBuilder

ValueNotifier包含一个 T value 值,当值改变的时候,会通知它的监听来刷新UI

class _CountPageState extends State<CountPage> {
  final ValueNotifier<int> _counter = ValueNotifier<int>(0);

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ValueListenableBuilder<int>(
            valueListenable: _counter,
            builder: (BuildContext context, int value, Widget? child) {
              return Text('$value');
            }),
        TextButton(
            onPressed: () {
              _counter.value++;
            },
            child: const Text('计数'))
      ],
    );
  }

  @override
  void dispose() {
    super.dispose();
    _counter.dispose();
  }
}

StreamBuilder

StreamBuilder也是官方内置的一种刷新UI方式,数据封装成流通知UI变更

class _CountPageState extends State<CountPage> {
  int _count = 0;
  final StreamController<int> _controller = StreamController<int>();

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        StreamBuilder<int>(
            stream: _controller.stream,
            initialData: 0,
            builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
              return Text('${snapshot.data}');
            }),
        TextButton(
            onPressed: () {
              _count++;
              _controller.add(_count);
            },
            child: const Text('计数'))
      ],
    );
  }

  @override
  void dispose() {
    super.dispose();
    _controller.close();
  }
}

FutureBuilder

通常用于异步编程

class _CountPageState extends State<CountPage> {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        FutureBuilder(
          future: _load(),
          builder: (BuildContext context, AsyncSnapshot snapshot) {
            String data = '加载中...';
            if (snapshot.connectionState == ConnectionState.done) {
              // 请求完成
              if (!snapshot.hasError) {
                // 请求成功
                if (null != snapshot.data) {
                  data = snapshot.data.toString();
                }
              }
            } else {
              // 请求还未结束
            }
            return Text(data);
          },
        ),
      ],
    );
  }

  Future _load() async {
    await Future.delayed(const Duration(milliseconds: 2 * 1000));
    return '测试数据';
  }

  @override
  void dispose() {
    super.dispose();
  }
}

全局状态管理

当应用中需要一些跨组件(包括跨路由)的状态需要同步时,上面介绍的方法便很难胜任了。例如我们要实现中英文版切换/字体大小切/主题切换等全局功能,我们就需要全局状态管理处理组件之间的通信。

scoped_model

Scoped model内部使用了InheritedWidget 来共享状态,它可以将数据模型从父Widget传递到其他后代,在顶层使用ScopedModel进行包裹,在子页面获取Model进行数据渲染。

它有两种方式可以找到ScopedModel:

  • 使用ScopedModelDescendant小部件
  • 使用ScopedModel.of<>(context, rebuildOnChange)静态方法

注意:rebuildOnChange属性能够控制当该状态发生变化时,是否rebuild,作用等同于setState

provider

对 InheritedWidget 组件的上层封装,使其更易用,更易复用。

使用 provider 而非手动书写 InheritedWidget,有以下的优势:

  • 简化的资源分配与处置
  • 懒加载
  • 创建新类时减少大量的模板代码
  • 支持 DevTools
  • 更通用的调用 InheritedWidget 的方式(参考 Provider.of/Consumer/Selector)
  • 提升类的可扩展性,整体的监听架构时间复杂度以指数级增长(如 ChangeNotifier, 其复杂度为 O(N))

注意:在onPressed、OnTap、onLongPressed等事件处理程序上,我们必须使用Provider.of<T>(context,listen:false) ,它们只负责响应事件,而非更新展示,像Text等小部件会负责UI的展示。所以当不需要模型中的数据来改变UI,但是还是需要访问数据,我们需要将listen 设置为 false,默认值为true。

bloc

BLoC是一种设计模式,利用流的方式实现界面的异步渲染和重绘,这种设计模式有助于将表示与业务逻辑分开,遵循 BLoC 模式有助于提高可测试性和可重用性。这个包抽象了模式的反应方面,允许开发人员专注于编写业务逻辑。BLoC只是一种设计模式,不包含任何代码,所以要在Flutter中实现BLoC设计模式需要借助flutter_bloc这个库来完成

MobX

MobX 是一个状态管理库,可以轻松地将应用程序的反应数据与 UI 连接起来。这种接线是完全自动的,感觉非常自然。作为应用程序开发人员,您完全关注需要在 UI(和其他地方)中使用哪些反应数据,而不用担心保持两者同步。

它并不是真正的魔法,但它确实在消耗的内容(可观察对象)和消耗的位置(反应)方面具有一些智能,并会自动为您跟踪它。当可观察量 发生变化时,所有反应都会重新运行。有趣的是,这些反应可以是任何东西,从简单的控制台日志、网络调用到重新呈现 UI

flutter_redux

说到Redux 前端小伙伴一定都不陌生,Redux在React/VUE中与在Flutter/Dart中概念一样,只是使用上的不同。

它主要由三部分组成:

  • Store: 它是整个数据的仓库,存储State对象,管理着整个应用的状态
  • Reducer:处理与分发事件的方法,通过返回新的State来更新Store
  • Action: 行为(也可以理解为事件),action将会分发至对应的reducer中

get

GetX 是一个超轻且强大的 Flutter 解决方案。它结合了高性能状态管理、智能依赖注入和路由管理,快速实用。GetX 有 3 个基本原则:

  • 性能:GetX 专注于性能和最少的资源消耗。GetX 不使用 Streams 或 ChangeNotifier。
  • 生产力:GetX 使用简单而愉快的语法。无论您想做什么,GetX 总有更简单的方法。它将节省开发时间,并提供您的应用程序可以提供的最大性能。
  • 组织: GetX 允许视图、表示逻辑、业务逻辑、依赖注入和导航的完全解耦。

状态 (State) 管理参考

总结

Flutter/Dart内置支持setState,在不需要组件之间数据共享的情况下复杂度较低的组件内部使用还是不错的选择,使用简单方便。而provider基于InheritedWidget实现,简洁易用,也是Flutter官方推荐的状态管理框架,选择状态管理框架,应该从程序解耦的原理、库的设计、实现、代码量等因素来选择合适的状态管理。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/51702.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【MySQL 读写分离】Sharding JDBC + Spring boot 实现数据库读写分离的登录 Demo

上篇文章我们搭建了 MySQL 数据库主从复制集群 MySQL 搭建主从复制集群~~~ 本篇文章我们利用搭建好的主从复制集群&#xff0c;使用 SpringBoot 结合 Sharding-JDBC 搭建一个小的 登录 Demo&#xff0c;测试实现数据库的读写分离 项目源码地址&#xff1a; https://gitee.com/l…

13 【操作mysql数据库】

13 【操作mysql数据库】 1.mysql 介绍 付费的商用数据库&#xff1a; Oracle&#xff0c;典型的高富帅&#xff1b;SQL Server&#xff0c;微软自家产品&#xff0c;Windows定制专款&#xff1b;DB2&#xff0c;IBM的产品&#xff0c;听起来挺高端&#xff1b;Sybase&#x…

android WebRtc 视频通话(P2P)

概述 WebRTC名称源自网页实时通信(Web Real-Time Communication)的缩写&#xff0c;是一个支持网页浏览器进行实时语音对话或视频对话的技术&#xff0c;是谷歌2010年以6820万美元收购Global IP Solutions公司而获得的一项技术。Google于2011年6月3日开源的即时通讯项目&#x…

centos7中mysql5.7.32服务离线升级到5.7.39教程

目录 一、导入新的离线安装包 二、备份原有mysql数据库 1、停止tomcat服务 2、查看mysql服务 3、备份数据库 三、停止mysql服务并打包备份旧版本 1、停止mysql 2、打包旧的mysql文件夹 3、删除旧的mysql文件夹 4、删除/etc/init.d/下跟mysql有关的全部文件&#xff0…

MongoDB数据迁移之迁移工具Kettle

MongoDB数据迁移之迁移工具Kettle ETL:简介 ETL&#xff08;Extract-Transform-Load的缩写&#xff0c;即数据抽取、转换、装载的过程&#xff09;&#xff0c;对于企业或行业应用来说&#xff0c;我们经常会遇到各种数据的处理&#xff0c;转换&#xff0c;迁移&#xff0c;所…

Java+JSP+MySQL基于SSM的医院挂号就诊系统-计算机毕业设计

项目介绍 随着计算机科技的快速发展&#xff0c;很多地方都实现了自动化管理&#xff0c;医院也不例外。在大多数医院&#xff0c;无论是挂号处&#xff0c;还是取药的窗口&#xff0c;都会看到有很长的队伍&#xff0c;很显然这样会让患者就医的过程中浪费太多的时间。其次&a…

【读论文】GANMcC

GANMcC简单介绍网络结构生成器辨别器损失函数生成器损失函数辨别器tips总结参考论文&#xff1a;https://ieeexplore.ieee.org/document/9274337 如有侵权请联系博主 这几天又读了一篇关于GAN实现红外融合的论文&#xff0c;不出意外&#xff0c;还是FusionGAN作者团队的人写…

Python语音合成小工具(PyQt5 + pyttsx3)

TTS简介 TTS&#xff08;Text To Speech&#xff09;是一种语音合成技术&#xff0c;可以让机器将输入文本以语音的方式播放出来&#xff0c;实现机器说话的效果。 TTS分成语音处理及语音合成&#xff0c;先由机器识别输入的文字&#xff0c;再根据语音库进行语音合成。现在有…

JavaScript -- 三种循环语句的介绍及示例代码

文章目录循环语句1 While循环2 do-while循环3 for循环4 嵌套循环循环语句 通过循环语句可以使指定的代码反复执行 JS中一共有三种循环语句 while语句do-while语句for语句 通常编写一个循环&#xff0c;要有三个要件 初始化表达式&#xff08;初始化变量&#xff09;条件表…

风云气象卫星系列介绍

风云气象卫星系列是中国于1977年开始研制的气象卫星系列&#xff0c;目前发射了风云一号、风云二号、风云三号、风云四号等卫星。 风云一号 FY-1卫星分为两个批次&#xff0c;各两颗星。01批的FY-1A星于1988年7月9日发射&#xff0c;FY-1B星于1990年9月3日发射。02批卫星在01批…

Word处理控件Aspose.Words功能演示:在 Java 中将 Word 文档转换为 EPUB

大多数智能设备&#xff0c;如智能手机、平板电脑、笔记本电脑等&#xff0c;都支持EPUB格式来查看或阅读文档。它是电子书或电子出版物的常用格式。另一方面&#xff0c;MS Word 格式&#xff0c;如DOCX、DOC等&#xff0c;是数字世界中广泛使用的文档格式之一。在本文中&…

Web3中文|NFT无法保障数字所有权?

来源 | nftnow 编译 | DaliiNFTnews.com 2021年&#xff0c;有这样一个头条新闻&#xff1a;一家投资公司以大约400万美元的价格在The Sandbox上买下了2000英亩的虚拟地产。 通过在以太坊区块链上购买792个NFT&#xff0c;该公司得到了元宇宙平台上的1200个城市街区。 但是…

家用宽带如何叠加多条宽带,提高局域网速度

前言 关于多条宽带如何合并&#xff0c;使局域网内带宽更快&#xff1f;通常我们在企业网络或实际项目中&#xff0c;随着用户的增加&#xff0c;一条或者几条带宽不能满足正常使用&#xff0c;便可以对带宽进行叠加&#xff0c;便于网络带度更快&#xff1b; 一、为什么要用…

web基础阶段的小兔鲜儿项目学习

小兔鲜儿1. 所用素材2. 项目文件介绍3. index页面的基本骨架4. 思路&#xff1a;先写外面大盒子和版心&#xff0c;由外往内写5. 源码&#xff1a;1. 所用素材 素材链接&#xff0c;点我跳转&#xff1a;https://download.csdn.net/download/angrynouse/87228151 2. 项目文件…

全国产!全志T3+Logos FPGA核心板(4核ARM Cortex-A7)规格书

核心板简介 创龙科技SOM-TLT3F是一款基于全志科技T3四核ARM Cortex-A7处理器 + 紫光同创Logos PGL25G/PGL50G FPGA设计的异构多核全国产工业核心板,ARM Cortex-A7处理单元主频高达1.2GHz。核心板CPU、FPGA、ROM、RAM、电源、晶振、连接器等所有器件均采用国产工业级方案,国产…

【HDU No. 4902】 数据结构难题 Nice boat

【HDU No. 4902】 数据结构难题 Nice boat 杭电OJ 题目地址 【题意】 有n 个数字a 1 , a 2 , …, an &#xff0c;每次都可以将[l , r ]区间的每个数字都更改为数字x &#xff08;类型1&#xff09;&#xff0c;或将[l ,r ]区间每个大于x 的ai 都更改为最大公约数gcd(ai , x …

云服务连续三年增长150%,网宿科技开拓新赛道

摘要&#xff1a;开拓云服务市场&#xff0c;网宿科技的打法。 提到网宿科技&#xff0c;很多人还停留在传统IT服务商的印象中。其实&#xff0c;网宿科技已经在一条新赛道加速前行&#xff0c;这就是云服务。 “借助亚马逊云科技的持续赋能&#xff0c;网宿科技积累了丰富的云…

swiper轮播图片+视频播放,预览及页面跳转功能

1.效果 2.上代码 <template> <swiper :circulartrue indicator-dots"true" change"changeSwiper" :autoplay"true" interval5000 classswiper-view><swiper-item class"swiper-img" v-for"(item,index) in swipe…

2022 CMU15-445 Project0 Trie

通过截图 在线测试 本地测试 总览 代码风格 我们的代码必须遵循 Google C Style Guide。在线检测网站使用 Clang 自动检查源代码的质量。如果我们的提交未通过任何这些检查&#xff0c;您的项目成绩将为零。 对于 Google C Style Guide &#xff0c;我们可以看这里 google-s…

Spring Boot Logback启动流程

Spring Boot 默认使用的是 Logback 的日志框架、Logback 的组件主要通过 Spring Boot ApplicationListener 启动的 // LoggingApplicationListener Override public void onApplicationEvent(ApplicationEvent event) {if (event instanceof ApplicationStartingEvent) {onApp…