【问题标题】:How to check when my widget screen comes to visibility in flutter like onResume in Android如何检查我的小部件屏幕何时像 Android 中的 onResume 一样在颤动中可见
【发布时间】:2025-12-22 22:45:06
【问题描述】:

在 android 中,如果一个 Activity 可见,则调用 onResumeFlutteronResume的等效方法是什么?

我需要知道我的小部件屏幕何时可见,以便我可以根据它自动播放视频。我可能会转到另一个小部件屏幕,当我回来时它应该会自动播放。

我的方法是在didUpdateWidget 中播放视频,但每次都会调用didUpdateWidget,即使小部件屏幕不可见。

注意:我不是从WidgetsBindingObserver 询问didChangeAppLifecycleState,因为它为应用程序生命周期而不是特定的小部件屏幕提供onResume 等回调。

【问题讨论】:

  • 不重复。我提到我不是在问didChangeAppLifecycleState。我的用例不同@Tokenyet
  • 当你在另一个屏幕时,如果你弹回到视频屏幕你可以在Navigator.pop(context, parameter)中传递一个参数,所以在视频屏幕你会得到参数并做相应的事情。看到这个帖子*.com/a/53861303/11020832
  • 哦,我终于知道你不接受onResume。它更多的是关于 PageTransition,看起来你有解决方法。为了不讲太多代码,我更喜欢在Navigator.pushawait Navigator.pop 之前停下来恢复视频。如果您没有使用Navigator,您可能需要提供更多代码向我们展示:P
  • 在下面的答案中,哪个方法等效于 onPause() 哪个方法等效于 onResume() ?我假设 didPush() 类似于 onResume() 而 didPop() 类似于 onPause()。我对么 ?我的理解正确吗?我在 didPopNext() 和 didPop() 之间感到困惑。 didPopNext() 是否也与 onResume() 相同?

标签: android flutter


【解决方案1】:

所有问题都解决了。

从小部件树的根目录(materialappwidget)将观察者放在导航器上。

如果您需要更多解释,请点击此链接: https://api.flutter.dev/flutter/widgets/RouteObserver-class.html

我已经在我的项目中实现了它,它工作得很好@Sp4Rx

// Register the RouteObserver as a navigation observer.
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>();

void main() {
  runApp(MaterialApp(
    home: Container(),
    navigatorObservers: [routeObserver],
  ));
}

class RouteAwareWidget extends StatefulWidget {
  State<RouteAwareWidget> createState() => RouteAwareWidgetState();
}

// Implement RouteAware in a widget's state and subscribe it to
// the
// RouteObserver.
class RouteAwareWidgetState extends State<RouteAwareWidget> with RouteAware {
  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    routeObserver.subscribe(this, ModalRoute.of(context));
  }

  @override
  void dispose() {
    routeObserver.unsubscribe(this);
    super.dispose();
  }

  @override
  void didPush() {
    // Route was pushed onto navigator and is now topmost route.
  }

  @override
  void didPopNext() {
    // Covering route was popped off the navigator.
  }

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

【讨论】:

  • 感谢您提供此信息。这是 Android 中与 onResume 最接近的答案。标记为正确。
  • 好一个!但是,可能会建议在可以从应用程序中的每个位置访问它的位置定义观察者,就像全局属性一样。
  • 当然你可以将它声明为全局,但正如我声明的那样,我可以从我的颤振应用程序的任何地方访问它
  • routeObserver.subscribe(this, ModalRoute.of(context));ModalRoute.of(context) 的结果为The argument type 'ModalRoute&lt;Object?&gt;?' can't be assigned to the parameter type 'PageRoute&lt;dynamic&gt;'.
  • @FerozKhan 使用observer.subscribe(this, ModalRoute.of(context) as PageRoute);
【解决方案2】:

在不查看我的应用主屏幕时,我很难让视频暂停。我应用了这个VisibilityDetector 并抓住了visiblePercentage 来强制暂停或恢复:

VisibilityDetector(
    key: Key('visible-video--key-${this.randomkeygenerator}-1'),
    onVisibilityChanged: (visibilityInfo) {
      var visiblePercentage = visibilityInfo.visibleFraction * 100;

      if (visiblePercentage < 1){ //the magic is done here
        if(_video_controller != null) {
          if(disposed_vid == false) {
            _video_controller.pause();
          }
        }

      }else{
        if(_video_controller != null) {
          if(disposed_vid == false) {
            _video_controller.play();
          }
        }
      }
      debugPrint(
          'Widget ${visibilityInfo.key} is ${visiblePercentage}% visible');
    },
    child: VideoPlayer(_video_controller)),


  @override
  void dispose() {
    // If the video is playing, pause it.
    _video_controller .pause();
    _video_controller .dispose();
    disposed_vid = true;
    super.dispose();
  }

【讨论】:

  • 列表中有视频吗? @石油
  • 是的,PageView 实际上
  • 那么这个实现就好了。但我的查询不同。
【解决方案3】:

因为后台路由的动画会被禁用。所以我们可以这样判断是否在前台:

final isForeground = TickerMode.of(context);

将其包装到一个小部件中:

/// Created by ipcjs on 2021/3/23.
class ForegroundDetector extends StatefulWidget {
  const ForegroundDetector({
    Key? key,
    required this.child,
    required this.onForegroundChanged,
  }) : super(key: key);

  final ValueChanged<bool> onForegroundChanged;
  final Widget child;

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

class ForegroundDetectorState extends State<ForegroundDetector> {
  bool get isForeground => _isForeground ?? false;
  bool? _isForeground;

  @override
  Widget build(BuildContext context) {
    final isForeground = TickerMode.of(context);
    if (_isForeground != isForeground) {
      _isForeground = isForeground;
      widget.onForegroundChanged(isForeground);
    }
    return widget.child;
  }
}

【讨论】:

  • 其实我在忙其他工作。我需要一些时间来验证。无论如何,这似乎很有希望。
  • 谢谢,正是我需要的。
【解决方案4】:

这可能不是最简单的,也绝对不是完美的,但不久前我实现了类似带有路由的事件。基本上,EventRoute&lt;T&gt;MaterialPageRoute&lt;T&gt; 的一个替代品,它为 Widget 的创建、推送到前台、推送到后台以及弹出的时间提供可选的回调。

event_route.dart

import 'package:flutter/material.dart';

enum RouteState {
  none,
  created,
  foreground,
  background,
  destroyed
}

class EventRoute<T> extends MaterialPageRoute<T> {
  BuildContext _context;
  RouteState _state;
  Function(BuildContext) _onCreateCallback;
  Function(BuildContext) _onForegroundCallback;
  Function(BuildContext) _onBackgroundCallback;
  Function(BuildContext) _onDestroyCallback;

  EventRoute(BuildContext context, {
    builder,
    RouteSettings settings,
    bool maintainState = true,
    bool fullscreenDialog = false,
    Function(BuildContext) onCreate,
    Function(BuildContext) onForeground,
    Function(BuildContext) onBackground,
    Function(BuildContext) onDestroy
  }):
        _context = context,
        _onCreateCallback = onCreate,
        _onForegroundCallback = onForeground,
        _onBackgroundCallback = onBackground,
        _onDestroyCallback = onDestroy,
        _state = RouteState.none,
        super(builder: builder, settings: settings, maintainState: maintainState, fullscreenDialog: fullscreenDialog);


  void get state => _state;

  @override
  void didChangeNext(Route nextRoute) {
    if (nextRoute == null) {
      _onForeground();
    } else {
      _onBackground();
    }
    super.didChangeNext(nextRoute);
  }

  @override
  bool didPop(T result) {
    _onDestroy();
    return super.didPop(result);
  }

  @override
  void didPopNext(Route nextRoute) {
    _onForeground();
    super.didPopNext(nextRoute);
  }

  @override
  TickerFuture didPush() {
    _onCreate();
    return super.didPush();
  }

  @override
  void didReplace(Route oldRoute) {
    _onForeground();
    super.didReplace(oldRoute);
  }

  void _onCreate() {
    if (_state != RouteState.none || _onCreateCallback == null) {
      return;
    }
    _onCreateCallback(_context);
  }

  void _onForeground() {
    if (_state == RouteState.foreground) {
      return;
    }
    _state = RouteState.foreground;
    if (_onForegroundCallback != null) {
      _onForegroundCallback(_context);
    }
  }

  void _onBackground() {
    if (_state == RouteState.background) {
      return;
    }
    _state = RouteState.background;
    if (_onBackgroundCallback != null) {
      _onBackgroundCallback(_context);
    }
  }

  void _onDestroy() {
    if (_state == RouteState.destroyed || _onDestroyCallback == null) {
      return;
    }
    _onDestroyCallback(_context);
  }
}

然后推送你的路线:

Navigator.push(context, EventRoute(context, builder: (context) => YourWidget(context),
      onCreate: (context) => print('create'),
      onForeground: (context) => print('foreground'),
      onBackground: (context) => print('background'),
      onDestroy: (context) => print('destroy')
));

虽然上下文有点恶心......

【讨论】:

  • 我无法得到你的答案 @software-person 和 await Navigator.push(...) 为我工作,正如 cmets 中提到的 @Tokenyet
  • 我创建了一个 EventRoute 类。它是一个类,它为 Widget 的创建、推送到前台、推送到后台以及它何时弹出提供可选的回调。所有这些事件都是可选的。您基本上可以用此类替换 MaterialPageRoute 并捕获这些事件。但是,如果@Toeknyet 他/她的解决方案适合您,您也可以使用该解决方案。我更新了我的答案
  • 虽然您的答案有更好的方法,但我会在某处实施并标记为正确。
  • @KPradeepKumarReddy 来源是我的头,the route class reference