Flutter笔记:状态提升、控制器模式、GetX控制器和服务

Flutter笔记
状态提升、控制器模式、GetX控制器和服务

作者李俊才 (jcLee95)https://blog.csdn.net/qq_28550263
邮箱 :291148484@163.com
本文地址https://blog.csdn.net/qq_28550263/article/details/134350949


【简介】本文聊一聊状态提升、控制器模式,GetX简单状态管理与响应式状态管理、GetX服务的相关思想和使用。

在这里插入图片描述


1. 概述

在Flutter中,状态管理是一个重要的主题,它涉及到如何存储和更新应用的数据以及如何在组件之间共享数据。状态管理的方法有很多种,包括状态提升、控制器模式、响应式编程等。每种方法都有其优点和适用场景。

状态提升是一种简单的状态管理方法,它通过将状态放在组件树的上层来实现状态的共享。但是,状态提升可能会导致组件树过于复杂,而且不适用于全局状态的管理。

控制器模式是一种更加灵活的状态管理方法,它通过将状态封装在控制器对象中,然后通过控制器来管理状态。控制器模式可以有效地管理全局状态,而且可以避免组件树过于复杂。

响应式编程是一种基于数据流的编程模式,它可以使状态的更新变得更加直观和易于理解。在响应式编程中,状态被视为数据流,组件可以监听数据流的变化并根据变化来更新自己。

GetX库提供了一种简单而强大的状态管理方法,它结合了控制器模式和响应式编程的优点。在GetX中,你可以创建一个继承自GetxController或GetxService的类来保存状态,然后在状态改变时调用update()方法或者使用.obs来通知所有监听这个状态的组件。

接下来,本文具体聊一聊状态提升、控制器模式,GetX简单状态管理与响应式状态管理、GetX服务的相关思想和使用。

2. 状态提升模式

2.1 状态提升的基本概念

状态提升(State Lifting) 是一种在 Flutter 中常用的状态管理模式,其基本思想是将状态放在需要这个状态的最小公共祖先组件上。这样,所有需要这个状态的子组件都可以通过祖先组件来访问和修改这个状态。例如,如果两个兄弟组件都需要访问和修改同一个状态,那么这个状态就应该放在它们的父组件上。

详细说来,这种模式通常用于处理以下情况:

  • 共享数据: 当多个组件需要访问和共享相同的数据时,将状态提升到这些组件的共同祖先组件中,以便它们可以共享数据。

  • 状态同步: 当某个状态需要被多个组件 修改 时,将这个状态提升到共同的父组件,由父组件负责管理和更新状态,然后将状态传递给子组件。

2.2 状态提升的缺陷

虽然状态提升模式在一些简单的场景下工作得很好,但是它也有一些缺陷。

由于每个组件都可以有自己的 内部状态(即局部状态),但当多个组件之间需要共享状态或协同工作时,状态提升就变得非常有用。但是在很多实际开发场景中并不是说你想提升状态就可以提升状态。显而易见的是,如果我们封装一个第三方组件库,不可能在组件发布后去库的使用者的代码里提升状态,但是使用者又有可能需要用到这些状态来控制我们所封装的组件,因此这种情况下状态提升并不是可行的解决方案。

另外一个方面,状态提升在 Flutter 中将很容易导致扩大刷新范围,浪费性能。因此需要一种有效的解决方案来弥补状态提升的不足。这个解决方案就是所谓的 控制器模式。

3. 控制器模式与状态管理

3.1 ChangeNotifier 与 ListerableBuilder(改变通知机制)

顾名思义,ChangeNotifier(改变通知) 可以在状态改变时通知其监听器,是一个可以混入到类中的类。你可以创建一个继承自 ChangeNotifier 的类来保存状态,然后在状态改变时调用 notifyListeners() 方法来通知所有监听这个状态的组件

其中需要指出的是,ChangeNotifier 实现了 Listenable 接口,用于提供一个可以发送变化通知的对象:

  • Listenable 接口定义了两个方法:addListener()removeListener(),这两个方法分别用于添加和移除监听器。任何实现了 Listenable 接口的对象都可以被其他对象监听,当 Listenable 对象的状态发生变化时,它可以通知所有的监听器。(实际上是 发布-订阅模式-见《发布订阅模式原理及其应用》,地址:https://jclee95.blog.csdn.net/article/details/129930814

  • ChangeNotifierListenable 的一个具体实现,它提供了一个 notifyListeners() 方法,可以在状态改变时调用,以通知所有的监听器。ChangeNotifier 内部维护了一个监听器列表,当你调用 addListener() 方法时,监听器会被添加到这个列表中;当你调用removeListener() 方法时,监听器会从这个列表中移除。

例如,你可以创建一个 Counter 类,它继承自 ChangeNotifier,并有一个 count 状态和一个 increment 方法:

class Counter with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

然后,你可以使用 ListenableBuilder监听 这个 Counter 对象。ListenableBuilder 函数会在每次 ChangeNotifier 调用 notifyListeners() 以实现 Counter 对象的 状态改变 时被调用。

class CounterWidget extends StatelessWidget {
  final Counter counter;

  CounterWidget({ this.counter});

  
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: counter,
      builder: (context, _) {
        return Text('Count: ${counter.count}');
      },
    );
  }
}

其中:

  1. 当你创建一个 ListenableBuilder 并传入一个 Listenable 对象(ChangeNotifierListenable 的实现)时,ListenableBuilder 会将自己添加到 Listenable 的监听器列表中。

  2. Listenable 对象的状态改变并调用 notifyListeners() 方法时,所有的监听器(包括ListenableBuilder)都会收到通知。

  3. ListenableBuilder 收到通知时,它会调用其builder函数来重建子组件。builder函数会接收到当前的 BuildContextListenable 对象,以及一个可选的 child 参数,然后返回一个新的 Widget

  4. ListenableBuilder 会将 builder 函数返回的 新 Widget 显示在屏幕上,从而更新UI的效果。

功能上 ListenableBuilderAnimationBuilder 是一样的。

因此在封装组件时,经常使用控制器来命名这个基于 改变通知(发布订阅) 的类:

/// 计数器控制器类
/// 
/// - 存储计数器状态;
/// - 提供改变状态的方法作为外部改变状态的接口。
class CounterController with ChangeNotifier {
  int _count = 0;
  int get count => _count;

  void increment() {
    _count++;
    notifyListeners();
  }
}

3.2 控制器模式是如何工作的

控制器模式是一种更加灵活的状态管理模式。在这种模式下,状态被保存在一个或多个控制器对象中,而不是直接保存在组件中。组件可以创建和管理这些控制器对象,也可以通过它们来访问和修改状态。

当状态改变时,控制器会通知所有监听这个状态的组件,这样这些组件就可以根据新的状态来更新自己。因为状态被保存在控制器中,所以它可以被任何可以访问到这个控制器的组件共享,这使得状态管理变得更加灵活和高效。

状态提升时,我们仅仅时把状态放在了层级更高的组件,但是控制器模式将状态放在一个独立的类中,这个类不仅用于存储状态,也提供相应的改变方法。实际上,上一节混入了 ChangeNotifierCounter 类就是一个控制器。

为什么控制器要混入或继承于ChangeNotifier?

因为我们的目标是状态改变后能够及时的更新UI。

Flutter 中,提供 ChangeNotifier - ListenableBuilder 机制:

前者用于控制器类——因为控制器类是状态改变的源:

  • 所有改变状态变量的操作被封装在控制器类中,以接口的形式暴露给外部使用;
  • 在修改的接口方法中,每当数据更改数据后调用 notifyListeners()方法完成通知监听器。

后者用于 build 方法的某个局部需要依赖于数据更新的UI中:

  • ListenableBuilder 的 builder 方法在监听器被通知后使用新的数据进行重构;

4. GetxController 与控制器模式

4.1 简单状态管理

GetX 库中,你可以创建一个继承自 GetxController 的类来保存状态,然后在状态改变时调用 update() 方法来通知所有监听这个状态的组件。

class CounterController extends GetxController {
  int count = 0;

  void increment() {
    count++;
    update();
  }
}

然后,你可以在UI类中使用 GetBuilder 来监听这个 CounterController 对象。当 CounterController 对象的状态改变时,GetBuilder 会自动重建,从而更新UI。

class CounterWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final counterController = Get.put(CounterController());

    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Center(
        child: GetBuilder<CounterController>(
          builder: (controller) => Text('Count: ${controller.count}'),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: counterController.increment,
      ),
    );
  }
}

仅仅从使用的角度上看:

  1. 在控制器类中:
  • 从继承于 ChangeNotifier 变成了继承于 GetxController
  • 从使用 notifyListeners(); 函数通知更新UI变成了 使用 update(); 函数更新UI。
  1. 在 UI 的build函数的需要更新处:
  • 从使用 ListenableBuilder 类包括需要依赖数据更新部分改成了使用 GetBuilder 类。

4.2 响应式状态管理

GetX的响应式状态管理提供了一种用起来更加方便地方式——不再需要使用 update() 方法。但是这意味着需要在每一个变量上做些手脚——添加.obs 变成“响应式变量”。

在GetX库内部,.obs 是一个扩展方法,它可以用于将普通的Dart值(如String、int、double等)转换为可观察的 Rx 对象。——这是因为在 Dart 语言中,你可以通过 扩展(extension) 语法来为已有的类型添加新的方法或属性。GetX库就使用了这个特性,为Dart的基本类型添加了 .obs 扩展方法。

所以,当你在一个String、int、double等值后面调用.obs时,你实际上是在创建一个新的Rx对象,这个对象的初始值就是这个值。例如,var count = 0.obs;就等价于var count = Rx(0);。

Rx对象是可观察的,你可以使用value属性来获取或设置它的值,也可以使用addListener()方法来添加监听器。当Rx对象的值改变时,所有的监听器都会收到通知。这就是GetX的响应式状态管理的基础。

基于响应式转台管理,控制器类调整为:

// 控制器类
class CounterController extends GetxController {
  RxInt count = 0.obs;

  void increment() {
    count++;
  }
}

而在UI部分,也不再使用 ChangeNotifierListenableBuilder或者简单状态管理的GetBuilder ,而是由 Obx进行包裹,例如:

// 界面组件
class CounterView extends StatelessWidget {
  final CounterController controller = Get.put(CounterController());

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Counter App with GetX'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Obx(() => Text('Count: ${controller.count}')),
            ElevatedButton(
              onPressed: () => controller.increment(),
              child: Text('Increment'),
            ),
          ],
        ),
      ),
    );
  }
}

5. GetxService 与全局状态管理

实际上 GetxService 和 的功能是很像的,我们可以将控制器类改为一个服务类,比如计数器的例子:

class CounterService extends GetxService {
  var count = 0.obs;

  void increment() {
    count.value++;
  }
}

然后,你可以在UI类中使用Obx来监听这个CounterService对象。当CounterService对象的状态改变时,Obx会自动重建,从而更新UI:

class CounterWidget extends StatelessWidget {
  
  Widget build(BuildContext context) {
    final counterService = Get.put(CounterService());

    return Scaffold(
      appBar: AppBar(title: Text('Counter')),
      body: Center(
        child: Obx(() => Text('Count: ${counterService.count.value}')),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: counterService.increment,
      ),
    );
  }
}

从使用上看没有什么不同。但是GetxService是一个长生命周期的类,一旦被创建,就会一直存在,直到应用被关闭或者你手动调用Get.reset()。

6. GetxController 和 GetxService的比较

GetxControllerGetxService 都是 GetX 库中的核心组件,它们都有生命周期方法(onInit(), onReady(), onClose()),并且都可以用于 管理状态和依赖。但是,它们的主要区别在于它们的存活时间和用途不一样。

6.1 GetxController 的应用场景

GetxController 是一个用于状态管理的类,它的实例可以通过 Get.put()Get.lazyPut()Get.putAsync()Get.create() 等方法创建并绑定到一个生命周期。当与绑定的页面不再需要时, GetX会自动删除GetxController的实例 以释放内存。因此,GetxController通常用于页面和小部件的 局部状态管理,例如用户界面的交互、表单状态、主题颜色等。

6.2 GetxService 的应用场景

GetxService 是一个长期存活在应用中的类,它的实例一旦被创建,就不会被自动删除,除非你手动调用Get.reset()。因此,GetxService 通常用于需要全局访问和长期存在的服务,例如用户认证、数据库操作、网络请求等。

最常见的就是认证和权限,者往往是整个应用生命周期都需要的,因此我们经常定义各异认证服务,用于处理应用中与认证相关的状态:

class AuthService extends GetxService {
  Future<AuthService> init() async {
    // Initialize your class
  }
}

在GetX中,Get.reset()方法用于清除所有的依赖项,包括GetxController和GetxService的实例。这个方法通常在你需要完全重置应用状态时使用,例如用户注销登录时。

然而,如果你有一个场景需要重置CounterService的状态,例如用户注销登录时,你可以调用Get.reset()。例如:

void userLogout() {
  // ...其他的注销逻辑...

  // 重置应用状态
  Get.reset();
}

需要指出的是,Get.reset()会清除所有的依赖项,包括所有的GetxController和GetxService的实例。如果你只想重置CounterService的状态,你可以在CounterService中添加一个重置状态的方法,然后在需要的地方调用这个方法。比如:

void userLogout() {
  // ...其他的注销逻辑...

  // 重置CounterService的状态
  Get.find<CounterService>().reset();
}

F. 附录

F.1 Listenable

/// 一个维护监听器列表的对象。
///
/// 监听器通常用于通知客户端对象已经更新。
///
/// 这个接口有两个变体:
///
///  * [ValueListenable],一个增强了[Listenable]接口的接口,
///    增加了_当前值_的概念。
///
///  * [Animation],一个增强了[ValueListenable]接口的接口,
///    增加了方向(前进或后退)的概念。
///
/// Flutter API中的许多类使用或实现了这些接口。以下子类特别相关:
///
///  * [ChangeNotifier],可以被子类化或混入以创建实现[Listenable]接口的对象。
///
///  * [ValueNotifier],实现了[ValueListenable]接口,具有一个可变值,
///    当修改时触发通知。
///
/// "通知客户端","发送通知","触发通知",
/// 和"发出通知"这些术语可以互换使用。
///
/// 另请参阅:
///
///  * [AnimatedBuilder],一个在给定的[Listenable]触发通知时使用构建器回调重建的小部件。
///    这个小部件通常与[Animation]子类一起使用,因此得名,但绝不仅限于动画,
///    因为它可以与任何[Listenable]一起使用。它是[AnimatedWidget]的子类,
///    可以用来创建由[Listenable]驱动的小部件。
///  * [ValueListenableBuilder],一个在[ValueListenable]对象触发通知时使用构建器回调重建的小部件,
///    向构建器提供对象的值。
///  * [InheritedNotifier],一个抽象的超类,用于使用[Listenable]的通知触发在它们上声明依赖关系的后代小部件的重建,
///    使用[InheritedWidget]机制。
///  * [Listenable.merge],创建一个[Listenable],当任何一个给定的[Listenable]触发通知时触发。
abstract class Listenable {
  /// 抽象的const构造函数。这个构造函数使子类能够提供const构造函数,
  /// 以便它们可以在const表达式中使用。
  const Listenable();

  /// 返回一个[Listenable],当任何给定的[Listenable]自身触发时触发。
  ///
  /// 在调用此方法后,列表不能更改。这样做会导致内存泄漏或异常。
  ///
  /// 列表可能包含null;它们会被忽略。
  factory Listenable.merge(List<Listenable?> listenables) = _MergingListenable;

  /// 注册一个闭包,当对象通知其监听器时调用。
  void addListener(VoidCallback listener);

  /// 从对象通知的闭包列表中删除一个先前注册的闭包。
  void removeListener(VoidCallback listener);
}

F.2 ValueListenable

/// 为[Listenable]的子类提供的接口,该接口暴露一个[value]。
///
/// 这个接口由[ValueNotifier<T>]和[Animation<T>]实现,并且
/// 允许其他API交替接受这两种实现。
///
/// 另请参见:
///
///  * [ValueListenableBuilder],一个使用构建器回调的小部件,
///    每当[ValueListenable]对象触发其通知时重建,
///    并为构建器提供对象的值。
abstract class ValueListenable<T> extends Listenable {
  /// 抽象的const构造函数。这个构造函数使得子类可以提供
  /// const构造函数,以便它们可以在const表达式中使用。
  const ValueListenable();

  /// 对象的当前值。当值发生变化时,将调用
  /// 使用[addListener]注册的回调。
  T get value;
}

F.3 ChangeNotifier

/// 一个可以被扩展或混入的类,它提供了一个使用[VoidCallback]进行通知的更改通知API。
///
/// 添加监听器的时间复杂度为O(1),移除监听器和分发通知的时间复杂度为O(N)(其中N为监听器的数量)。
///
/// ## 使用ChangeNotifier子类作为数据模型
///
/// 一个数据结构可以扩展或混入[ChangeNotifier]来实现[Listenable]接口,从而可以与监听[Listenable]更改的小部件一起使用,例如[ListenableBuilder]。
///
/// {@tool dartpad}
/// 下面的示例实现了一个简单的计数器,它使用[ListenableBuilder]来限制只有包含计数的[Text]小部件重建。当前计数存储在[ChangeNotifier]子类中,当其值更改时,它会重建[ListenableBuilder]的内容。
///
/// ** 参见 examples/api/lib/widgets/transitions/listenable_builder.2.dart 中的代码 **
/// {@end-tool}
///
/// {@tool dartpad}
/// 在这种情况下,[ChangeNotifier]子类封装了一个列表,并在列表中添加任何项目时通知客户端。此示例仅支持添加项目;作为练习,考虑添加从列表中删除项目的按钮。
///
/// ** 参见 examples/api/lib/widgets/transitions/listenable_builder.3.dart 中的代码 **
/// {@end-tool}
///
/// 另请参见:
///
///  * [ValueNotifier],它是一个包装单个值的[ChangeNotifier]。
mixin class ChangeNotifier implements Listenable {
  int _count = 0;
  // _listeners故意设置为固定长度的_GrowableList而不是const []。
  //
  // const []会创建一个_ImmutableList的实例,这与此类中其他地方使用的固定长度_GrowableList不同。
  // 在此类的生命周期中保持运行时类型相同,可以让编译器为此属性推断具体类型,从而提高性能。
  static final List<VoidCallback?> _emptyListeners = List<VoidCallback?>.filled(0, null);
  List<VoidCallback?> _listeners = _emptyListeners;
  int _notificationCallStackDepth = 0;
  int _reentrantlyRemovedListeners = 0;
  bool _debugDisposed = false;

  /// 如果为true,那么此实例的[ObjectCreated]事件已经被分派到[MemoryAllocations]。
  ///
  /// 由于[ChangedNotifier]被用作mixin,它没有构造函数,
  /// 所以我们使用[addListener]来分派事件。
  bool _creationDispatched = false;

  /// 由子类使用来断言[ChangeNotifier]尚未被处理。
  ///
  /// {@tool snippet}
  /// [debugAssertNotDisposed]函数应该只在断言中被调用,如下例所示。
  ///
  /// ```dart
  /// class MyNotifier with ChangeNotifier {
  ///   void doUpdate() {
  ///     assert(ChangeNotifier.debugAssertNotDisposed(this));
  ///     // ...
  ///   }
  /// }
  /// ```
  /// {@end-tool}
  // 这是静态的而不是实例方法,因为太多的人试图
  // 实现ChangeNotifier而不是扩展它(所以添加一个方法,特别是对于debug,是太破坏性的)。
  static bool debugAssertNotDisposed(ChangeNotifier notifier) {
    assert(() {
      if (notifier._debugDisposed) {
        throw FlutterError(
          'A ${notifier.runtimeType} was used after being disposed.\n'
          'Once you have called dispose() on a ${notifier.runtimeType}, it '
          'can no longer be used.',
        );
      }
      return true;
    }());
    return true;
  }

  /// 是否有任何监听器当前已注册。
  ///
  /// 客户端不应依赖此值来决定他们的行为,因为当一个监听器的逻辑在另一个监听器开始或停止
  /// 监听时发生变化,将导致极其难以追踪的错误。然而,子类可能会使用这个信息来决定当没有
  /// 监听器时是否做任何工作;例如,当添加一个监听器时恢复一个[Stream],当一个监听器被移除时暂停它。
  ///
  /// 通常,这是通过覆盖[addListener],在调用`super.addListener()`之前检查
  /// [hasListeners]是否为false,如果是,那么开始确定何时调用
  /// [notifyListeners]所需的任何工作;同样,通过覆盖[removeListener],在调用
  /// `super.removeListener()`之后检查[hasListeners]是否为false,如果是,
  /// 那么停止同样的工作。
  ///
  /// 如果调用了[dispose],此方法返回false。
  
  bool get hasListeners => _count > 0;

  /// 将[object]创建的事件分派到[MemoryAllocations.instance]。
  ///
  /// 如果事件已经被分派或[kFlutterMemoryAllocationsEnabled]
  /// 为false,该方法是无操作的。
  ///
  /// 像leak_tracker这样的工具使用对象创建的事件来帮助
  /// 开发者识别对象的所有者,用于故障排除,通过在事件发生时获取堆栈跟踪。
  ///
  /// 但是,由于[ChangeNotifier]是mixin,它没有自己的构造函数。所以,它在第一次`addListener`中通知对象创建,这导致堆栈跟踪指向`addListener`,而不是构造函数。
  ///
  /// 为了使调试更容易,在类的构造函数中调用[ChangeNotifier.maybeDispatchObjectCreation]。
  /// 它将帮助
  /// 识别所有者。
  ///
  /// 确保以条件`if (kFlutterMemoryAllocationsEnabled) ...`
  /// 调用它,以便在标志为false时将该方法摇树剪掉。
  
  static void maybeDispatchObjectCreation(ChangeNotifier object) {
    // 如果kFlutterMemoryAllocationsEnabled为false,树摇器不会包含此方法和MemoryAllocations类。
    if (kFlutterMemoryAllocationsEnabled && !object._creationDispatched) {
      MemoryAllocations.instance.dispatchObjectCreated(
        library: _flutterFoundationLibrary,
        className: '$ChangeNotifier',
        object: object,
      );
      object._creationDispatched = true;
    }
  }
  /// 注册一个闭包,当对象改变时调用。
  ///
  /// 如果给定的闭包已经注册,将添加一个额外的实例,
  /// 并且必须移除添加的次数相同的次数,才会停止调用。
  ///
  /// 在调用[dispose]之后,不得调用此方法。
  ///
  /// {@template flutter.foundation.ChangeNotifier.addListener}
  /// 如果一个监听器被添加两次,并且在迭代期间被移除一次
  /// (例如,响应通知),它仍然会被再次调用。如果,
  /// 另一方面,它被移除的次数与它被注册的次数相同,那么
  /// 它将不再被调用。这种奇怪的行为是[ChangeNotifier]无法确定
  /// 哪个监听器被移除的结果,因为它们是相同的,因此它会保守地仍然
  /// 在知道任何监听器仍然被注册时调用所有的监听器。
  ///
  /// 当在两个单独的对象上注册一个监听器,这两个对象都将所有
  /// 注册转发到一个公共的上游对象时,可以意外地观察到这种惊人的行为。
  /// {@endtemplate}
  ///
  /// 另请参见:
  ///
  ///  * [removeListener],它从列表中移除一个先前注册的闭包,
  ///    当对象改变时,这些闭包会被通知。
  
  void addListener(VoidCallback listener) {
    assert(ChangeNotifier.debugAssertNotDisposed(this));

    if (kFlutterMemoryAllocationsEnabled) {
      maybeDispatchObjectCreation(this);
    }

    if (_count == _listeners.length) {
      if (_count == 0) {
        _listeners = List<VoidCallback?>.filled(1, null);
      } else {
        final List<VoidCallback?> newListeners =
            List<VoidCallback?>.filled(_listeners.length * 2, null);
        for (int i = 0; i < _count; i++) {
          newListeners[i] = _listeners[i];
        }
        _listeners = newListeners;
      }
    }
    _listeners[_count++] = listener;
  }

  void _removeAt(int index) {
    // 由于性能原因,持有监听器的列表不是可增长的。
    // 我们仍然希望在添加了很多监听器然后在notifyListeners迭代之外移除它们时缩小这个列表。
    // 我们只在监听器的实际数量是我们列表长度的一半时这样做。
    _count -= 1;
    if (_count * 2 <= _listeners.length) {
      final List<VoidCallback?> newListeners = List<VoidCallback?>.filled(_count, null);

      // 索引之前的监听器在同一位置。
      for (int i = 0; i < index; i++) {
        newListeners[i] = _listeners[i];
      }

      // 索引之后的监听器向列表的开始移动。
      for (int i = index; i < _count; i++) {
        newListeners[i] = _listeners[i + 1];
      }

      _listeners = newListeners;
    } else {
      // 当监听器的数量超过列表长度的一半时,我们只是
      // 移动我们的监听器,这样我们就可以避免为
      // 整个列表重新分配内存。
      for (int i = index; i < _count; i++) {
        _listeners[i] = _listeners[i + 1];
      }
      _listeners[_count] = null;
    }
  }

  /// 从列表中移除一个先前注册的闭包,当对象改变时,
  /// 这些闭包会被通知。
  ///
  /// 如果给定的监听器没有被注册,调用将被忽略。
  ///
  /// 如果调用了[dispose],此方法立即返回。
  ///
  /// {@macro flutter.foundation.ChangeNotifier.addListener}
  ///
  /// 另请参见:
  ///
  ///  * [addListener],它注册一个闭包,当对象
  ///    改变时调用。
  
  void removeListener(VoidCallback listener) {
    // 出于可用性原因,允许在处理实例后调用此方法。
    // 由于我们的帧调度逻辑在渲染对象和覆盖之间,
    // 这个实例的所有者通常会在监听器之前一帧被处理。
    // 允许在处理后调用此方法使监听器更容易正确清理。
    for (int i = 0; i < _count; i++) {
      final VoidCallback? listenerAtIndex = _listeners[i];
      if (listenerAtIndex == listener) {
        if (_notificationCallStackDepth > 0) {
          // 我们在notifyListeners迭代期间不调整列表的大小
          // 但是我们设置为null,我们想要移除的监听器。我们将
          // 在所有notifyListeners迭代结束后有效地调整列表的大小。
          _listeners[i] = null;
          _reentrantlyRemovedListeners++;
        } else {
          // 当我们在notifyListeners迭代之外时,我们可以
          // 有效地缩小列表。
          _removeAt(i);
        }
        break;
      }
    }
  }

  /// 丢弃对象使用的任何资源。在调用此方法后,对象
  /// 不处于可用状态,应该被丢弃(在对象被处理后调用
  /// [addListener]将抛出异常)。
  ///
  /// 此方法只应由对象的所有者调用。
  ///
  /// 此方法不会通知监听器,并在调用后清除监听器列表。
  /// 此类的消费者必须在处理前立即决定是否通知
  /// 监听器。
  
  void dispose() {
    assert(ChangeNotifier.debugAssertNotDisposed(this));
    assert(
      _notificationCallStackDepth == 0,
      '在调用"notifyListeners()"的过程中调用了"$this"的"dispose()"方法。'
      '这可能会导致错误,因为它在列表被使用的时候修改了监听器列表。',
    );
    assert(() {
      _debugDisposed = true;
      return true;
    }());
    if (kFlutterMemoryAllocationsEnabled && _creationDispatched) {
      MemoryAllocations.instance.dispatchObjectDisposed(object: this);
    }
    _listeners = _emptyListeners;
    _count = 0;
  }

  /// 调用所有注册的监听器。
  ///
  /// 每当对象改变时,调用此方法,以通知任何客户端
  /// 对象可能已经改变。在此迭代期间添加的监听器
  /// 将不会被访问。在此迭代期间被移除的监听器将
  /// 在被移除后不会被访问。
  ///
  /// 监听器抛出的异常将被捕获并使用
  /// [FlutterError.reportError]报告。
  ///
  /// 在调用[dispose]之后,不得调用此方法。
  ///
  /// 当重入地移除一个监听器(例如,
  /// 响应通知)已经被注册多次时,可能会出现意外的行为。
  /// 请参见[removeListener]的讨论。
  
  
  ('vm:notify-debugger-on-exception')
  void notifyListeners() {
    assert(ChangeNotifier.debugAssertNotDisposed(this));
    if (_count == 0) {
      return;
    }

    // 为了确保在此迭代期间被移除的监听器不被调用,
    // 我们将它们设置为null,但我们不会立即缩小列表。
    // 通过这样做,我们可以继续在我们的列表上迭代,直到它到达
    // 在调用此方法之前添加的最后一个监听器。

    // 为了允许潜在的监听器递归调用notifyListener,我们跟踪
    // 此方法被调用的次数在_notificationCallStackDepth中。
    // 一旦每个递归迭代完成(即,当_notificationCallStackDepth == 0时),
    // 我们可以安全地缩小我们的列表,以便它只包含不为null
    // 的监听器。

    _notificationCallStackDepth++;

    final int end = _count;
    for (int i = 0; i < end; i++) {
      try {
        _listeners[i]?.call();
      } catch (exception, stack) {
        FlutterError.reportError(FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'foundation library',
          context: ErrorDescription('while dispatching notifications for $runtimeType'),
          informationCollector: () => <DiagnosticsNode>[
            DiagnosticsProperty<ChangeNotifier>(
              'The $runtimeType sending notification was',
              this,
              style: DiagnosticsTreeStyle.errorProperty,
            ),
          ],
        ));
      }
    }

    _notificationCallStackDepth--;

    if (_notificationCallStackDepth == 0 && _reentrantlyRemovedListeners > 0) {
      // 当所有通知都完成时,我们真正地移除监听器。
      final int newLength = _count - _reentrantlyRemovedListeners;
      if (newLength * 2 <= _listeners.length) {
        // 如同_removeAt,我们只在真正的监听器数量是
        // 我们列表长度的一半时缩小列表。
        final List<VoidCallback?> newListeners = List<VoidCallback?>.filled(newLength, null);

        int newIndex = 0;
        for (int i = 0; i < _count; i++) {
          final VoidCallback? listener = _listeners[i];
          if (listener != null) {
            newListeners[newIndex++] = listener;
          }
        }

        _listeners = newListeners;
      } else {
        // 否则我们将所有的null引用放在最后。
        for (int i = 0; i < newLength; i += 1) {
          if (_listeners[i] == null) {
            // 我们将此项与下一个非null项交换。
            int swapIndex = i + 1;
            while (_listeners[swapIndex] == null) {
              swapIndex += 1;
            }
            _listeners[i] = _listeners[swapIndex];
            _listeners[swapIndex] = null;
          }
        }
      }

      _reentrantlyRemovedListeners = 0;
      _count = newLength;
    }
}

F.4 ValueNotifier

/// 一个持有单个值的[ChangeNotifier]。
///
/// 当[value]被替换为不等于旧值的东西时
/// 由等号运算符==评估,此类通知其监听器。
///
/// ## 限制
///
/// 因为这个类只在[value]的 _identity_ 改变时通知监听器,
/// 所以当值本身的可变状态改变时,监听器不会被通知。
///
/// 例如,当列表的 _内容_ 改变时。`ValueNotifier<List<int>>`不会通知其监听器
/// 
/// 因此,这个类最好只用于不可变的数据类型。
///
/// 对于可变的数据类型,考虑直接扩展[ChangeNotifier]。
class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
  /// 创建一个包装此值的[ChangeNotifier]。
  ValueNotifier(this._value) {
    if (kFlutterMemoryAllocationsEnabled) {
      ChangeNotifier.maybeDispatchObjectCreation(this);
    }
  }

  /// 此通知器中存储的当前值。
  ///
  /// 当值被替换为不等于旧值的东西时
  /// 由等号运算符==评估,此类通知其监听器。
  
  T get value => _value;
  T _value;
  set value(T newValue) {
    if (_value == newValue) {
      return;
    }
    _value = newValue;
    notifyListeners();
  }

  
  String toString() => '${describeIdentity(this)}($value)';
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,以下是一个简单的Flutter视频控制器的实现: ```dart import 'package:flutter/material.dart'; import 'package:video_player/video_player.dart'; class VideoPlayerController extends StatefulWidget { final String videoUrl; VideoPlayerController({required this.videoUrl}); @override _VideoPlayerControllerState createState() => _VideoPlayerControllerState(); } class _VideoPlayerControllerState extends State<VideoPlayerController> { late VideoPlayerController _videoPlayerController; late Future<void> _initializeVideoPlayerFuture; @override void initState() { super.initState(); // 创建一个VideoPlayerController对象 _videoPlayerController = VideoPlayerController.network(widget.videoUrl); // 初始化VideoPlayerController对象 _initializeVideoPlayerFuture = _videoPlayerController.initialize(); // 开始播放视频 _videoPlayerController.play(); } @override void dispose() { // 释放VideoPlayerController对象 _videoPlayerController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: FutureBuilder( future: _initializeVideoPlayerFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { return AspectRatio( aspectRatio: _videoPlayerController.value.aspectRatio, // 创建一个VideoPlayer对象 child: VideoPlayer(_videoPlayerController), ); } else { return Center( child: CircularProgressIndicator(), ); } }, ), bottomNavigationBar: BottomAppBar( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: Icon( _videoPlayerController.value.isPlaying ? Icons.pause : Icons.play_arrow, ), onPressed: () { setState(() { if (_videoPlayerController.value.isPlaying) { _videoPlayerController.pause(); } else { _videoPlayerController.play(); } }); }, ), IconButton( icon: Icon(Icons.fullscreen), onPressed: () { // 进入全屏模式 _videoPlayerController.pause(); Navigator.push( context, MaterialPageRoute( builder: (context) => FullScreenVideoPlayer( videoPlayerController: _videoPlayerController, ), ), ); }, ), IconButton( icon: Icon(Icons.volume_up), onPressed: () { setState(() { _videoPlayerController.setVolume(1.0); }); }, ), ], ), ), ); } } class FullScreenVideoPlayer extends StatefulWidget { final VideoPlayerController videoPlayerController; FullScreenVideoPlayer({required this.videoPlayerController}); @override _FullScreenVideoPlayerState createState() => _FullScreenVideoPlayerState(); } class _FullScreenVideoPlayerState extends State<FullScreenVideoPlayer> { late VideoPlayerController _videoPlayerController; late Future<void> _initializeVideoPlayerFuture; @override void initState() { super.initState(); // 初始化VideoPlayerController对象 _videoPlayerController = widget.videoPlayerController; _initializeVideoPlayerFuture = _videoPlayerController.initialize(); // 开始播放视频 _videoPlayerController.play(); } @override void dispose() { // 释放VideoPlayerController对象 _videoPlayerController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( body: FutureBuilder( future: _initializeVideoPlayerFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { return AspectRatio( aspectRatio: _videoPlayerController.value.aspectRatio, // 创建一个VideoPlayer对象 child: VideoPlayer(_videoPlayerController), ); } else { return Center( child: CircularProgressIndicator(), ); } }, ), bottomNavigationBar: BottomAppBar( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ IconButton( icon: Icon( _videoPlayerController.value.isPlaying ? Icons.pause : Icons.play_arrow, ), onPressed: () { setState(() { if (_videoPlayerController.value.isPlaying) { _videoPlayerController.pause(); } else { _videoPlayerController.play(); } }); }, ), IconButton( icon: Icon(Icons.fullscreen_exit), onPressed: () { // 退出全屏模式 _videoPlayerController.pause(); Navigator.pop(context); }, ), IconButton( icon: Icon(Icons.volume_up), onPressed: () { setState(() { _videoPlayerController.setVolume(1.0); }); }, ), ], ), ), ); } } ``` 以上代码创建了一个包含视频播放器的页面,并在底部添加了一些控制按钮。在底部导航栏中,有一个“全屏”按钮,可以让用户进入全屏模式观看视频。同时,该页面也为竖屏模式做了适配,保证用户在不同设备上都能获得良好的观看体验。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

jcLee95

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值