使用相机拍照
许多应用程序需要使用设备的相机来拍摄照片和视频。Flutter 提供了 camera
插件来实现此目的。camera
插件提供了获取可用相机列表、显示特定相机预览以及拍摄照片或视频的工具。
本指南演示了如何使用 camera
插件通过以下步骤显示预览、拍照并显示照片:
- 添加必要的依赖项。
- 获取可用相机列表。
- 创建并初始化
CameraController
。 - 使用
CameraPreview
显示相机画面。 - 使用
CameraController
拍照。 - 使用
Image
widget 显示照片。
1. 添加必要的依赖项
#要完成本指南,您需要在应用中添加三个依赖项:
camera
- 提供与设备相机交互的工具。
path_provider
- 查找存储图像的正确路径。
path
- 创建可在任何平台上使用的路径。
要将这些包添加为依赖项,请运行 flutter pub add
flutter pub add camera path_provider path
2. 获取可用相机列表
#接下来,使用 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 上,新图像都存储在各自的缓存目录中,并且 XFile
返回指向该位置的 path
。
在此示例中,创建一个 FloatingActionButton
,当用户点击该按钮时,它会使用 CameraController
拍照。
拍照需要 2 个步骤:
- 确保相机已初始化。
- 使用控制器拍照,并确保它返回一个
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 显示已保存的照片。在此情况下,照片将作为文件存储在设备上。
因此,您必须为 Image.file
构造函数提供一个 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)),
);
}
}