Flutter生命周期方法小技巧

需求

  • A界面跳轉(zhuǎn)到B界面,暫停A界面的音樂或者視頻
  • B界面返回到A界面奕锌,播放A界面的音樂或者視頻
  • A界面切換到后臺殖告,暫停A界面的音樂或者視頻
  • A界面從后臺切換到前臺,播放A界面的音樂或者視頻

需求通過理解修改為:

  • 監(jiān)聽 StatefulWidget 的 onPause 方法
  • 監(jiān)聽 StatefulWidget 的 onResume 方法

背景

我們在使用Flutter開發(fā)界面時恳邀,很多時候是需要在當(dāng)前界面切換到后臺,或者說當(dāng)前界面跳轉(zhuǎn)到第二個頁面時颂砸,需要處理一些事宜噪奄,比如說音樂暫停,視頻暫停等等人乓。但是 StatefulWidget 中的state并沒有提供如Activity的onPause梗醇,onResume方法。所以我們需要自己搞一下撒蟀,搞一下這2個方法出來叙谨,只要在這2個方法中處理便可以滿足上面的需求。

效果

創(chuàng)建2個widget界面

1保屯、LifecyclePage 簡稱 A界面

2手负、LifecycleNextPage 簡稱 B界面

  • 場景:A界面打開生命周期如下
image.png
  • 場景2:A界面點擊按鈕跳轉(zhuǎn)到B界面


    image.png
  • 場景3:B界面點擊返回到A界面


    image.png
  • 場景4:A界面切換到后臺


    image.png
  • 場景5:A界面后臺切換到前臺


    image.png
  • 場景6:A界面彈出對話框


    image.png
  • 場景7:關(guān)閉對話框


    image.png

實現(xiàn)

新增方法

  • onCreate
  • onResume
  • onPause
  • onDestroy

onStart和onStop 這里就不做添加了涤垫,以上4個方法便可以完成需求。

代碼

老樣子竟终,直接上代碼蝠猬,不講原理,就是這么拽统捶。

  • 創(chuàng)建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 }

  • 設(shè)置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()],
      ...
    );
  }
}
  • 創(chuàng)建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();

  • 創(chuàng)建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("跳轉(zhuǎn)"),
              ),
              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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末喘鸟,一起剝皮案震驚了整個濱河市匆绣,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌什黑,老刑警劉巖崎淳,帶你破解...
    沈念sama閱讀 219,110評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異愕把,居然都是意外死亡拣凹,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評論 3 395
  • 文/潘曉璐 我一進店門恨豁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嚣镜,“玉大人,你說我怎么就攤上這事橘蜜【漳洌” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評論 0 356
  • 文/不壞的土叔 我叫張陵扮匠,是天一觀的道長。 經(jīng)常有香客問我凡涩,道長棒搜,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評論 1 295
  • 正文 為了忘掉前任活箕,我火速辦了婚禮力麸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘育韩。我一直安慰自己克蚂,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,902評論 6 392
  • 文/花漫 我一把揭開白布筋讨。 她就那樣靜靜地躺著埃叭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪悉罕。 梳的紋絲不亂的頭發(fā)上赤屋,一...
    開封第一講書人閱讀 51,698評論 1 305
  • 那天立镶,我揣著相機與錄音,去河邊找鬼类早。 笑死媚媒,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的涩僻。 我是一名探鬼主播缭召,決...
    沈念sama閱讀 40,418評論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼逆日!你這毒婦竟也來了嵌巷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評論 0 276
  • 序言:老撾萬榮一對情侶失蹤屏富,失蹤者是張志新(化名)和其女友劉穎晴竞,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體狠半,經(jīng)...
    沈念sama閱讀 45,796評論 1 316
  • 正文 獨居荒郊野嶺守林人離奇死亡噩死,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,968評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了神年。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片已维。...
    茶點故事閱讀 40,110評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖已日,靈堂內(nèi)的尸體忽然破棺而出垛耳,到底是詐尸還是另有隱情,我是刑警寧澤飘千,帶...
    沈念sama閱讀 35,792評論 5 346
  • 正文 年R本政府宣布堂鲜,位于F島的核電站,受9級特大地震影響护奈,放射性物質(zhì)發(fā)生泄漏缔莲。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,455評論 3 331
  • 文/蒙蒙 一霉旗、第九天 我趴在偏房一處隱蔽的房頂上張望痴奏。 院中可真熱鬧,春花似錦厌秒、人聲如沸读拆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽檐晕。三九已至,卻和暖如春蚌讼,著一層夾襖步出監(jiān)牢的瞬間棉姐,已是汗流浹背屠列。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留伞矩,地道東北人笛洛。 一個月前我還...
    沈念sama閱讀 48,348評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像乃坤,于是被迫代替她去往敵國和親苛让。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,047評論 2 355

推薦閱讀更多精彩內(nèi)容