在移动应用开发中,调用设备原生功能(如相机、相册、定位等)是提升用户体验的关键。Flutter 提供了丰富的第三方插件,让我们可以轻松实现这些功能。本节课将详细讲解设备功能调用的核心知识点,包括权限管理、相机 / 相册调用、定位获取,并通过综合实例演示实际应用。
一、权限管理:permission_handler
插件
调用设备功能前必须获得用户授权,permission_handler
是 Flutter 中最常用的权限管理插件,支持 Android 和 iOS 平台的几乎所有权限类型。
1. 安装与配置
步骤 1:添加依赖
在 pubspec.yaml
中添加插件:
yaml
dependencies:
permission_handler: ^12.0.1 # 建议使用最新版本
执行 flutter pub get
安装。
步骤 2:平台权限配置
权限需要在原生配置文件中声明,否则调用时会直接失败。
- Android 配置
编辑android/app/src/main/AndroidManifest.xml
,添加需要的权限(根据功能添加):
xml
<!-- 相机权限 -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- 读写存储权限 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- 定位权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
iOS 配置
编辑 ios/Runner/Info.plist
,添加权限描述(用户会看到这些说明):
xml
<!-- 相机权限描述 -->
<key>NSCameraUsageDescription</key>
<string>需要相机权限用于拍照</string>
<!-- 相册权限描述 -->
<key>NSPhotoLibraryUsageDescription</key>
<string>需要相册权限用于选择图片</string>
<!-- 定位权限描述 -->
<key>NSLocationWhenInUseUsageDescription</key>
<string>需要定位权限用于获取当前位置</string>
2. 权限操作核心方法
permission_handler
提供了简洁的 API 用于权限检查和请求:
dart
import 'package:permission_handler/permission_handler.dart';
// 检查权限状态
Future<bool> checkPermission(Permission permission) async {
PermissionStatus status = await permission.status;
return status.isGranted; // 返回是否已授权
}
// 请求权限
Future<bool> requestPermission(Permission permission) async {
PermissionStatus status = await permission.request();
return status.isGranted;
}
// 打开应用权限设置页面(当用户拒绝且勾选"不再询问"时使用)
Future<void> openAppSet() async {
await openAppSettings();
}
3. 常用权限常量
Permission
类包含所有支持的权限,常用的有:
Permission.camera
:相机权限Permission.photos
/Permission.storage
:相册 / 存储权限Permission.locationWhenInUse
:使用中定位权限Permission.locationAlways
:始终允许定位权限
二、调用相机 / 相册:image_picker
插件
image_picker
是 Flutter 官方推荐的媒体选择插件,支持从相机拍照或从相册选择图片 / 视频。
1. 安装与配置
步骤 1:添加依赖
yaml
dependencies:
image_picker: ^1.1.2 # 建议使用最新版本
执行 flutter pub get
安装。
步骤 2:平台额外配置(部分场景需要)
- iOS 视频选择 :如需选择视频,需在
Info.plist
中添加NSCameraUsageDescription
(同相机权限) - Android 10+ 存储 :如需保存图片到公共目录,需在
AndroidManifest.xml
中添加:
xml
<application ...>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Android 10+ 需添加 -->
<application ... android:requestLegacyExternalStorage="true">
</application>
2. 核心功能实现
(1)从相机拍照
dart
import 'package:image_picker/image_picker.dart';
// 从相机获取图片
Future<XFile?> takePhoto() async {
final ImagePicker picker = ImagePicker();
// 调用相机,返回XFile(包含图片路径等信息)
final XFile? photo = await picker.pickImage(
source: ImageSource.camera, // 来源为相机
imageQuality: 80, // 图片质量(0-100)
maxWidth: 1080, // 最大宽度
);
return photo;
}
(2)从相册选择图片
dart
// 从相册选择图片
Future<XFile?> pickImageFromGallery() async {
final ImagePicker picker = ImagePicker();
final XFile? image = await picker.pickImage(
source: ImageSource.gallery, // 来源为相册
imageQuality: 80,
);
return image;
}
(3)显示选中的图片
获取 XFile
后,可通过 Image.file
显示图片:
dart
import 'dart:io';
XFile? selectedImage; // 存储选中的图片
// 拍照后更新图片
void _onTakePhoto() async {
XFile? photo = await takePhoto();
if (photo != null) {
setState(() {
selectedImage = photo;
});
}
}
// 界面中显示图片
Widget buildImagePreview() {
if (selectedImage == null) {
return Text("未选择图片");
}
return Image.file(
File(selectedImage!.path),
width: 300,
height: 300,
fit: BoxFit.cover,
);
}
三、定位功能:geolocator
插件
geolocator
提供了跨平台的定位服务,支持获取经纬度、海拔、速度等信息,还能监听位置变化。
1. 安装与配置
步骤 1:添加依赖
yaml
dependencies:
geolocator: ^14.0.2 # 建议使用最新版本
执行 flutter pub get
安装。
步骤 2:平台权限配置
定位功能需要额外的权限配置(已在 permission_handler
部分添加,这里补充细节):
- Android :
除了ACCESS_FINE_LOCATION
(精确定位)和ACCESS_COARSE_LOCATION
(粗略定位),如需后台定位,需添加:
xml
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
iOS :
如需后台定位,需在 Info.plist
中添加:
xml
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>需要始终允许定位权限用于后台定位</string>
<key>UIBackgroundModes</key>
<array>
<string>location</string>
</array>
2. 核心功能实现
(1)获取当前位置
dart
import 'package:geolocator/geolocator.dart';
// 获取当前经纬度
Future<Position?> getCurrentLocation() async {
// 检查定位服务是否开启
bool isLocationEnabled = await Geolocator.isLocationServiceEnabled();
if (!isLocationEnabled) {
// 提示用户开启定位服务
return null;
}
// 检查定位权限
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
// 请求权限
permission = await Geolocator.requestPermission();
if (permission != LocationPermission.whileInUse &&
permission != LocationPermission.always) {
// 权限被拒绝
return null;
}
}
// 获取当前位置(最多等待10秒)
try {
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high, // 高精度
timeLimit: Duration(seconds: 10),
);
return position; // 包含latitude(纬度)和longitude(经度)
} catch (e) {
print("获取位置失败:$e");
return null;
}
}
(2)监听位置变化
dart
// 监听位置变化(每移动10米或30秒更新一次)
StreamSubscription<Position>? positionStream;
void startListeningLocation() {
positionStream = Geolocator.getPositionStream(
locationSettings: LocationSettings(
accuracy: LocationAccuracy.high,
distanceFilter: 10, // 移动10米以上才更新
intervalDuration: Duration(seconds: 30), // 至少30秒更新一次
),
).listen((Position position) {
print("当前位置:${position.latitude}, ${position.longitude}");
});
}
// 停止监听
void stopListeningLocation() {
if (positionStream != null) {
positionStream!.cancel();
positionStream = null;
}
}
四、综合实例:拍照上传与获取位置
下面实现一个完整页面,包含以下功能:
- 检查并请求相机、定位权限
- 拍照或从相册选择图片
- 获取当前位置经纬度
- 模拟图片上传(显示上传状态)
完整代码实现
dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:image_picker/image_picker.dart';
import 'package:geolocator/geolocator.dart';
class DeviceDemoPage extends StatefulWidget {
const DeviceDemoPage({super.key});
@override
State<DeviceDemoPage> createState() => _DeviceDemoPageState();
}
class _DeviceDemoPageState extends State<DeviceDemoPage> {
XFile? _selectedImage; // 选中的图片
Position? _currentPosition; // 当前位置
bool _isUploading = false; // 是否正在上传
// 检查并请求权限
Future<bool> _checkAndRequestPermission(Permission permission) async {
bool isGranted = await checkPermission(permission);
if (!isGranted) {
isGranted = await requestPermission(permission);
}
return isGranted;
}
// 拍照
void _takePhoto() async {
bool hasCameraPermission = await _checkAndRequestPermission(Permission.camera);
if (!hasCameraPermission) {
_showSnackBar("请授予相机权限");
return;
}
XFile? photo = await ImagePicker().pickImage(
source: ImageSource.camera,
imageQuality: 80,
);
if (photo != null) {
setState(() => _selectedImage = photo);
}
}
// 从相册选择
void _pickFromGallery() async {
bool hasStoragePermission = await _checkAndRequestPermission(Permission.photos);
if (!hasStoragePermission) {
_showSnackBar("请授予相册权限");
return;
}
XFile? image = await ImagePicker().pickImage(
source: ImageSource.gallery,
imageQuality: 80,
);
if (image != null) {
setState(() => _selectedImage = image);
}
}
// 获取当前位置
void _getCurrentLocation() async {
bool hasLocationPermission = await _checkAndRequestPermission(Permission.locationWhenInUse);
if (!hasLocationPermission) {
_showSnackBar("请授予定位权限");
return;
}
Position? position = await getCurrentLocation();
if (position != null) {
setState(() => _currentPosition = position);
_showSnackBar("已获取位置信息");
} else {
_showSnackBar("获取位置失败,请检查定位服务");
}
}
// 模拟上传图片
void _uploadImage() async {
if (_selectedImage == null) {
_showSnackBar("请先选择图片");
return;
}
setState(() => _isUploading = true);
// 模拟网络请求(2秒后完成)
await Future.delayed(Duration(seconds: 2));
setState(() => _isUploading = false);
_showSnackBar("图片上传成功");
}
// 显示提示
void _showSnackBar(String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("设备功能演示")),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 图片预览
_selectedImage == null
? const Text("未选择图片", style: TextStyle(fontSize: 16))
: Image.file(
File(_selectedImage!.path),
width: 300,
height: 300,
fit: BoxFit.cover,
),
const SizedBox(height: 20),
// 操作按钮
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton.icon(
onPressed: _takePhoto,
icon: const Icon(Icons.camera_alt),
label: const Text("拍照"),
),
const SizedBox(width: 16),
ElevatedButton.icon(
onPressed: _pickFromGallery,
icon: const Icon(Icons.photo_library),
label: const Text("相册"),
),
],
),
const SizedBox(height: 16),
// 定位信息
ElevatedButton.icon(
onPressed: _getCurrentLocation,
icon: const Icon(Icons.location_on),
label: const Text("获取当前位置"),
),
if (_currentPosition != null)
Padding(
padding: const EdgeInsets.all(16),
child: Text(
"当前位置:\n纬度:${_currentPosition!.latitude}\n经度:${_currentPosition!.longitude}",
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 16),
),
),
const SizedBox(height: 16),
// 上传按钮
_isUploading
? const CircularProgressIndicator()
: ElevatedButton.icon(
onPressed: _uploadImage,
icon: const Icon(Icons.upload),
label: const Text("上传图片"),
),
],
),
),
);
}
}
// 权限检查与请求的工具方法(可抽离到工具类)
Future<bool> checkPermission(Permission permission) async {
return (await permission.status).isGranted;
}
Future<bool> requestPermission(Permission permission) async {
return (await permission.request()).isGranted;
}
// 定位工具方法(可抽离到工具类)
Future<Position?> getCurrentLocation() async {
// 检查定位服务是否开启
if (!await Geolocator.isLocationServiceEnabled()) {
return null;
}
// 检查权限
LocationPermission permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission != LocationPermission.whileInUse &&
permission != LocationPermission.always) {
return null;
}
}
// 获取位置
try {
return await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
timeLimit: const Duration(seconds: 10),
);
} catch (e) {
return null;
}
}
代码说明
- 权限管理 :通过封装的
checkPermission
和requestPermission
方法,统一处理权限逻辑,确保调用设备功能前已获得授权。 - 图片处理 :使用
image_picker
分别实现拍照和相册选择功能,并通过Image.file
显示选中的图片。 - 定位功能 :通过
geolocator
获取经纬度,包含定位服务检查和权限处理,确保定位功能可靠。 - 用户体验:添加加载状态(上传时显示进度条)和提示信息(SnackBar),提升交互友好度。
五、扩展知识:其他常用设备功能插件
除了本节课讲解的功能,以下插件也常用于设备功能调用:
url_launcher
:调用系统浏览器、拨打电话、发送邮件等share_plus
:分享文本、图片到其他应用flutter_local_notifications
:本地通知功能connectivity_plus
:网络连接状态监听package_info_plus
:获取应用版本、名称等信息