Flutter - 图片/视频选择器(支持拍照及录制视频)
Posted 西半球
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter - 图片/视频选择器(支持拍照及录制视频)相关的知识,希望对你有一定的参考价值。
demo 地址: https://github.com/iotjin/jh_flutter_demo
代码不定时更新,请前往github查看最新代码
Flutter - 图片/视频选择器(支持拍照及录制视频)
序
之前基于一些图片选择库写了个图片选择器的组件Flutter - 实现多图选择,相机拍照功能,但是升级flutter2.0后作者不在更新了,一直没再管
升级flutter3.0后,基于
wechat_assets_picker
和wechat_camera_picker
重新封装了个图片/视频选择器,这两个库是基于微信UI实现的,选择图片和拍照录像效果类似于微信,颜值比较高,简单封装即可使用
支持
- 支持拍照/录制视频以及从图库选择资源
- 设置资源选择类型(图片、视频、图片和视频)
- 设置最大选择数量(默认9)
- 设置最大视频录入时长(默认15秒)
- 一行显示几个(默认3)
- 图片视频全屏查看
- 主题色跟随系统设置切换
效果图
库引用
# 手机权限 https://pub.flutter-io.cn/packages/permission_handler
permission_handler: ^9.2.0
# 图片视频选择器(微信UI) https://pub.flutter-io.cn/packages/wechat_assets_picker
wechat_assets_picker: ^8.0.2
# 拍照与录像(微信UI) https://pub.flutter-io.cn/packages/wechat_camera_picker
wechat_camera_picker: ^3.5.0+1
权限处理
- android -
AndroidManifest.xml
<!--拍照权限,允许访问摄像头进行拍照-->
<uses-permission android:name="android.permission.CAMERA" />
<!-- 闪光灯 -->
<uses-permission android:name="android.permission.FLASHLIGHT" />
<!-- 写权限 -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29" />
<activity
android:exported="true"
android:requestLegacyExternalStorage="true">
</activity>
<!-- targetSdkVersion 31以上 (android12) 需要设置 android:exported="true" -->
- ios -
Info.plist
<key>NSCameraUsageDescription</key>
<string>是否允许"APP"使用您的相机,以便于进行拍照</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>是否允许"APP"访问您的相册,以便于保存图片</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>是否允许"APP"访问您的相册,以便于进行图片上传等操作</string>
<key>NSMicrophoneUsageDescription</key>
<string>是否允许"APP"使用您的麦克风,以便于视频录制、语音识别、语音聊天</string>
因为我在底部弹框之前使用
permission_handler
做了权限判断,所以除了在info
中加权限描述,还需要在Podfile
文件加,否则iOS请求权限不弹框
# Permission权限配置
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [
'$(inherited)',
## dart: PermissionGroup.camera
'PERMISSION_CAMERA=1',
## dart: PermissionGroup.microphone
'PERMISSION_MICROPHONE=1',
## dart: PermissionGroup.photos
'PERMISSION_PHOTOS=1',
]
Example
import 'package:flutter/material.dart';
import '/jh_common/widgets/jh_asset_picker.dart';
import '/base_appbar.dart';
class PhotoSelectTest extends StatelessWidget
Widget build(BuildContext context)
return Scaffold(
appBar: backAppBar(context, 'PhotoSelect'),
body: _body(),
);
Widget _body()
return Container(
color: Colors.yellow,
padding: EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('请选择图片(最多3张)'),
SizedBox(height: 6),
Container(
padding: EdgeInsets.only(left: 5),
child: JhAssetPicker(
assetType: AssetType.image,
maxAssets: 3,
bgColor: Colors.red,
callBack: (assetEntityList) async
print('assetEntityList-------------');
print(assetEntityList);
if (assetEntityList.isNotEmpty)
var asset = assetEntityList[0];
print(await asset.file);
print(await asset.originFile);
print('assetEntityList-------------');
,
),
),
SizedBox(height: 6),
Text('请选择视频(最多1个)'),
Container(
padding: EdgeInsets.only(left: 5),
child: JhAssetPicker(
maxAssets: 1,
assetType: AssetType.video,
bgColor: Colors.red,
callBack: (assetEntityList)
print('assetEntityList-------------');
print(assetEntityList);
print('assetEntityList-------------');
,
),
),
Text('请选择图片或视频(一行展示4个)'),
Container(
padding: EdgeInsets.only(left: 5),
child: JhAssetPicker(
lineCount: 4,
bgColor: Colors.red,
callBack: (assetEntityList)
print('assetEntityList-------------');
print(assetEntityList);
print('assetEntityList-------------');
,
),
),
],
));
源码
/// jh_asset_picker.dart
///
/// Created by iotjin on 2022/09/10.
/// description: 图片/视频选择器(支持拍照及录制视频) 封装wechat_assets_picker、wechat_camera_picker
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart';
import 'package:wechat_assets_picker/wechat_assets_picker.dart';
import 'package:wechat_camera_picker/wechat_camera_picker.dart';
import '/jh_common/widgets/jh_bottom_sheet.dart';
import '/jh_common/widgets/jh_progress_hud.dart';
import '/project/configs/colors.dart';
import '/project/provider/theme_provider.dart';
// 最大数量
const int _maxAssets = 9;
// 录制视频最长时长, 默认为 15 秒,可以使用 `null` 来设置无限制的视频录制
const Duration _maximumRecordingDuration = Duration(seconds: 15);
// 一行显示几个
const int _lineCount = 3;
// 每个GridView item间距(GridView四周与内部item间距在此统一设置)
const double _itemSpace = 5.0;
// 右上角删除按钮大小
const double _deleteBtnWH = 20.0;
// 默认添加图片
const String _addBtnIcon = 'assets/images/selectPhoto_add.png';
// 默认删除按钮图片
const String _deleteBtnIcon = 'assets/images/selectPhoto_close.png';
// 默认背景色
const Color _bgColor = Colors.transparent;
typedef _CallBack = void Function(List<AssetEntity> assetEntityList);
enum AssetType
image,
video,
imageAndVideo,
class JhAssetPicker extends StatefulWidget
const JhAssetPicker(
Key? key,
this.assetType = AssetType.image,
this.maxAssets = _maxAssets,
this.lineCount = _lineCount,
this.itemSpace = _itemSpace,
this.maximumRecordingDuration = _maximumRecordingDuration,
this.bgColor = _bgColor,
this.callBack,
) : super(key: key);
final AssetType assetType; // 资源类型
final int maxAssets; // 最大数量
final int lineCount; // 一行显示几个
final double itemSpace; // 每个GridView item间距(GridView四周与内部item间距在此统一设置)
final Duration? maximumRecordingDuration; // 录制视频最长时长, 默认为 15 秒,可以使用 `null` 来设置无限制的视频录制
final Color bgColor; // 背景色
final _CallBack? callBack; // 选择回调
_JhAssetPickerState createState() => _JhAssetPickerState();
class _JhAssetPickerState extends State<JhAssetPicker>
List<AssetEntity> _selectedAssets = [];
Color _themeColor = KColors.kThemeColor;
void initState()
// TODO: implement initState
super.initState();
Widget build(BuildContext context)
// TODO: 通过ThemeProvider进行主题管理
final provider = Provider.of<ThemeProvider>(context);
_themeColor = KColors.dynamicColor(context, provider.getThemeColor(), KColors.kThemeColor);
var allCount = _selectedAssets.length + 1;
return Container(
color: widget.bgColor,
child: GridView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
//可以直接指定每行(列)显示多少个Item
crossAxisCount: widget.lineCount, //一行的Widget数量
crossAxisSpacing: widget.itemSpace, //水平间距
mainAxisSpacing: widget.itemSpace, //垂直间距
childAspectRatio: 1.0, //子Widget宽高比例
),
//GridView内边距
padding: EdgeInsets.all(widget.itemSpace),
itemCount: _selectedAssets.length == widget.maxAssets ? _selectedAssets.length : allCount,
itemBuilder: (context, index)
if (_selectedAssets.length == widget.maxAssets)
return _itemWidget(index);
if (index == allCount - 1)
return _addBtnWidget();
else
return _itemWidget(index);
,
),
);
// 添加按钮
Widget _addBtnWidget()
return GestureDetector(
child: Image(image: AssetImage(_addBtnIcon)),
onTap: () => _showBottomSheet(),
);
// 图片和删除按钮
Widget _itemWidget(index)
return GestureDetector(
child: Container(
color: Colors.transparent,
child: Stack(alignment: Alignment.topRight, children: <Widget>[
ConstrainedBox(
child: _loadAsset(_selectedAssets[index]),
constraints: BoxConstraints.expand(),
),
GestureDetector(
child: Image(
image: AssetImage(_deleteBtnIcon),
width: _deleteBtnWH,
height: _deleteBtnWH,
),
onTap: () => _deleteAsset(index),
)
]),
),
onTap: () => _clickAsset(index),
);
Widget _loadAsset(AssetEntity asset)
return Image(image: AssetEntityImageProvider(asset), fit: BoxFit.cover);
void _deleteAsset(index)
setState(()
_selectedAssets.removeAt(index);
// 选择回调
widget.callBack?.call(_selectedAssets);
);
// 全屏查看
void _clickAsset(index)
AssetPickerViewer.pushToViewer(
context,
currentIndex: index,
previewAssets: _selectedAssets,
themeData: AssetPicker.themeData(_themeColor),
);
// 点击添加按钮
void _showBottomSheet()
JhBottomSheet.showText(context, dataArr: ['拍照', '相册'], title: '请选择', clickCallback: (index, str) async
if (index == 1)
_openCamera();
if (index == 2)
_openAlbum();
);
// 相册选择
Future<void> _openAlbum() async
// 相册权限
final PermissionState ps = await PhotoManager.requestPermissionExtend();
if (ps != PermissionState.authorized && ps != PermissionState.limited)
JhProgressHUD.showText('暂无相册权限,请前往设置开启权限');
return;
RequestType requestType = RequestType.image;
if (widget.assetType == AssetType.video)
requestType = RequestType.video;
if (widget.assetType == AssetType.imageAndVideo)
requestType = RequestType.common;
final List<AssetEntity>? result = await AssetPicker.pickAssets(
context,
pickerConfig: AssetPickerConfig(
maxAssets: widget.maxAssets,
requestType: requestType,
selectedAssets: _selectedAssets,
themeColor: _themeColor,
// textDelegate: const EnglishAssetPickerTextDelegate(),
),
);
if (result != null)
setState(()
_selectedAssets = result;
);
// 相册选择回调
widget.callBack?.call(result);
// 拍照或录像
Future<void> _openCamera() async
// 相机权限
var isGrantedCamera = await Permission.camera.request().isGranted;
if (!isGrantedCamera)
JhProgressHUD.showText('暂无相机权限,请前往设置开启权限');
return;
if (widget.assetType != AssetType.image)
// 麦克风权限
var isGrantedMicrophone = await Permission.microphone.request().isGranted;
if (!isGrantedMicrophone)
JhProgressHUD.showText('暂无麦克风权限,请前往设置开启权限');
return;
// 相册权限
final PermissionState ps = await PhotoManager.requestPermissionExtend();
if (ps != PermissionState.authorized && ps != PermissionState.limited)
JhProgressHUD.showText('暂无相册权限,请前往设置开启权限');
return;
final AssetEntity? result = await CameraPicker.pickFromCamera(
context,
pickerConfig: CameraPickerConfig(
// 是否可以录像
enabl以上是关于Flutter - 图片/视频选择器(支持拍照及录制视频)的主要内容,如果未能解决你的问题,请参考以下文章
Flutter 调用原生硬件 Api 实现照相机 拍照和相册选择 以及拍照上传
Uniapp 图片选择插件(支持视频音频) Ba-MediaPicker
Uniapp 图片选择插件(支持视频音频) Ba-MediaPicker