Flutter - 图片/视频选择器(支持拍照及录制视频)

Posted 西半球

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Flutter - 图片/视频选择器(支持拍照及录制视频)相关的知识,希望对你有一定的参考价值。

demo 地址: https://github.com/iotjin/jh_flutter_demo
代码不定时更新,请前往github查看最新代码

Flutter - 图片/视频选择器(支持拍照及录制视频)

之前基于一些图片选择库写了个图片选择器的组件Flutter - 实现多图选择,相机拍照功能,但是升级flutter2.0后作者不在更新了,一直没再管

升级flutter3.0后,基于wechat_assets_pickerwechat_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

权限处理

<!--拍照权限,允许访问摄像头进行拍照-->
<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 - 图片/视频选择器(支持拍照及录制视频)的主要内容,如果未能解决你的问题,请参考以下文章

android之图片选择器ImageSelector的使用

Flutter 调用原生硬件 Api 实现照相机 拍照和相册选择 以及拍照上传

Uniapp 图片选择插件(支持视频音频) Ba-MediaPicker

Uniapp 图片选择插件(支持视频音频) Ba-MediaPicker

Android 图片选择器,丰富的配置选项,极大程度的简化使用

Android图片选择器 多选框架