目录
用到的组件
ios和android端配置文件
ios
android
主页部分代码
显示及保存照片组件代码
显示及保存视频组件代码
视频演示
接上文Flutter简单聊天界面布局及语音录制播放_chw-di的博客-CSDN博客
本文主要对聊天布局内的图片及视频的上传、显示和保存到相册进行简单开发。
#相册插件
image_picker: ^0.8.5+3
#查看图片组件
photo_view: ^0.14.0
#缓存照片插件
cached_network_image: ^3.2.1
#视频播放
video_player: ^2.4.7
#视频缩略图
video_thumbnail: ^0.5.2
#文件目录获取
path_provider: ^2.0.11
#保存视频、照片到本地相册
image_gallery_saver: ^1.7.1
在info.plist中添加
NSPhotoLibraryAddUsageDescription
保存图片
NSAppTransportSecurity
http
在AndroidManifest.xml中添加
//获取相册照片并上传
_getPhotos() async {
final XFile? pickImage =
await _picker.pickImage(source: ImageSource.gallery);
//上传
var filePath = await _uploadFile(pickImage!, MessageType.photo);
insertFile(MessageType.photo,filePath,"");
}
//拍照并上传
_takePhotos() async {
final XFile? pickImage =
await _picker.pickImage(source: ImageSource.camera);
//上传
var filePath = await _uploadFile(pickImage!, MessageType.photo);
insertFile(MessageType.photo,filePath,"");
}
//上传视频
_getVideo() async {
final XFile? pickImage = await _picker.pickVideo(source: ImageSource.gallery);
//获取缩略图文件
File videoThumbnailFile = await _getVideoThumbnail(pickImage!);
XFile file = XFile(videoThumbnailFile.path);
//上传缩略图
var videoThumbnailFilePath = await _uploadFile(file, MessageType.photo);
//上传视频
var videoPath = await _uploadFile(pickImage, MessageType.video);
insertFile(MessageType.video,videoPath,videoThumbnailFilePath);
}
//获取视频缩略图
Future _getVideoThumbnail(XFile videoFile) async {
Uint8List? thumbnail = await VideoThumbnail.thumbnailData(
video: videoFile.path,
imageFormat: ImageFormat.JPEG,
quality: 25,
);
var tempDir = await getTemporaryDirectory();
//生成file文件格式
String videoThumbnail = '${tempDir.path}/image_${DateTime.now().millisecond}.jpg';
var file = await File(videoThumbnail).create();
file.writeAsBytesSync(thumbnail!);
return file;
}
//上传文件
Future _uploadFile(XFile imageDir, String type) async {
String filePath = imageDir.path;
var list = filePath.split(".");
var last = list.last;
String fileName = "${const Uuid().v4()}.$last";
FormData formData = FormData.fromMap({
"file": await MultipartFile.fromFile(imageDir.path, filename: fileName),
});
Dio dio = Dio();
var response = await dio.post("http://192.168.9.253:8091/sc/file/upload", data: formData);
var path = response.data["data"]["detail"]["filePath"];
return path;
}
//写入文件
void insertFile(String type,String path,String thumbnail) {
Map data = {};
data['messageId'] = const Uuid().v4();
data['message'] = "图片";
data['messageType'] = type;
data['messageTime'] =
TimeUtils.getFormatDataString(DateTime.now(), "yyyy-MM-dd HH:mm:ss");
data['isMe'] = Random.secure().nextBool();
data['fileUrl'] = path;
if(thumbnail.isNotEmpty){
data['thumbnail'] = thumbnail;
}
setState(() {
_messageData.insert(0, data);
});
}
//照片显示组件:
GestureDetector(
onTap: () {
Navigator.push(
context,
PanPageRouteBuilder(
builder: (context) =>
FullImageWidget(imageUrl: data['fileUrl']),
popDirection: AxisDirection.up));
},
child: Container(
clipBehavior: Clip.hardEdge,
width: ScreenAdapter.width(300),
height: ScreenAdapter.height(400),
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(10))),
child: CachedNetworkImage(
imageUrl: data['fileUrl'],
fit: BoxFit.cover,
)),
);
//视频显示组件
GestureDetector(
onTap: () {
Navigator.push(
context,
PanPageRouteBuilder(
builder: (context) =>
FullVideoWidget(videoUrl: data['fileUrl']),
popDirection: AxisDirection.up));
},
child: Stack(
alignment: Alignment.center,
children: [
Container(
color: Colors.white,
width: ScreenAdapter.width(300),
height: ScreenAdapter.height(400),
child: Image.network(data['thumbnail'],fit: BoxFit.cover,),),
Icon(Icons.play_circle,color: Colors.white,size: ScreenAdapter.size(70),)
],
)
);
import 'dart:typed_data';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:new_chat/service/screen_adapter.dart';
import 'package:new_chat/widget/toast_widget.dart';
import 'package:photo_view/photo_view.dart';
class FullImageWidget extends StatelessWidget {
final String imageUrl;
const FullImageWidget({Key? key, required this.imageUrl}) : super(key: key);
//保存照片
_saveImage() async {
var response = await Dio().get(
imageUrl,
options: Options(responseType: ResponseType.bytes));
final result = await ImageGallerySaver.saveImage(
Uint8List.fromList(response.data),
name: "hello");
if(result['isSuccess']){
ToastWidget.showToast("照片保存成功", ToastGravity.CENTER);
}
}
//长摁保存照片组件
_showSaveVideoWidget(BuildContext context) async {
showModalBottomSheet(
context: context,
isDismissible: true,
isScrollControlled: false,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15))),
builder: (BuildContext context) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(topLeft: Radius.circular(15),topRight: Radius.circular(15))
),
height: ScreenAdapter.height(400),
child: Column(
children: [
Container(padding: EdgeInsets.all(ScreenAdapter.height(40)),child: Text(textAlign:TextAlign.center,"保存照片到相册",maxLines:2,style: TextStyle(color: const Color.fromRGBO(102, 102, 102, 1),fontSize: ScreenAdapter.size(25)),),),
Divider(height: ScreenAdapter.height(0.5)),
InkWell(
child:Padding(padding: EdgeInsets.all(ScreenAdapter.height(18)),child: Center(child: Text("保存照片",style: TextStyle(color: Colors.red,fontSize: ScreenAdapter.size(30)),),),),
onTap: () async{
_saveImage();
Navigator.pop(context);
},
),
Container(color: const Color.fromRGBO(245, 245, 245,1),height: ScreenAdapter.height(15)),
InkWell(
onTap: (){
Navigator.pop(context);
},
child:Padding(padding: EdgeInsets.fromLTRB(ScreenAdapter.width(0),ScreenAdapter.height(10),ScreenAdapter.width(0),ScreenAdapter.height(15)),child: Text("取消",style: TextStyle(color: const Color.fromRGBO(51, 51, 51, 1),fontSize: ScreenAdapter.size(30)),)),),
],
),
);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black87,
body: GestureDetector(
child: Center(
child: PhotoView(
imageProvider: NetworkImage(imageUrl),
)),
onTap: () {
Navigator.pop(context);
},
//长摁弹出保存照片界面
onLongPress: (){
_showSaveVideoWidget(context);
},
),
);
}
}
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:new_chat/service/screen_adapter.dart';
import 'package:new_chat/widget/toast_widget.dart';
import 'package:path_provider/path_provider.dart';
import 'package:uuid/uuid.dart';
import 'package:video_player/video_player.dart';
class FullVideoWidget extends StatefulWidget {
final String videoUrl;
const FullVideoWidget({Key? key, required this.videoUrl}) : super(key: key);
@override
State createState() => _FullVideoWidgetState();
}
class _FullVideoWidgetState extends State {
late VideoPlayerController _controller;
//视频总时长
String videoPlayerEndTime = "";
//视频正在播放的时长
String videoPlayerTime = "";
@override
void initState() {
_controller = VideoPlayerController.network(widget.videoUrl)
..initialize().then((_) {
setState(() {
_controller.play();
});
});
_controller.addListener(() {
setState(() {
//拼接视频总时长
int endMinutes = _controller.value.duration.inMinutes;
//不足2位补0
var endMinutesPadLeft = endMinutes.toString().padLeft(2,"0");
int endSeconds = _controller.value.duration.inSeconds;
var endSecondsPadLeft = endSeconds.toString().padLeft(2,"0");
videoPlayerEndTime = "$endMinutesPadLeft:$endSecondsPadLeft";
int videoPlayerMinutes = _controller.value.position.inMinutes;
var videoPlayerMinutesPadLeft = videoPlayerMinutes.toString().padLeft(2,"0");
int videoPlayerSeconds = _controller.value.position.inSeconds;
var videoPlayerSecondsPadLeft = videoPlayerSeconds.toString().padLeft(2,"0");
videoPlayerTime = "$videoPlayerMinutesPadLeft:$videoPlayerSecondsPadLeft";
});
});
super.initState();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
//保存视频
_saveVideo() async {
var appDocDir = await getTemporaryDirectory();
String savePath = "${appDocDir.path}+${const Uuid().v4()}/temp.mp4";
await Dio().download(widget.videoUrl, savePath);
final result = await ImageGallerySaver.saveFile(savePath);
if(result['isSuccess']){
ToastWidget.showToast("视频保存成功", ToastGravity.CENTER);
}
}
//长摁保存视频组件
_showSaveVideoWidget(BuildContext context) async {
showModalBottomSheet(
context: context,
isDismissible: true,
isScrollControlled: false,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.only(topLeft: Radius.circular(15), topRight: Radius.circular(15))),
builder: (BuildContext context) {
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(topLeft: Radius.circular(15),topRight: Radius.circular(15))
),
height: ScreenAdapter.height(400),
child: Column(
children: [
Container(padding: EdgeInsets.all(ScreenAdapter.height(40)),child: Text(textAlign:TextAlign.center,"保存视频到相册",maxLines:2,style: TextStyle(color: const Color.fromRGBO(102, 102, 102, 1),fontSize: ScreenAdapter.size(25)),),),
Divider(height: ScreenAdapter.height(0.5)),
InkWell(
child:Padding(padding: EdgeInsets.all(ScreenAdapter.height(18)),child: Center(child: Text("保存视频",style: TextStyle(color: Colors.red,fontSize: ScreenAdapter.size(30)),),),),
onTap: () async{
_saveVideo();
Navigator.pop(context);
},
),
Container(color: const Color.fromRGBO(245, 245, 245,1),height: ScreenAdapter.height(15)),
InkWell(
onTap: (){
Navigator.pop(context);
},
child:Padding(padding: EdgeInsets.fromLTRB(ScreenAdapter.width(0),ScreenAdapter.height(10),ScreenAdapter.width(0),ScreenAdapter.height(15)),child: Text("取消",style: TextStyle(color: const Color.fromRGBO(51, 51, 51, 1),fontSize: ScreenAdapter.size(30)),)),),
],
),
);
});
}
@override
Widget build(BuildContext context) {
ScreenAdapter.init(context);
return Scaffold(
backgroundColor: Colors.black87,
body: GestureDetector(
onLongPress: ()async {
//长摁弹出保存视频界面
await _showSaveVideoWidget(context);
},
child: Stack(
children: [
//视频内容
Align(
child: Container(
child: _controller.value.isInitialized
? AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: VideoPlayer(_controller),
)
: Container(),
),
),
//播放暂定和播放进度条和视频时间
Container(
margin: EdgeInsets.only(bottom: ScreenAdapter.height(200)),
child: Align(
alignment: Alignment.bottomCenter,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
padding:
EdgeInsets.only(left: ScreenAdapter.width(20)),
child: GestureDetector(
onTap: () {
_controller.value.isPlaying
? _controller.pause()
: _controller.play();
},
child: Icon(
_controller.value.isPlaying
? Icons.pause_outlined
: Icons.play_arrow,
color: Colors.white,
size: ScreenAdapter.size(60),
),
),
),
Container(
padding:
EdgeInsets.only(left: ScreenAdapter.width(40)),
child: Text(
videoPlayerTime,
style: const TextStyle(color: Colors.white),
),
),
Expanded(
flex: 1,
child: VideoProgressIndicator(
_controller,
allowScrubbing: true,
colors: const VideoProgressColors(
playedColor: Colors.white,
bufferedColor: Colors.white10,
backgroundColor: Colors.black26),
padding: EdgeInsets.fromLTRB(
ScreenAdapter.width(20),
0,
ScreenAdapter.width(20),
0),
),
),
Container(
padding:
EdgeInsets.only(right: ScreenAdapter.width(50)),
child: Text(
videoPlayerEndTime,
style: const TextStyle(color: Colors.white),
),
),
],
)),
),
//关闭视频按钮
Align(
alignment: Alignment.bottomLeft,
child:Container(
padding: EdgeInsets.fromLTRB(ScreenAdapter.width(20),0,0,ScreenAdapter.height(100)),
child: GestureDetector(
onTap: (){
Navigator.pop(context);
},
child: Icon(Icons.cancel,color: Colors.white,size: ScreenAdapter.size(60),),
),),),
//视频保存按钮
Align(
alignment: Alignment.bottomRight,
child:Container(
padding: EdgeInsets.fromLTRB(0,0,ScreenAdapter.width(20),ScreenAdapter.height(100)),
child: GestureDetector(
onTap: () async{
_saveVideo();
},
child: Icon(Icons.download_for_offline,color: Colors.white,size: ScreenAdapter.size(60),),
),),),
],
),)
);
}
}
语音聊天优化&照片及视频发送和保存到相册配套视频