許多應用程式需要使用裝置的相機來拍攝照片和影片。Flutter 提供了 camera 外掛來實現此目的。camera 外掛提供了獲取可用相機列表、顯示特定相機預覽以及拍攝照片或影片的工具。

本指南演示瞭如何使用 camera 外掛透過以下步驟顯示預覽、拍照並顯示照片:

  1. 新增必要的依賴項。
  2. 獲取可用相機列表。
  3. 建立並初始化 CameraController
  4. 使用 CameraPreview 顯示相機畫面。
  5. 使用 CameraController 拍照。
  6. 使用 Image widget 顯示照片。

1. 新增必要的依賴項

#

要完成本指南,您需要在應用中新增三個依賴項:

camera
提供與裝置相機互動的工具。
path_provider
查詢儲存影像的正確路徑。
path
建立可在任何平臺上使用的路徑。

要將這些包新增為依賴項,請執行 flutter pub add

flutter pub add camera path_provider path

2. 獲取可用相機列表

#

接下來,使用 camera 外掛獲取可用相機列表。

dart
// Ensure that plugin services are initialized so that `availableCameras()`
// can be called before `runApp()`
WidgetsFlutterBinding.ensureInitialized();

// Obtain a list of the available cameras on the device.
final cameras = await availableCameras();

// Get a specific camera from the list of available cameras.
final firstCamera = cameras.first;

3. 建立並初始化 CameraController

#

獲得相機後,請按照以下步驟建立並初始化 CameraController。此過程將建立與裝置相機的連線,使您能夠控制相機並顯示相機畫面的預覽。

  1. 建立一個帶有配套 State 類的 StatefulWidget
  2. State 類中新增一個變數以儲存 CameraController
  3. State 類中新增一個變數以儲存從 CameraController.initialize() 返回的 Future
  4. initState() 方法中建立並初始化控制器。
  5. dispose() 方法中銷燬控制器。
dart
// A screen that allows users to take a picture using a given camera.
class TakePictureScreen extends StatefulWidget {
  const TakePictureScreen({super.key, required this.camera});

  final CameraDescription camera;

  @override
  TakePictureScreenState createState() => TakePictureScreenState();
}

class TakePictureScreenState extends State<TakePictureScreen> {
  late CameraController _controller;
  late Future<void> _initializeControllerFuture;

  @override
  void initState() {
    super.initState();
    // To display the current output from the Camera,
    // create a CameraController.
    _controller = CameraController(
      // Get a specific camera from the list of available cameras.
      widget.camera,
      // Define the resolution to use.
      ResolutionPreset.medium,
    );

    // Next, initialize the controller. This returns a Future.
    _initializeControllerFuture = _controller.initialize();
  }

  @override
  void dispose() {
    // Dispose of the controller when the widget is disposed.
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    // Fill this out in the next steps.
    return Container();
  }
}

4. 使用 CameraPreview 顯示相機畫面

#

接下來,使用 camera 包中的 CameraPreview widget 來顯示相機畫面的預覽。

FutureBuilder 正是為了這個目的而設計的。

dart
// You must wait until the controller is initialized before displaying the
// camera preview. Use a FutureBuilder to display a loading spinner until the
// controller has finished initializing.
FutureBuilder<void>(
  future: _initializeControllerFuture,
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.done) {
      // If the Future is complete, display the preview.
      return CameraPreview(_controller);
    } else {
      // Otherwise, display a loading indicator.
      return const Center(child: CircularProgressIndicator());
    }
  },
)

5. 使用 CameraController 拍照

#

您可以使用 CameraController 透過 takePicture() 方法拍照。該方法返回一個 XFile,這是一個跨平臺的、簡化的 File 抽象。在 Android 和 iOS 上,新影像都儲存在各自的快取目錄中,並且 XFile 返回指向該位置的 path

在此示例中,建立一個 FloatingActionButton,當用戶點選該按鈕時,它會使用 CameraController 拍照。

拍照需要 2 個步驟:

  1. 確保相機已初始化。
  2. 使用控制器拍照,並確保它返回一個 Future<XFile>

將這些操作包裝在 try / catch 塊中以處理可能發生的任何錯誤是一種良好的實踐。

dart
FloatingActionButton(
  // Provide an onPressed callback.
  onPressed: () async {
    // Take the Picture in a try / catch block. If anything goes wrong,
    // catch the error.
    try {
      // Ensure that the camera is initialized.
      await _initializeControllerFuture;

      // Attempt to take a picture and then get the location
      // where the image file is saved.
      final image = await _controller.takePicture();
    } catch (e) {
      // If an error occurs, log the error to the console.
      print(e);
    }
  },
  child: const Icon(Icons.camera_alt),
)

6. 使用 Image widget 顯示照片

#

如果拍照成功,您就可以使用 Image widget 顯示已儲存的照片。在此情況下,照片將作為檔案儲存在裝置上。

因此,您必須為 Image.file 建構函式提供一個 File。您可以透過傳遞上一步中建立的路徑來建立 File 類的例項。

dart
Image.file(File('path/to/my/picture.png'));

完整示例

#
dart
import 'dart:async';
import 'dart:io';

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

Future<void> main() async {
  // Ensure that plugin services are initialized so that `availableCameras()`
  // can be called before `runApp()`
  WidgetsFlutterBinding.ensureInitialized();

  // Obtain a list of the available cameras on the device.
  final cameras = await availableCameras();

  // Get a specific camera from the list of available cameras.
  final firstCamera = cameras.first;

  runApp(
    MaterialApp(
      theme: ThemeData.dark(),
      home: TakePictureScreen(
        // Pass the appropriate camera to the TakePictureScreen widget.
        camera: firstCamera,
      ),
    ),
  );
}

// A screen that allows users to take a picture using a given camera.
class TakePictureScreen extends StatefulWidget {
  const TakePictureScreen({super.key, required this.camera});

  final CameraDescription camera;

  @override
  TakePictureScreenState createState() => TakePictureScreenState();
}

class TakePictureScreenState extends State<TakePictureScreen> {
  late CameraController _controller;
  late Future<void> _initializeControllerFuture;

  @override
  void initState() {
    super.initState();
    // To display the current output from the Camera,
    // create a CameraController.
    _controller = CameraController(
      // Get a specific camera from the list of available cameras.
      widget.camera,
      // Define the resolution to use.
      ResolutionPreset.medium,
    );

    // Next, initialize the controller. This returns a Future.
    _initializeControllerFuture = _controller.initialize();
  }

  @override
  void dispose() {
    // Dispose of the controller when the widget is disposed.
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Take a picture')),
      // You must wait until the controller is initialized before displaying the
      // camera preview. Use a FutureBuilder to display a loading spinner until the
      // controller has finished initializing.
      body: FutureBuilder<void>(
        future: _initializeControllerFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            // If the Future is complete, display the preview.
            return CameraPreview(_controller);
          } else {
            // Otherwise, display a loading indicator.
            return const Center(child: CircularProgressIndicator());
          }
        },
      ),
      floatingActionButton: FloatingActionButton(
        // Provide an onPressed callback.
        onPressed: () async {
          // Take the Picture in a try / catch block. If anything goes wrong,
          // catch the error.
          try {
            // Ensure that the camera is initialized.
            await _initializeControllerFuture;

            // Attempt to take a picture and get the file `image`
            // where it was saved.
            final image = await _controller.takePicture();

            if (!context.mounted) return;

            // If the picture was taken, display it on a new screen.
            await Navigator.of(context).push(
              MaterialPageRoute<void>(
                builder: (context) => DisplayPictureScreen(
                  // Pass the automatically generated path to
                  // the DisplayPictureScreen widget.
                  imagePath: image.path,
                ),
              ),
            );
          } catch (e) {
            // If an error occurs, log the error to the console.
            print(e);
          }
        },
        child: const Icon(Icons.camera_alt),
      ),
    );
  }
}

// A widget that displays the picture taken by the user.
class DisplayPictureScreen extends StatelessWidget {
  final String imagePath;

  const DisplayPictureScreen({super.key, required this.imagePath});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Display the Picture')),
      // The image is stored as a file on the device. Use the `Image.file`
      // constructor with the given path to display the image.
      body: Image.file(File(imagePath)),
    );
  }
}