目录
接上文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中添加
- <key>NSPhotoLibraryAddUsageDescription</key>
- <string>保存图片</string>
- <key>NSAppTransportSecurity</key>
- <string>http</string>
在AndroidManifest.xml中添加
-
- <uses-permission android:name="android.permission.INTERNET"/>
- <application ...
- android:requestLegacyExternalStorage="true"
- ...
- </application>
- //获取相册照片并上传
- _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<String> _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<FullVideoWidget> {
- 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),),
- ),),),
- ],
- ),)
- );
- }
- }
语音聊天优化&照片及视频发送和保存到相册配套视频