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

Flutter Camera问题:切换相机/重新初始化CameraController后预览黑屏

Flutter Camera Switch Results in Black Screen (No Error Logs)

Let's break down what's going wrong with your code and fix it step by step. The black screen on camera switch usually happens due to unhandled async operations, missing error handling, or improper resource management.

Core Issues in Your Current Code:

  • No upfront camera list fetch: Your code uses cameras.firstWhere but doesn't show logic to retrieve available cameras. While it works the first time, switching can fail if the list isn't properly initialized.
  • Unawaited async operations: You're not waiting for the old controller to dispose before creating a new one, which can cause resource conflicts. Also, initialization errors are uncaught, so you can't debug failures.
  • Missing widget state check: Calling setState after initialization might lead to errors if the widget has been disposed in the meantime.
  • No loading state: Users see a blank screen while the camera initializes, which looks like a failure even when it's working.

Fixed Code with Explanations:

First, ensure you have the required imports:

import 'package:camera/camera.dart';
import 'package:flutter/material.dart';

Here's the updated widget code:

class CameraTestScreen extends StatefulWidget {
  const CameraTestScreen({super.key});

  @override
  State<CameraTestScreen> createState() => _CameraTestScreenState();
}

class _CameraTestScreenState extends State<CameraTestScreen> {
  CameraController? controller;
  bool frontCamera = false;
  List<CameraDescription> cameras = []; // Store all available cameras

  @override
  void initState() {
    super.initState();
    _fetchAvailableCameras(); // Get cameras first before initializing
  }

  // Fetch available cameras from the device
  Future<void> _fetchAvailableCameras() async {
    try {
      cameras = await availableCameras();
      initCamera(); // Initialize camera once we have the list
    } catch (e) {
      print('Failed to load cameras: $e');
    }
  }

  Future<void> initCamera() async {
    // Clean up old controller first
    if (controller != null) {
      await controller!.dispose();
    }

    try {
      // Find the target camera (fallback to first camera if none matches)
      final targetCamera = cameras.firstWhere(
        (cam) => cam.lensDirection == (frontCamera ? CameraLensDirection.front : CameraLensDirection.back),
        orElse: () => cameras.first,
      );

      // Create new controller
      controller = CameraController(
        targetCamera,
        ResolutionPreset.medium,
        enableAudio: false,
      );

      // Wait for initialization to complete
      await controller!.initialize();

      // Update UI only if the widget is still active
      if (mounted) {
        setState(() {});
      }
    } catch (e) {
      print('Camera initialization error: $e');
      // Optionally show an error message to the user here
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("Camera Test")),
      body: Column(
        children: [
          // Show camera preview only if initialized
          if (controller != null && controller!.value.isInitialized)
            Container(
              height: 400,
              child: CameraPreview(controller!),
            ),
          // Show loading indicator while initializing
          if (controller != null && !controller!.value.isInitialized)
            const Padding(
              padding: EdgeInsets.all(16.0),
              child: CircularProgressIndicator(),
            ),
          Row(
            children: [
              ElevatedButton(
                onPressed: () async {
                  frontCamera = !frontCamera;
                  await initCamera(); // Wait for switch to complete
                },
                child: const Text("Switch"),
              ),
            ],
          ),
        ],
      ),
    );
  }

  // Clean up controller when widget is disposed
  @override
  void dispose() {
    controller?.dispose();
    super.dispose();
  }
}

Key Fixes Explained:

  1. Upfront camera list fetch: We first retrieve all available cameras using availableCameras() to ensure we have a valid list to pick from when switching.
  2. Await async operations: Using await for dispose() and initialize() ensures resources are properly cleaned up before creating a new controller, preventing conflicts.
  3. Error handling: Wrapped camera operations in try/catch blocks to log issues—this will help you debug problems like permission errors or unsupported camera directions.
  4. Mounted check: Before calling setState, we verify the widget is still in the tree to avoid state errors.
  5. Loading state: Added a progress indicator to show users the camera is initializing, so they don't confuse a loading state with a failure.
  6. Proper disposal: Overrode dispose() to clean up the camera controller, preventing memory leaks.

Additional Checks:

  • Verify camera permissions are added to AndroidManifest.xml (Android) and Info.plist (iOS).
  • Test on a physical device—emulators/simulators often have limited or buggy camera support.

内容的提问来源于stack exchange,提问作者Jonas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 20:52:35