Flutter:小部件和导航的生命周期
Posted
技术标签:
【中文标题】Flutter:小部件和导航的生命周期【英文标题】:Flutter: Lifecycle of a Widget and Navigation 【发布时间】:2018-10-13 18:39:18 【问题描述】:我编写了一个颤振插件,它显示相机预览并扫描条形码。我有一个名为ScanPage
的Widget
,它显示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
另一种解决方案是将打开ScanPage
的route
的maintainState
属性设置为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(生命周期状态:已创建,无小部件,未安装)