美文网首页Flutter
Flutter之选择图片视频与上传

Flutter之选择图片视频与上传

作者: h2coder | 来源:发表于2023-08-01 22:38 被阅读0次

    效果展示

    文件上传.png

    项目地址

    依赖

    # 网络请求
    dio: ^5.1.2
    dio_cookie_manager: ^3.0.0
    # 图片加载
    fast_cached_network_image: ^1.2.0
    # 图片、视频选择
    image_picker: ^0.8.7+5
    # 权限请求
    permission_handler: ^10.2.0
    # Toast
    fluttertoast: ^8.0.9
    

    权限申请工具类

    import 'package:permission_handler/permission_handler.dart';
    
    /// 权限工具类
    class PermissionUtil {
      /// 申请摄像头权限
      static Future<bool> requestCameraPermission() async {
        await [
          Permission.camera,
        ].request();
    
        if (await Permission.camera.isGranted) {
          return true;
        } else {
          return false;
        }
      }
    
      /// 申请存储权限
      static Future<bool> requestStoragePermission() async {
        await [
          Permission.storage,
        ].request();
    
        if (await Permission.storage.isGranted) {
          return true;
        } else {
          return false;
        }
      }
    }
    

    Toast工具类

    // toast_util.dart
    
    import 'package:flutter/material.dart';
    import 'package:fluttertoast/fluttertoast.dart';
    
    /// Toast 工具类
    class ToastUtil {
      static toast(String msg) {
        Fluttertoast.showToast(
            msg: msg,
            toastLength: Toast.LENGTH_SHORT,
            gravity: ToastGravity.BOTTOM,
            timeInSecForIosWeb: 1,
            backgroundColor: Colors.black,
            textColor: Colors.white,
            fontSize: 16.0);
      }
    }
    

    文件上传结果实体类

    // file_upload_result.dart
    
    class FileUploadResult {
      int? code;
      String? msg;
      String? data;
    
      FileUploadResult({this.code, this.msg, this.data});
    
      FileUploadResult.fromJson(Map<String, dynamic> json) {
        code = json['code'];
        msg = json['msg'];
        data = json['data'];
      }
    
      Map<String, dynamic> toJson() {
        final Map<String, dynamic> data = <String, dynamic>{};
        data['code'] = code;
        data['msg'] = msg;
        data['data'] = this.data;
        return data;
      }
    }
    

    文件上传工具类

    // http_file_util.dart
    
    import 'dart:convert';
    import 'dart:io';
    
    import 'package:cookie_jar/cookie_jar.dart';
    import 'package:dio/dio.dart';
    import 'package:dio/io.dart';
    import 'package:dio_cookie_manager/dio_cookie_manager.dart';
    import 'package:flutter/foundation.dart';
    import 'package:flutter_image_picker/model/file_upload_result.dart';
    import 'package:flutter_image_picker/url.dart';
    
    /// 上传成功的回调
    typedef SuccessCallback = Function(String uploadFileUrl);
    
    /// 上传失败的回调
    typedef ErrorCallback = Function(String errorMsg);
    
    /// 文件上传工具类
    class HttpFileUtil {
      /// 发起文件上传请求
      static void fileUpload(SuccessCallback? callBack,
          {FormData? formData, ErrorCallback? errorCallBack}) async {
        _post(Url.fileUploadUrl, callBack,
            formData: formData, errorCallBack: errorCallBack);
      }
    
      /// POST请求
      static void _post(String url, SuccessCallback? callBack,
          {FormData? formData, ErrorCallback? errorCallBack}) async {
        String errorMsg = "";
        int statusCode;
    
        BaseOptions baseOptions;
        try {
          baseOptions = BaseOptions(
              baseUrl: Url.getBaseUrl(),
              connectTimeout: const Duration(milliseconds: 30000),
              receiveTimeout: const Duration(milliseconds: 30000),
              responseType: ResponseType.plain,
              contentType: "multipart/form-data; boundary=${formData?.boundary}",
              headers: {
                HttpHeaders.contentTypeHeader:
                    "multipart/form-data; boundary=${formData?.boundary}",
              });
        } catch (exception) {
          if (kDebugMode) {
            print(exception);
          }
          return;
        }
    
        // 构建Dio请求客户端
        Dio dio = Dio(baseOptions);
        if (!kIsWeb) {
          // 添加Cookie拦截器
          dio.interceptors.add(CookieManager(CookieJar()));
        }
        if (!kIsWeb) {
          // 简单粗暴方式处理校验证书
          (dio.httpClientAdapter as IOHttpClientAdapter).onHttpClientCreate =
              (client) {
            client.badCertificateCallback =
                (X509Certificate cert, String host, int port) {
              return true;
            };
            return client;
          };
        }
    
        // 发起请求
        try {
          Response response;
          if (formData != null) {
            response = await dio.post(
              url,
              data: formData,
            );
          } else {
            response = await dio.post(url);
          }
          // 获取响应状态码
          statusCode = response.statusCode ?? -1;
    
          // 处理状态码错误
          if (statusCode < 0) {
            errorMsg = "网络请求错误,状态码:$statusCode";
            _handError(errorCallBack, errorMsg: errorMsg);
            return;
          }
    
          // 解析响应结果
          if (callBack != null) {
            var result = response.data.toString();
            if (kDebugMode) {
              print("返回数据:$result");
            }
            var data = json.decode(result);
            FileUploadResult resultData = FileUploadResult.fromJson(data);
            // 成功
            if (resultData.code == 1) {
              callBack(resultData.data ?? "");
            } else {
              // 失败
              _handError(errorCallBack, errorMsg: resultData.msg);
            }
          }
        } catch (exception) {
          // 解析失败
          String code = exception.toString();
          String errorString = code.contains("timed out") ? "请求超时" : "服务异常";
          _handError(errorCallBack, errorMsg: errorString);
        }
      }
    
      /// 异常处理
      static void _handError(ErrorCallback? errorCallback,
          {String? errorMsg = ""}) {
        if (errorCallback != null) {
          errorCallback(errorMsg!);
        }
        if (kDebugMode) {
          print("<net> errorMsg :$errorMsg");
        }
      }
    }
    

    上传页面

    // upload_page.dart
    
    import 'package:dio/dio.dart';
    import 'package:fast_cached_network_image/fast_cached_network_image.dart';
    import 'package:flutter/cupertino.dart';
    import 'package:flutter/foundation.dart';
    import 'package:flutter/material.dart';
    import 'package:flutter_image_picker/util/http_file_util.dart';
    import 'package:flutter_image_picker/util/permission_util.dart';
    import 'package:flutter_image_picker/util/toast_util.dart';
    import 'package:image_picker/image_picker.dart';
    
    /// 上传页面
    class UploadPage extends StatefulWidget {
      const UploadPage({super.key});
    
      @override
      State<StatefulWidget> createState() {
        return UploadPageState();
      }
    }
    
    /// 上传文件类型
    enum UploadType {
      // 图片
      image,
      // 视频
      video
    }
    
    /// 选取类型
    enum TakeType {
      // 从相机拍照后选择
      camera,
      // 从图库中选择
      gallery
    }
    
    class UploadPageState extends State<UploadPage> {
      /// 上传的文件Url
      String _uploadFileUrl = "";
    
      /// 上传图片或视频
      void _takeAndUpload(UploadType uploadType, TakeType type) async {
        // 申请相机、存储等权限
        var isGrantedCamera = await PermissionUtil.requestCameraPermission();
        var isGrantedStorage = await PermissionUtil.requestStoragePermission();
    
        if (!isGrantedCamera) {
          ToastUtil.toast("未允许相机权限");
          return;
        }
        if (!isGrantedStorage) {
          ToastUtil.toast("未允许存储读写权限");
          return;
        }
    
        ImageSource imageSource;
        // 打开相机
        if (type == TakeType.camera) {
          imageSource = ImageSource.camera;
        } else {
          // 打开图库
          imageSource = ImageSource.gallery;
        }
    
        XFile? pickedFile;
        if (uploadType == UploadType.image) {
          pickedFile = await ImagePicker().pickImage(source: imageSource);
        } else if (uploadType == UploadType.video) {
          pickedFile = await ImagePicker().pickVideo(source: imageSource);
        } else {
          ToastUtil.toast("不支持上传该类型的文件");
          return;
        }
        if (pickedFile != null) {
          // 选择的文件
          String path = pickedFile.path;
          // 选择的文件名
          var fileName = path.substring(path.lastIndexOf("/") + 1, path.length);
          // 文件转字节数组
          var bytes = await pickedFile.readAsBytes();
          // 发起上传文件请求
          _uploadBytesFile(bytes, fileName, (url) {
            // 上传成功
            setState(() {
              _uploadFileUrl = url;
              ToastUtil.toast("上传成功");
            });
          }, () {
            // 上传失败
            ToastUtil.toast("上传失败,请重试");
          });
        }
      }
    
      /// 发起上传文件请求
      void _uploadBytesFile(
        Uint8List bytes,
        String fileName,
        Function onSuccess,
        Function? onFail,
      ) {
        MultipartFile multipartFile = MultipartFile.fromBytes(
          bytes,
          filename: fileName,
        );
        FormData data = FormData.fromMap({
          // file是上传的参数名,要与服务端接口定义对应
          "file": multipartFile,
        });
        HttpFileUtil.fileUpload(
          (String url) {
            // 上传成功后,返回的文件地址
            onSuccess(url);
          },
          //上传的文件数据
          formData: data,
          errorCallBack: (errorMsg) {
            onFail?.call("上传失败:$errorMsg");
          },
        );
      }
    
      /// 从底部弹出CupertinoActionSheet
      void _handleTapUpload(UploadType uploadType) {
        showCupertinoModalPopup(
          context: context,
          builder: (BuildContext context) => _buildActionSheet(uploadType),
        ).then((value) {});
      }
    
      /// 构建底部弹出菜单actionSheet
      Widget _buildActionSheet(UploadType uploadType) {
        return CupertinoActionSheet(
          title: const Text(
            '请选择',
          ),
          actions: <Widget>[
            CupertinoActionSheetAction(
              child: const Text(
                '相机',
                style: TextStyle(
                  fontSize: 14.0,
                ),
              ),
              onPressed: () {
                // 打开相机拍照
                _takeAndUpload(uploadType, TakeType.camera);
                // 关闭菜单
                Navigator.of(context).pop();
              },
            ),
            CupertinoActionSheetAction(
              child: const Text(
                '相册',
                style: TextStyle(
                  fontSize: 14.0,
                ),
              ),
              onPressed: () {
                // 打开相册,选取照片
                _takeAndUpload(uploadType, TakeType.gallery);
                // 关闭菜单
                Navigator.of(context).pop();
              },
            )
          ],
          cancelButton: CupertinoActionSheetAction(
            child: const Text(
              '取消',
              style: TextStyle(
                fontSize: 13.0,
                color: Color(0xFF666666),
              ),
            ),
            onPressed: () {
              // 关闭菜单
              Navigator.of(context).pop();
            },
          ),
        );
      }
    
      /// 构建上传按钮
      Widget _buildUploadBtn(UploadType uploadType, String text) {
        return GestureDetector(
          onTap: () {
            // 上传图片或视频
            _handleTapUpload(uploadType);
          },
          child: Container(
            height: 150.0,
            width: 150.0,
            margin: const EdgeInsets.only(right: 5.0),
            color: const Color(0xFFC7C4BF),
            child: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Icon(
                    Icons.add,
                    size: 40.0,
                    color: Colors.white,
                  ),
                  Container(
                    margin: const EdgeInsets.only(top: 5.0),
                    child: Text(
                      text,
                      style: const TextStyle(color: Colors.white, fontSize: 14.0),
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      }
    
      /// 构建文件预览控件
      Widget _buildFilePreviewWidget(String uploadFileUrl) {
        if (_uploadFileUrl.isEmpty) {
          return Container();
        } else {
          if (FilePreviewHelper.isImageFile(uploadFileUrl)) {
            // 图片文件
            return FastCachedImage(
              url: _uploadFileUrl,
              loadingBuilder: (context, progress) => const Center(
                child: CircularProgressIndicator(),
              ),
              errorBuilder: (context, exception, stacktrace) => const Center(
                child: Icon(Icons.error),
              ),
            );
          } else {
            // 视频文件
            return Container(
              color: Colors.grey,
              child: const Center(
                child: Text(
                  "视频",
                  style: TextStyle(
                    color: Colors.white,
                    fontSize: 15.0,
                  ),
                ),
              ),
            );
          }
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            leading: IconButton(
                onPressed: () {
                  Navigator.pop(context, null);
                },
                icon: const Icon(Icons.arrow_back)),
            title: const Text("上传"),
          ),
          body: Column(
            children: [
              Expanded(
                flex: 1,
                child: GestureDetector(
                  onTap: () {
                    // 被点击了
                  },
                  child: _buildFilePreviewWidget(_uploadFileUrl),
                ),
              ),
              Container(
                margin: const EdgeInsets.all(5.0),
                child: Row(
                  children: [
                    _buildUploadBtn(UploadType.image, "上传图片"),
                    _buildUploadBtn(UploadType.video, "上传视频"),
                  ],
                ),
              )
            ],
          ),
        );
      }
    }
    

    相关文章

      网友评论

        本文标题:Flutter之选择图片视频与上传

        本文链接:https://www.haomeiwen.com/subject/hwyupdtx.html