Flutter:小部件和导航的生命周期

Posted

技术标签:

【中文标题】Flutter:小部件和导航的生命周期【英文标题】:Flutter: Lifecycle of a Widget and Navigation 【发布时间】:2018-10-13 18:39:18 【问题描述】:

我编写了一个颤振插件,它显示相机预览并扫描条形码。我有一个名为ScanPageWidget,它显示CameraPreview,并在检测到条形码时导航到新的Route

问题: 当我将新路线 (SearchProductPage) 推送到导航堆栈时,CameraController 会继续检测条形码。当ScanPage 从屏幕上移除时,我需要在我的CameraController 上调用stop()。当用户返回ScanPage时,我需要再次调用start()

我尝试了什么: CameraController 实现 WidgetsBindingObserver 并对 didChangeAppLifecycleState() 作出反应。当我按下主页按钮时,这非常有效,但当我将新的Route 推送到导航堆栈时就不行了。

问题: ios 上的viewDidAppear()viewWillDisappear()android 上的onPause()onResume() 在Flutter 中的Widgets 是否有等价物?如果没有,我如何启动和停止我的CameraController,以便在另一个小部件位于导航堆栈顶部时停止扫描条形码?

class ScanPage extends StatefulWidget 

  ScanPage( Key key ) : super(key: key);

  @override
  _ScanPageState createState() => new _ScanPageState();



class _ScanPageState extends State<ScanPage> 

  //implements WidgetsBindingObserver
  CameraController controller;

  @override
  void initState() 

    controller = new CameraController(this.didDetectBarcode);
    WidgetsBinding.instance.addObserver(controller);

    controller.initialize().then((_) 
      if (!mounted) 
        return;
      
      setState(() );
    );
  

  //navigate to new page
  void didDetectBarcode(String barcode) 
      Navigator.of(context).push(
          new MaterialPageRoute(
            builder: (BuildContext buildContext) 
              return new SearchProductPage(barcode);
            ,
          )
      );    
  

  @override
  void dispose() 
    WidgetsBinding.instance.removeObserver(controller);
    controller?.dispose();
    super.dispose();
  

  @override
  Widget build(BuildContext context) 

    if (!controller.value.initialized) 
      return new Center(
        child: new Text("Lade Barcodescanner..."),
      );
    

    return new CameraPreview(controller);
  

编辑:

/// Controls a device camera.
///
///
/// Before using a [CameraController] a call to [initialize] must complete.
///
/// To show the camera preview on the screen use a [CameraPreview] widget.
class CameraController extends ValueNotifier<CameraValue> with WidgetsBindingObserver 

  int _textureId;
  bool _disposed = false;

  Completer<Null> _creatingCompleter;
  BarcodeHandler handler;

  CameraController(this.handler) : super(const CameraValue.uninitialized());

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) 

    switch(state)
      case AppLifecycleState.inactive:
        print("--inactive--");
        break;
      case AppLifecycleState.paused:
        print("--paused--");
        stop();
        break;
      case AppLifecycleState.resumed:
        print("--resumed--");
        start();
        break;
      case AppLifecycleState.suspending:
        print("--suspending--");
        dispose();
        break;
    
  

  /// Initializes the camera on the device.
  Future<Null> initialize() async 

    if (_disposed) 
      return;
    
    try 
      _creatingCompleter = new Completer<Null>();
      _textureId = await BarcodeScanner.initCamera();

      print("TextureId: $_textureId");

      value = value.copyWith(
        initialized: true,
      );
      _applyStartStop();
     on PlatformException catch (e) 
      value = value.copyWith(errorDescription: e.message);
      throw new CameraException(e.code, e.message);
    

    BarcodeScanner._channel.setMethodCallHandler((MethodCall call)
      if(call.method == "barcodeDetected")
        String barcode = call.arguments;
        debounce(2500, this.handler, [barcode]);
      
    );

    _creatingCompleter.complete(null);
  

  void _applyStartStop() 
    if (value.initialized && !_disposed) 
      if (value.isStarted) 
        BarcodeScanner.startCamera();
       else 
        BarcodeScanner.stopCamera();
      
    
  

  /// Starts the preview.
  ///
  /// If called before [initialize] it will take effect just after
  /// initialization is done.
  void start() 
    value = value.copyWith(isStarted: true);
    _applyStartStop();
  

  /// Stops the preview.
  ///
  /// If called before [initialize] it will take effect just after
  /// initialization is done.
  void stop() 
    value = value.copyWith(isStarted: false);
    _applyStartStop();
  

  /// Releases the resources of this camera.
  @override
  Future<Null> dispose() 
    if (_disposed) 
      return new Future<Null>.value(null);
    
    _disposed = true;
    super.dispose();
    if (_creatingCompleter == null) 
      return new Future<Null>.value(null);
     else 
      return _creatingCompleter.future.then((_) async 
        BarcodeScanner._channel.setMethodCallHandler(null);
        await BarcodeScanner.disposeCamera();
      );
    
  

【问题讨论】:

您可以添加您的CameraController 的一部分吗? @RémiRousselet:是的,我添加了 CameraController 的实现。它深受 Flutter 的 Camera Plugin 的启发。 @RémiRousselet 对此有什么想法吗?这是一个不好的方法吗? 【参考方案1】:

在调用pop() 时,我在导航到另一个页面并重新启动它之前停止了controller

//navigate to new page
void didDetectBarcode(String barcode) 
   controller.stop();
   Navigator.of(context)
       .push(...)
       .then(() => controller.start()); //future completes when pop() returns to this page


另一种解决方案是将打开ScanPageroutemaintainState 属性设置为false

【讨论】:

【参考方案2】:

也许您可以为您的小部件覆盖dispose 方法并让您的控制器在其中停止。 AFAIK,这将是一种很好的处理方式,因为每次您处理小部件时颤动都会“自动”停止它,因此您不必自己关注何时启动或停止相机。

顺便说一句,我需要一个带有实时预览功能的条码/二维码扫描仪。您介意在 git(或 zip)上分享您的插件吗?

【讨论】:

【参考方案3】:

谢谢,这个太有用了!

我还需要ViewDidAppear 的等价物。我最终做的是从这里获取“恢复”状态,然后在 Build-function 中进行检查。

这意味着当应用返回前台以及加载时会调用我的检查。

当然需要设置布尔值以确保 Build-Check 只会被调用一次,而不是每次重新加载视图时。

对于我的应用,我实际上希望它每天只发生一次,但这可以很容易地调整为每次应用加载发生一次。然后,当应用暂停/退出时,必须重置检查的布尔值。

(pseudocode, greatly reduced, still in progress)

bool hasRunViewDidAppearThisAppOpening = false;

@override
Widget build(BuildContext context) 
  _viewDidAppear();

  ...


@override
void didChangeAppLifecycleState(AppLifecycleState state) 
  super.didChangeAppLifecycleState(state);
  if (state == AppLifecycleState.resumed) 
    _viewDidAppear();
   else 
    hasRunViewDidAppearThisAppOpening = false;
  


Future<void> _viewDidAppear() async 
  if (!hasRunViewDidAppearThisAppOpening) 
    hasRunViewDidAppearThisAppOpening = true;
    // Do your _viewDidAppear code here
  


【讨论】:

【参考方案4】:

您还可以使用 FocusDetector 包,它是您可以获得的最接近“viewDidAppear”和“onResume”的东西。

https://pub.dev/packages/focus_detector

【讨论】:

以上是关于Flutter:小部件和导航的生命周期的主要内容,如果未能解决你的问题,请参考以下文章

Flutter:如何在没有小部件的情况下跟踪应用程序生命周期

Flutter:在从小部件树中删除时或在其生命周期结束时为小部件设置动画?

Flutter:在构造函数中调用 setState():_SharesListState#6c96a(生命周期状态:已创建,无小部件,未安装)

Flutter 页面生命周期

flutter 页面的生命周期(转)

Flutter 中的生命周期