Flutter中关于小部件的onResume()和onPause()方法

46

目前,小部件只有initeState()方法,在小部件创建时第一次被触发,以及dispose()方法,在小部件销毁时被触发。是否有一种方法可以检测到小部件何时返回前台?以及当另一个小部件刚刚成为前台时,小部件将要进入后台的情况?这相当于在Android中触发onResume和onPause,在iOS中触发viewWillAppear和viewWillDisappear。


你的使用场景是什么?了解你想要做什么可以帮助我们提供更多信息。谢谢! - Seth Ladd
答案非常棒!但我确切寻找的是在 https://dev59.com/PaLia4cB1Zd3GeqPiGHS#44417260 中给出的答案。 - user3217522
1
看一下FocusDetector,它在监听小部件的前台可见性:https://pub.dev/packages/focus_detector - JustADeveloper
7个回答

56

有一个抽象类叫做 WidgetsBindingObserver。

https://docs.flutter.io/flutter/widgets/WidgetsBindingObserver-class.html

在中。

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    setState(() {
            _notification = state;
    });
  }

这里有一个名为"state"的东西,可以被管理。

switch(state) {
  case AppLifecycleState.resumed:
    // Handle this case
    break;
  case AppLifecycleState.inactive:
    // Handle this case
    break;
  case AppLifecycleState.paused:
    // Handle this case
    break;
  case AppLifecycleState.suspending:
    // Handle this case
    break;
}

1
我尝试了这个,但是didChangeAppLifecycleState()从未被调用。还能做什么? - CanCoder
1
你是否添加了“with WidgetsBindingObserver”?请参阅文档: https://docs.flutter.io/flutter/widgets/WidgetsBindingObserver-class.html - Mamnarock
35
这是应用程序级别的生命周期,这意味着当整个应用程序处于恢复、非活动或暂停状态时,而不是单个小部件。 - TeeTracker
3
这是关于应用程序生命周期的,而不是小部件生命周期。 - user9900987
新手或初学者可能无法理解这个解决方案。他们必须查看下面的答案。 - Pratik Butani
显示剩余2条评论

41

下面是一个完整的示例,演示如何正确地处理事情,要测试它,请按home键并恢复应用程序,您会看到didChangeAppLifecycleState被调用。

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();

    // Add the observer. 
    WidgetsBinding.instance!.addObserver(this);
  }

  @override
  void dispose() {
    // Remove the observer
    WidgetsBinding.instance!.removeObserver(this);

    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);

    // These are the callbacks
    switch (state) {
      case AppLifecycleState.resumed:
        // widget is resumed
        break;
      case AppLifecycleState.inactive:
        // widget is inactive
        break;
      case AppLifecycleState.paused:
        // widget is paused
        break;
      case AppLifecycleState.detached:
        // widget is detached
        break;
    }
  }

  @override
  Widget build(BuildContext context) => Scaffold();
}

4
太棒了!这一定是被接受的答案! - Alexey
6
这段代码实际上是监控应用程序生命周期,而不是小部件。 如果你测试它,你会发现"first"在恢复时没有被调用。这是因为应用程序已经被恢复了。 需要为Android Fragment/Activity寻找另一种解决方案 :) - user3193413
正如@TeeTracker所提到的,这是应用程序级别的生命周期,这意味着当整个应用程序恢复、不活动或暂停时,而不是单个小部件。 - 3ameration

17

最常见的情况是您正在运行动画,但不希望在后台消耗资源。在这种情况下,您应该使用TickerProviderStateMixin扩展您的State,并将您的State用作AnimationControllervsync参数。Flutter会在您的State可见时仅调用动画控制器的监听器。

如果您希望在其他内容遮挡PageRoute时,该页面中的State被处理,您可以将maintainState参数设置为false,并传递给PageRoute构造函数。这样做会导致State及其子类在隐藏时重置自身,并在initState中使用传递给其widget构造函数参数的方式重新构建自身。如果您不想进行完全重置,则可以使用模型或控制器类,或者PageStorage来保存用户的进度信息。
以下是演示这些概念的示例应用程序。

screen 1 screen 2 screen 3

import 'package:flutter/material.dart';

void main() {
  runApp(new MaterialApp(
    onGenerateRoute: (RouteSettings settings) {
      if (settings.name == '/') {
        return new MaterialPageRoute<Null>(
          settings: settings,
          builder: (_) => new MyApp(),
          maintainState: false,
        );
      }
      return null;
    }
  ));
}

class MyApp extends StatefulWidget {
  MyAppState createState() => new MyAppState();
}

class MyAppState extends State<MyApp> with TickerProviderStateMixin {
  AnimationController _controller;

  @override
  void initState() {
    print("initState was called");
    _controller = new AnimationController(vsync: this)
      ..repeat(min: 0.0, max: 1.0, period: const Duration(seconds: 1))
      ..addListener(() {
        print('animation value ${_controller.value}');
      });
    super.initState();
  }

  @override
  void dispose() {
    print("dispose was called");
    _controller.dispose();
    super.dispose();
  }

  int _counter = 0;

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('home screen')
      ),
      body: new Center(
        child: new RaisedButton(
          onPressed: () {
            setState(() {
              _counter++;
            });
          },
          child: new Text('Button pressed $_counter times'),
        ),
      ),
      floatingActionButton: new FloatingActionButton(
        child: new Icon(Icons.remove_red_eye),
        onPressed: () {
          Navigator.push(context, new MaterialPageRoute(
            builder: (BuildContext context) {
              return new MySecondPage(counter: _counter);
            },
          ));
        },
      ),
    );
  }
}

class MySecondPage extends StatelessWidget {
  MySecondPage({ this.counter });

  final int counter;

  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Certificate of achievement'),
      ),
      body: new Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        mainAxisAlignment: MainAxisAlignment.spaceAround,
        children: [
          new Icon(Icons.developer_mode, size: 200.0),
          new Text(
            'Congrats, you clicked $counter times.',
            style: Theme.of(context).textTheme.title,
            textAlign: TextAlign.center,
          ),
          new Text(
            'All your progress has now been lost.',
            style: Theme.of(context).textTheme.subhead,
            textAlign: TextAlign.center,
          ),
        ],
      ),
    );
  }
}

如果您只想处理/重新创建选项卡控制器中的一个选项卡,而且您不想干扰其他选项卡,因为保存/恢复会很麻烦,该怎么办? - Anton Duzenko

13

我有点晚了,但是为那些未来可能正在寻找解决方案的人带来了完美的解决方案。 Navigator.push() 实际上是一个 Future。这意味着它有一个 then() 回调函数。所以在从第二个屏幕调用 Navigator.pop() 后将会调用 then(),即使您可以从第二个屏幕发送一些数据并在第一个屏幕中访问数据。

例如:

//from Screen A
Navigator.of(context).push(MaterialPageRoute(builder:(context)=>B()))
.then((value)=>{ refresh() });

//in Screen B with data
Navigator.pop(context,[1]);

//or without data
Navigator.pop(context);

所以在恢复A屏幕时,refresh()将被调用。


如果屏幕B没有Navigator.pop(context,[1]);,那么屏幕A上的then将永远不会运行。 - Ben

8

我创建了visibility_aware_state,目的是为了有一个类似于Android的Activity.onResume()行为的东西。它还考虑到了弹出和推入导航。

class Example extends StatefulWidget {
  @override
  _ExampleState createState() => _ExampleState();
}

class _ExampleState extends VisibilityAwareState<Example> {
  @override
  Widget build(BuildContext context) {
    // return your widget
  }

  @override
  void onVisibilityChanged(WidgetVisibility visibility) {
    switch(visibility) {
      case WidgetVisibility.VISIBLE:
        // Like Android's Activity.onResume()
        break;
      case WidgetVisibility.INVISIBLE:
        // Like Android's Activity.onPause()
        break;
      case WidgetVisibility.GONE:
        // Like Android's Activity.onDestroy()
        break;
    }
    super.onVisibilityChanged(visibility);
  }
}

1
很好,感谢您构建和分享这个解决方案。我已经设置了一个基类,并且使用WidgetsBindingObserver@override来处理void didChangeAppLifecycleState(AppLifecycleState state)在那里无法工作。但是这个解决方案可以在该设置中工作。 - Gene Bo

3

Mamnarock,你的答案是正确的,但不完整,并且你分享的链接不可用。

这是完整的代码:

import 'package:flutter/material.dart';

class YourClass extends StatefulWidget {

  @override
  _YourClassState createState() => _YourClassState();
}

class _YourClassState extends State<YourClass>
    with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        // Handle this case
        break;
      case AppLifecycleState.inactive:
        // Handle this case
        break;
      case AppLifecycleState.paused:
        // Handle this case
        break;
      case AppLifecycleState.detached:
        // Handle this case
        break;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

正如 TeeTracker 在评论中提到的:

这是应用程序级别的生命周期,意味着当整个应用程序恢复、不活动或暂停时,而不是单个小部件。


谢谢,@Abbas,对我有用。有没有关于屏幕生命周期的建议? - Suresh
抱歉,我不知道。 - Abbas Jafari

0
如果您已经安装了Flutter Hookshttps://pub.dev/packages/flutter_hooks):
@override
Widget build(BuildContext context) {
  useOnAppLifecycleStateChange((previous, current) => null);
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接