使用相機拍照
許多應用程式都需要透過裝置的相機來拍攝照片與錄製影片。Flutter 提供了 camera 外掛程式來達成這個目的。camera 外掛程式提供了取得可用相機清單、顯示特定相機預覽畫面,以及拍攝照片或錄影的相關工具。
本教學將示範如何使用 camera 外掛程式來顯示預覽畫面、拍照並顯示照片,步驟如下:
- 新增所需的相依套件。
- 取得可用相機的清單。
- 建立並初始化
CameraController。 - 使用
CameraPreview顯示相機畫面。 - 透過
CameraController拍攝照片。 - 使用
Image元件顯示照片。
1. 新增所需的相依套件
#完成本教學需在應用程式中加入三個相依套件:
camera- 提供操作裝置相機的相關工具。
path_provider- 用於尋找儲存圖片的正確路徑。
path- 建立可在任何平台運作的路徑。
要將這些套件加入相依套件,請執行 flutter pub add:
flutter pub add camera path_provider path2. 取得可用相機的清單
#接下來,使用 camera 插件來取得可用相機的清單。
// 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。 這個流程會建立與裝置相機的連線,讓你可以控制相機, 並顯示相機畫面的預覽。
- 使用一個配套的
State類別建立StatefulWidget。 - 在
State類別中新增一個變數來儲存CameraController。 - 在
State類別中新增一個變數來儲存從CameraController.initialize()回傳的Future。 - 在
initState()方法中建立並初始化控制器。 - 在
dispose()方法中釋放控制器資源。
// 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 的用途。
// 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 上,新的圖片會儲存在各自的快取目錄中,path 會將該位置的路徑回傳於 XFile。
在這個範例中,建立一個 FloatingActionButton,當使用者點擊按鈕時,會透過 CameraController 拍攝一張照片。
拍攝照片需要兩個步驟:
- 確認相機已經初始化。
- 使用 controller 拍攝照片,並確保它回傳一個
Future<XFile>。
建議將這些操作包裹在 try / catch 區塊中,以處理可能發生的錯誤。
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)來顯示儲存的圖片。在這個案例中,圖片是以檔案的形式儲存在裝置上。
因此,你必須將 File 傳遞給 Image.file 建構函式。你可以將前一步驟產生的路徑傳入,建立 File 類別的實例。
Image.file(File('path/to/my/picture.png'));完整範例
#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)),
);
}
}