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, "上传视频"),
],
),
)
],
),
);
}
}