Flutter:Widget生命周期与导航——相机控制器启停处理
解决方案:控制CameraController在页面切换时启停
这是Flutter开发中很常见的场景,我给你两种可靠的实现方式,你可以根据项目需求选择:
方案一:利用State生命周期方法(简单直接)
对于StatefulWidget的ScanPage,可以借助deactivate和activate这两个生命周期方法来监听页面的临时移除和重新激活:
class ScanPage extends StatefulWidget { const ScanPage({super.key}); @override State<ScanPage> createState() => _ScanPageState(); } class _ScanPageState extends State<ScanPage> { late CameraController _cameraController; bool _isControllerInitialized = false; @override void initState() { super.initState(); // 初始化CameraController _initCameraController(); } Future<void> _initCameraController() async { // 这里替换成你的相机初始化逻辑,比如获取相机列表、配置参数等 final cameras = await availableCameras(); _cameraController = CameraController( cameras[0], ResolutionPreset.medium, ); await _cameraController.initialize(); if (!mounted) return; setState(() => _isControllerInitialized = true); // 初始启动扫描 await _startScanning(); } Future<void> _startScanning() async { if (_isControllerInitialized && !_cameraController.value.isStreamingImages) { await _cameraController.startImageStream((image) { // 你的条形码检测逻辑,检测到后跳转 _handleBarcodeDetected(image); }); } } Future<void> _stopScanning() async { if (_isControllerInitialized && _cameraController.value.isStreamingImages) { await _cameraController.stopImageStream(); } } void _handleBarcodeDetected(CameraImage image) { // 假设这里已经检测到条形码 Navigator.push( context, MaterialPageRoute(builder: (context) => const SearchProductPage()), ); } @override void deactivate() { // 当页面被推入新路由(暂时从Widget树移除)时,停止扫描 _stopScanning(); super.deactivate(); } @override void activate() { // 当用户返回页面(重新回到Widget树)时,重启扫描 _startScanning(); super.activate(); } @override void dispose() { _cameraController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { if (!_isControllerInitialized) { return const Center(child: CircularProgressIndicator()); } return CameraPreview(_cameraController); } }
说明:
deactivate:当当前页面被新路由覆盖(比如push到SearchProductPage)时触发,此时我们停止相机的图像流扫描activate:当用户从SearchProductPage返回,当前页面重新回到前台时触发,此时重启扫描- 加入了状态判断(
_isControllerInitialized、isStreamingImages),避免在控制器未就绪时调用启停方法导致异常
方案二:使用RouteAware监听路由变化(更精准)
如果需要更精准的路由状态监听(比如区分页面是被push还是被pop),可以使用Flutter提供的RouteAware和RouteObserver:
第一步:全局注册RouteObserver
在你的MaterialApp中添加路由观察者:
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>(); void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Barcode Scanner', navigatorObservers: [routeObserver], // 注册观察者 home: const ScanPage(), ); } }
第二步:在ScanPage中混入RouteAware并实现监听方法
class ScanPage extends StatefulWidget { const ScanPage({super.key}); @override State<ScanPage> createState() => _ScanPageState(); } class _ScanPageState extends State<ScanPage> with RouteAware { late CameraController _cameraController; bool _isControllerInitialized = false; @override void didChangeDependencies() { super.didChangeDependencies(); // 订阅路由观察者 routeObserver.subscribe(this, ModalRoute.of(context)! as PageRoute); } @override void dispose() { // 取消订阅 routeObserver.unsubscribe(this); _cameraController.dispose(); super.dispose(); } // 页面被推入导航栈时触发(首次进入) @override void didPush() { super.didPush(); _startScanning(); } // 从下一个页面返回当前页面时触发 @override void didPopNext() { super.didPopNext(); _startScanning(); } // 当前页面被推入新路由(跳转到SearchProductPage)时触发 @override void didPushNext() { super.didPushNext(); _stopScanning(); } // 以下是相机初始化、启停扫描的方法,和方案一一致 Future<void> _initCameraController() async { final cameras = await availableCameras(); _cameraController = CameraController( cameras[0], ResolutionPreset.medium, ); await _cameraController.initialize(); if (!mounted) return; setState(() => _isControllerInitialized = true); } Future<void> _startScanning() async { if (_isControllerInitialized && !_cameraController.value.isStreamingImages) { await _cameraController.startImageStream((image) { _handleBarcodeDetected(image); }); } } Future<void> _stopScanning() async { if (_isControllerInitialized && _cameraController.value.isStreamingImages) { await _cameraController.stopImageStream(); } } void _handleBarcodeDetected(CameraImage image) { Navigator.push( context, MaterialPageRoute(builder: (context) => const SearchProductPage()), ); } @override Widget build(BuildContext context) { if (!_isControllerInitialized) { return const Center(child: CircularProgressIndicator()); } return CameraPreview(_cameraController); } }
说明:
didPushNext:精准对应跳转到下一页的动作,此时停止扫描didPopNext:精准对应从下一页返回的动作,此时重启扫描- 这种方式不会受屏幕旋转等其他生命周期事件的干扰,路由状态监听更准确
内容的提问来源于stack exchange,提问作者hendra
相关产品推荐
相关产品推荐

