Flutter:Widget 和导航的生命周期
Flutter: Lifecycle of a Widget and Navigation
我写了一个 flutter 插件,可以显示相机预览和扫描条形码。我有一个名为 ScanPage
的 Widget
,它显示 CameraPreview
并在检测到条形码时导航到新的 Route
。
问题:
当我将新路线 (SearchProductPage
) 推送到导航堆栈时,CameraController
会继续检测条形码。当 ScanPage
从屏幕上移除时,我需要在我的 CameraController
上调用 stop()
。当用户 returns 转到 ScanPage
.
时,我需要再次调用 start()
我试过的:
CameraController
实现 WidgetsBindingObserver
并对 didChangeAppLifecycleState()
作出反应。当我按下主页按钮时,这非常有效,但当我将新的 Route
推送到导航堆栈时却没有。
问题:
在 Flutter 中,iOS 上的 viewDidAppear()
和 viewWillDisappear()
或 Android 上的 onPause()
和 onResume()
是否与 Widgets
等价?如果没有,我如何启动和停止我的 CameraController
以便它在导航堆栈顶部有另一个 Widget 时停止扫描条形码?
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();
});
}
}
}
在调用 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
。
也许您可以覆盖小部件的 dispose
方法并让您的控制器在其中停止。 AFAIK,这将是一个很好的处理方式,因为 flutter 会在您每次处理小部件时 'automatically' 停止它,因此您不必自己密切关注何时启动或停止相机。
顺便说一句,我需要一个带有实时预览功能的 barcode/QR-code 扫描仪。您介意在 git(或 zip)上分享您的插件吗?
谢谢,这太有用了!
我还需要 ViewDidAppear
的等价物。我最后做的是从这里获取 "resumed" 状态,然后检查构建函数。
这意味着我的检查将在应用程序返回前台以及加载时调用。
当然需要设置布尔值以确保 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
}
}
您还可以使用 FocusDetector 包,它是您可以获得的最接近“viewDidAppear”和“onResume”的包。
我写了一个 flutter 插件,可以显示相机预览和扫描条形码。我有一个名为 ScanPage
的 Widget
,它显示 CameraPreview
并在检测到条形码时导航到新的 Route
。
问题:
当我将新路线 (SearchProductPage
) 推送到导航堆栈时,CameraController
会继续检测条形码。当 ScanPage
从屏幕上移除时,我需要在我的 CameraController
上调用 stop()
。当用户 returns 转到 ScanPage
.
start()
我试过的:
CameraController
实现 WidgetsBindingObserver
并对 didChangeAppLifecycleState()
作出反应。当我按下主页按钮时,这非常有效,但当我将新的 Route
推送到导航堆栈时却没有。
问题:
在 Flutter 中,iOS 上的 viewDidAppear()
和 viewWillDisappear()
或 Android 上的 onPause()
和 onResume()
是否与 Widgets
等价?如果没有,我如何启动和停止我的 CameraController
以便它在导航堆栈顶部有另一个 Widget 时停止扫描条形码?
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();
});
}
}
}
在调用 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
。
也许您可以覆盖小部件的 dispose
方法并让您的控制器在其中停止。 AFAIK,这将是一个很好的处理方式,因为 flutter 会在您每次处理小部件时 'automatically' 停止它,因此您不必自己密切关注何时启动或停止相机。
顺便说一句,我需要一个带有实时预览功能的 barcode/QR-code 扫描仪。您介意在 git(或 zip)上分享您的插件吗?
谢谢,这太有用了!
我还需要 ViewDidAppear
的等价物。我最后做的是从这里获取 "resumed" 状态,然后检查构建函数。
这意味着我的检查将在应用程序返回前台以及加载时调用。
当然需要设置布尔值以确保 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
}
}
您还可以使用 FocusDetector 包,它是您可以获得的最接近“viewDidAppear”和“onResume”的包。