Flutter

Flutter之选择图片视频与上传

2023-08-01  本文已影响0人  h2coder

效果展示

文件上传.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, "上传视频"),
              ],
            ),
          )
        ],
      ),
    );
  }
}
上一篇下一篇

猜你喜欢

热点阅读