You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:49:22