Flutter生命周期方法小技巧

简介: Flutter生命周期方法小技巧
需求
  • A界面跳转到B界面,暂停A界面的音乐或者视频
  • B界面返回到A界面,播放A界面的音乐或者视频
  • A界面切换到后台,暂停A界面的音乐或者视频
  • A界面从后台切换到前台,播放A界面的音乐或者视频

需求通过理解修改为:

  • 监听 StatefulWidget 的 onPause 方法
  • 监听 StatefulWidget 的 onResume 方法
背景

我们在使用Flutter开发界面时,很多时候是需要在当前界面切换到后台,或者说当前界面跳转到第二个页面时,需要处理一些事宜,比如说音乐暂停,视频暂停等等。但是 StatefulWidget 中的state并没有提供如Activity的onPause,onResume方法。所以我们需要自己搞一下,搞一下这2个方法出来,只要在这2个方法中处理便可以满足上面的需求。

效果

创建2个widget界面

1、LifecyclePage 简称 A界面

2、LifecycleNextPage 简称 B界面

  • 场景:A界面打开生命周期如下

  • 场景2:A界面点击按钮跳转到B界面

  • 场景3:B界面点击返回到A界面

  • 场景4:A界面切换到后台

  • 场景5:A界面后台切换到前台

  • 场景6:A界面弹出对话框

  • 场景7:关闭对话框

实现

新增方法

  • onCreate
  • onResume
  • onPause
  • onDestroy

onStart和onStop 这里就不做添加了,以上4个方法便可以完成需求。

代码

老样子,直接上代码,不讲原理,就是这么拽。

  • 创建NavigatorObserver
import 'dart:async';

import 'package:built_collection/built_collection.dart';
import 'package:flutter/widgets.dart';

class NavigationHistoryObserver extends NavigatorObserver {
  final List<Route<dynamic>?> _history = <Route<dynamic>?>[];

  BuiltList<Route<dynamic>> get history =>
      BuiltList<Route<dynamic>>.from(_history);

  Route<dynamic>? get top => _history.last;

  final List<Route<dynamic>?> _poppedRoutes = <Route<dynamic>?>[];

  BuiltList<Route<dynamic>> get poppedRoutes =>
      BuiltList<Route<dynamic>>.from(_poppedRoutes);

  Route<dynamic>? get next => _poppedRoutes.last;

  final StreamController _historyChangeStreamController =
      StreamController.broadcast();

  Stream<dynamic> get historyChangeStream =>
      _historyChangeStreamController.stream;

  static final NavigationHistoryObserver _singleton =
      NavigationHistoryObserver._internal();

  NavigationHistoryObserver._internal();

  factory NavigationHistoryObserver() {
    return _singleton;
  }

  @override
  void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
    _poppedRoutes.add(_history.last);
    _history.removeLast();
    _historyChangeStreamController.add(HistoryChange(
      action: NavigationStackAction.pop,
      newRoute: route,
      oldRoute: previousRoute,
    ));
  }

  @override
  void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
    _history.add(route);
    _poppedRoutes.remove(route);
    _historyChangeStreamController.add(HistoryChange(
      action: NavigationStackAction.push,
      newRoute: route,
      oldRoute: previousRoute,
    ));
  }

  @override
  void didRemove(Route<dynamic> route, Route<dynamic>? previousRoute) {
    _history.remove(route);
    _historyChangeStreamController.add(HistoryChange(
      action: NavigationStackAction.remove,
      newRoute: route,
      oldRoute: previousRoute,
    ));
  }

  @override
  void didReplace({Route<dynamic>? newRoute, Route<dynamic>? oldRoute}) {
    int oldRouteIndex = _history.indexOf(oldRoute);
    _history.replaceRange(oldRouteIndex, oldRouteIndex + 1, [newRoute]);
    _historyChangeStreamController.add(HistoryChange(
      action: NavigationStackAction.replace,
      newRoute: newRoute,
      oldRoute: oldRoute,
    ));
  }
}

class HistoryChange {
  HistoryChange({this.action, this.newRoute, this.oldRoute});

  final NavigationStackAction? action;
  final Route<dynamic>? newRoute;
  final Route<dynamic>? oldRoute;
}

enum NavigationStackAction { push, pop, remove, replace }

  • 设置navigatorObservers
void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Android小样',
      ...
      navigatorObservers: [NavigationHistoryObserver()],
      ...
    );
  }
}
  • 创建PageState

abstract class PageState<T extends StatefulWidget> extends State<T>
    with PageStateMixin {
  static final List<BuildContext> _contextList = [];

  @override
  void initState() {
    super.initState();
    onCreate();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _addToContextList();
    });
  }

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

  @override
  void setState(VoidCallback fn) {
    if (mounted) {
      super.setState(fn);
    }
  }

  void _addToContextList() {
    if (!mounted) return;
    if (!_contextList.contains(context)) {
      _contextList.add(context);
    }
  }

  void _removeFromContextList() {
    if (_contextList.isEmpty) return;
    _contextList.removeWhere((element) => element == context);
  }
}

mixin PageStateMixin<T extends StatefulWidget> on State<T> {
  Route? _route;

  @override
  void didChangeDependencies() {
    _route ??= ModalRoute.of(context);
    if (_route != null) {
      RouteHistoryObserver.addResumeCallback(_route!, onResume);
      RouteHistoryObserver.addPauseCallback(_route!, onPause);
    }
    super.didChangeDependencies();
  }

  @override
  void dispose() {
    _route ??= ModalRoute.of(context);
    if (_route != null) {
      RouteHistoryObserver.removeResumeCallback(_route!, onResume);
      RouteHistoryObserver.removePauseCallback(_route!, onPause);
    }
    super.dispose();
  }

  void onCreate() {}

  void onResume() {}

  void onPause() {}

  void onDestroy() {}
}

class RouteHistoryObserver with WidgetsBindingObserver {
  static final Map<Route, Set<VoidCallback>> _resumeCallbacks = {};
  static final Map<Route, Set<VoidCallback>> _pauseCallbacks = {};
  static bool _initialized = false;
  static Route? _currTopRoute;

  static Route<dynamic>? get topRoute => _currTopRoute;

  static void init() {
    if (_initialized) return;
    _initialized = true;
    NavigationHistoryObserver()
        .historyChangeStream
        .listen(_appRouteHistoryChange);
    WidgetsBinding.instance.addObserver(RouteHistoryObserver());
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if (state == AppLifecycleState.resumed) {
      _appResume();
    } else if (state == AppLifecycleState.paused) {
      _appPause();
    }
  }

  static void _appRouteHistoryChange(dynamic historyChange) {
    if (NavigationHistoryObserver().history.isEmpty) return;
    var topRoute = NavigationHistoryObserver().top;
    if (historyChange.action == NavigationStackAction.push) {
      var preRoute = (historyChange as HistoryChange).oldRoute;
      var pauseCallbackList = _pauseCallbacks[preRoute];
      if (pauseCallbackList != null) {
        for (var callback in pauseCallbackList) {
          callback.call();
        }
      }
    } else if (historyChange.action == NavigationStackAction.pop) {
      var pauseCallbackList = _pauseCallbacks[_currTopRoute];
      if (pauseCallbackList != null) {
        for (var callback in pauseCallbackList) {
          callback.call();
        }
      }
    }

    if (topRoute != _currTopRoute) {
      _currTopRoute = topRoute;
      var resumeCallbackList = _resumeCallbacks[topRoute];
      if (resumeCallbackList == null) return;
      for (var callback in resumeCallbackList) {
        callback.call();
      }
    }
  }

  static void addResumeCallback(Route route, VoidCallback callback) {
    var callbackList = _resumeCallbacks[route];
    if (callbackList == null) {
      callbackList = {};
      _resumeCallbacks[route] = callbackList;
    }
    if (callbackList.add(callback) && _currTopRoute == route) {
      callback.call();
    }
  }

  static void removeResumeCallback(Route route, VoidCallback callback) {
    var callbackList = _resumeCallbacks[route];
    if (callbackList == null) return;
    callbackList.remove(callback);
  }

  static void addPauseCallback(Route route, VoidCallback callback) {
    var callbackList = _pauseCallbacks[route];
    if (callbackList == null) {
      callbackList = {};
      _pauseCallbacks[route] = callbackList;
    }
    callbackList.add(callback);
  }

  static void removePauseCallback(Route route, VoidCallback callback) {
    var callbackList = _pauseCallbacks[route];
    if (callbackList == null) return;
    callbackList.remove(callback);
  }

  static void _appResume() {
    if (_currTopRoute == null) return;
    var callbackList = _resumeCallbacks[_currTopRoute];
    if (callbackList == null) return;
    for (var callback in callbackList) {
      callback.call();
    }
  }

  static void _appPause() {
    if (_currTopRoute == null) return;
    var pauseCallbackList = _pauseCallbacks[_currTopRoute];
    if (pauseCallbackList == null) return;
    for (var callback in pauseCallbackList) {
      callback.call();
    }
  }
}


// 需要在app启动时初始化
RouteHistoryObserver.init();

  • 创建2个界面Widget

// 第一个界面
class LifecyclePage extends StatefulWidget {
  const LifecyclePage({super.key});

  @override
  State<LifecyclePage> createState() => _LifecyclePageState();
}

class _LifecyclePageState extends PageState<LifecyclePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: XYAppBar(
        title: "Flutter生命周期",
        onBack: () {
          Navigator.pop(context);
        },
      ),
      body: SafeArea(
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                onPressed: () {
                  Navigator.push(context, MaterialPageRoute(
                    builder: (BuildContext context) {
                      return const LifecycleNextPage();
                    },
                  ));
                },
                child: const Text("跳转"),
              ),
              ElevatedButton(
                onPressed: () {
                  _showDialog(context);
                },
                child: const Text("弹出框"),
              ),
            ],
          ),
        ),
      ),
    );
  }

  void _showDialog(BuildContext context) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: const Text('Dialog Title'),
          content: const Text('This is the content of the dialog.'),
          actions: [
            TextButton(
              onPressed: () {
                Navigator.of(context).pop(); // Close the dialog
              },
              child: const Text('Close'),
            ),
          ],
        );
      },
    );
  }

  @override
  void onCreate() {
    super.onCreate();
    logger.d("LifecyclePage----onCreate");
  }

  @override
  void onPause() {
    super.onPause();
    logger.d("LifecyclePage----onPause");
  }

  @override
  void onResume() {
    super.onResume();
    logger.d("LifecyclePage----onResume");
  }

  @override
  void onDestroy() {
    logger.d("LifecyclePage----onDestroy");
    super.onDestroy();
  }
}


//第二个界面
import 'package:flutter/material.dart';
import 'package:flutter_xy/widgets/xy_app_bar.dart';
import 'package:flutter_xy/xydemo/lifecycle/core/page_state.dart';

import '../../utils/log_utils.dart';

class LifecycleNextPage extends StatefulWidget {
  const LifecycleNextPage({super.key});

  @override
  State<LifecycleNextPage> createState() => _LifecycleNextPageState();
}

class _LifecycleNextPageState extends PageState<LifecycleNextPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: XYAppBar(
        title: "Flutter生命周期第二页",
        onBack: () {
          Navigator.pop(context);
        },
      ),
      body: const SafeArea(
        child: Center(
          child: Text(
            "第二页",
            style: TextStyle(fontSize: 30),
          ),
        ),
      ),
    );
  }

  @override
  void onCreate() {
    super.onCreate();
    logger.d("LifecycleNextPage----onCreate");
  }

  @override
  void onPause() {
    super.onPause();
    logger.d("LifecycleNextPage----onPause");
  }

  @override
  void onResume() {
    super.onResume();
    logger.d("LifecycleNextPage----onResume");
  }

  @override
  void onDestroy() {
    logger.d("LifecycleNextPage----onDestroy");
    super.onDestroy();
  }
}


OK了,自己去测试测试吧。

详情见Github:github.com/yixiaolunhui/flutter_xy

相关文章
|
开发者
Flutter笔记:build方法、构建上下文BuildContext解析
本文主要介绍Flutter中的build方法和构建上下文对象。
439 2
Flutter笔记:build方法、构建上下文BuildContext解析
Flutter Getx 路由 until 方法帮助你跳转指定路由
不少同学都会问我,这样一个场景,当我点击商品列表,进入商品页,点击购买,支付成功后,想返回商品页,或者我的中心的订单列表。怎么做,这中间跨度了 n 个路由。 我不只一次的推荐 GetX 的 until 方法,和 offNamedUntil 方法。 我写了个 demo 今天我们就一起来看下这两个方法如何使用。
1617 0
Flutter Getx 路由 until 方法帮助你跳转指定路由
|
7月前
Flutter 小技巧之 ListView 和 PageView 的各种花式嵌套
Flutter 小技巧之 ListView 和 PageView 的各种花式嵌套 在 Flutter 中,ListView 和 PageView 是两个常用的控件,它们可以用于滑动展示大量内容的场景,且支持各种嵌套方式,本文将介绍其中的一些花式嵌套方式。
277 0
|
27天前
|
开发框架 前端开发 定位技术
Flutter框架中的插件市场及开源资源的利用方法。内容涵盖插件市场的扩展功能、时间节省与质量保证
本文深入探讨了Flutter框架中的插件市场及开源资源的利用方法。内容涵盖插件市场的扩展功能、时间节省与质量保证,常见插件市场的介绍,选择合适插件的策略,以及开源资源的利用价值与注意事项。通过案例分析和对社区影响的讨论,展示了这些资源如何促进开发效率和技术进步,并展望了未来的发展趋势。
36 11
|
26天前
|
缓存 前端开发 数据安全/隐私保护
Flutter 框架提供了丰富的机制和方法来优化键盘处理和输入框体验
在移动应用开发中,Flutter 框架提供了丰富的机制和方法来优化键盘处理和输入框体验。本文深入探讨了键盘的显示与隐藏、输入框的焦点管理、键盘类型的适配、输入框高度自适应、键盘遮挡问题处理及性能优化等关键技术,结合实例分析,旨在帮助开发者提升应用的用户体验。
42 6
|
6月前
|
存储 开发框架 JavaScript
深入探讨Flutter中动态UI构建的原理、方法以及数据驱动视图的实现技巧
【6月更文挑战第11天】Flutter是高效的跨平台移动开发框架,以其热重载、高性能渲染和丰富组件库著称。本文探讨了Flutter中动态UI构建原理与数据驱动视图的实现。动态UI基于Widget树模型,状态变化触发UI更新。状态管理是关键,Flutter提供StatefulWidget、Provider、Redux等方式。使用ListView等可滚动组件和StreamBuilder等流式组件实现数据驱动视图的自动更新。响应式布局确保UI在不同设备上的适应性。Flutter为开发者构建动态、用户友好的界面提供了强大支持。
113 2
|
2月前
|
开发者
flutter:总结所有需要用到的方法与实战 (十六)
本文介绍了Flutter中路由和顶部导航的使用方法,包括简单路由、命名路由、返回及返回根路由的实现。同时,详细讲解了顶部导航的定义与属性设置,并通过实战案例展示了复杂布局、新闻列表和页面制作的思路。最后,还提供了父类向子类传递参数的方法以及如何添加依赖库。
|
2月前
|
开发框架 UED 计算机视觉
flutter:图片&stful 生命周期 (三)
本文档介绍了如何在Flutter中处理图片,包括加载网络图片、本地图片、创建圆形图片和带有圆角的图片,以及如何配置`pubspec.yaml`文件来添加资源文件。还展示了如何使用`AssetImage`对象来显示本地资源图片,并通过代码示例详细说明了这些操作的实现方法。最后,简要介绍了StatefulWidget的生命周期。
|
7月前
|
Dart 安全
简化代码、提高效率:Dart和Flutter开发小技巧
在日常开发中,我们常常会使用一些常用的技巧或语法糖,以简化代码、提高开发效率。本文将分享一些在Dart和Flutter中常用的小贴士,帮助你更轻松地编写优雅高效的代码。
简化代码、提高效率:Dart和Flutter开发小技巧
|
7月前
|
Dart Android开发 iOS开发
Flutter 弃用 WillPopScope 使用 PopScope 替代方法
了解如何在 Flutter 3.16 中将弃用的 WillPopScope 替换为 PopScope,并学习如何升级您的 Flutter 应用程序。详细指南和最佳实践,帮助您顺利迁移和更新您的导航逻辑。
297 7
Flutter 弃用 WillPopScope 使用 PopScope 替代方法