跳到主要內容

使用相機拍照

許多應用程式需要使用裝置的相機來拍照和錄影。Flutter 為此提供了 camera 外掛程式。camera 外掛程式提供了工具來取得可用相機的列表、顯示來自特定相機的預覽畫面,以及拍照或錄影。

本食譜示範如何使用 camera 外掛程式來顯示預覽畫面、拍照,並使用下列步驟來顯示它

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

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 小工具來顯示相機畫面的預覽。

使用 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 拍照

#

您可以使用 CameraControllertakePicture() 方法來拍照,該方法會傳回 XFile,這是一個跨平台的簡化 File 抽象。在 Android 和 IOS 上,新影像會儲存在各自的快取目錄中,而該位置的 path 會在 XFile 中傳回。

在本範例中,建立一個 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 小工具顯示照片

#

如果您成功拍照,則可以使用 Image 小工具顯示儲存的照片。在這種情況下,照片會以檔案形式儲存在裝置上。

因此,您必須為 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(
                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)),
    );
  }
}