와챠의 우당탕탕 코딩 일기장
[Flutter] Camera/Gallery/Preview/Select Video/카메라/갤러리/미리보기/동영상 선택 본문
코딩 일기장/Flutter
[Flutter] Camera/Gallery/Preview/Select Video/카메라/갤러리/미리보기/동영상 선택
minWachya 2023. 7. 17. 12:14반응형
결과 화면
- 바텀 시트: 바텀 시트에서 갤러리에서 가져올 건지, 직접 찍을 건지 선택
- 갤러리: 갤러리에서 영상 선택, 카메리: 카메라로 영상 촬영
- 미리보기: 선택한/촬영한 영상 미리보기(무한 재생) + 제목, 태그 입력
1. 의존성 추가
dependencies:
video_player: ^2.6.1
image_picker: ^1.0.0
2. 바텀 시트(갤러리, 카메라 촬영)
2-1. 갤러리 선택 시 갤러리에서 영상 선택하는 코드
// 갤러리 선택 시 동작하는 코드
onTap: () {
getVideo(ImageSource.gallery); // 갤러리에서 영상 가져오는 함수
Navigator.of(context).pop(); // 바텀시트 내리기
}
2-2. 카메라 선택 시 카메라로 영상 촬영하는 코드
// 카메라 선택 시 동작하는 코드
onTap: () {
getVideo(ImageSource.camera); // 카메라에서 영상 촬영하기
Navigator.of(context).pop(); // 바텀시트 내리기
}
2-3. getVideo 함수
Future getVideo(
ImageSource img,
) async {
final pickedFile = await picker.pickVideo(
source: img,
preferredCameraDevice: CameraDevice.front,
maxDuration: const Duration(minutes: 10)); // 10초 제한
XFile? xfilePick = pickedFile;
setState(
() {
// 파일 선택 시
if (xfilePick != null) {
videoFile = File(pickedFile!.path);
Navigator.push(
context,
MaterialPageRoute(
// UploadScreen: 미리보기 화면으로 이동
builder: (context) => UploadScreen(uploadFile: videoFile!)),
);
} else {
// 파일 미 선택 시 스낵바 띄우기
ScaffoldMessenger.of(widget.context).showSnackBar(
const SnackBar(content: Text('Nothing is selected')));
}
},
);
}
2-4. 전체 코드
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:image_picker/image_picker.dart';
import 'package:pocket_pose/config/app_color.dart';
import 'package:pocket_pose/ui/screen/upload_screen.dart';
class UploadButtonWidget extends StatefulWidget {
const UploadButtonWidget({
super.key,
required this.context,
});
final BuildContext context;
@override
State<UploadButtonWidget> createState() => _UploadButtonWidgetState();
}
class _UploadButtonWidgetState extends State<UploadButtonWidget> {
@override
Widget build(BuildContext context) {
File? videoFile;
final picker = ImagePicker();
Future getVideo(
ImageSource img,
) async {
final pickedFile = await picker.pickVideo(
source: img,
preferredCameraDevice: CameraDevice.front,
maxDuration: const Duration(minutes: 10));
XFile? xfilePick = pickedFile;
setState(
() {
if (xfilePick != null) {
videoFile = File(pickedFile!.path);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => UploadScreen(uploadFile: videoFile!)),
);
} else {
ScaffoldMessenger.of(widget.context).showSnackBar(
const SnackBar(content: Text('Nothing is selected')));
}
},
);
}
return InkWell(
onTap: () => {
showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
top: Radius.circular(15.0),
),
),
backgroundColor: Colors.black,
builder: (context) {
return SizedBox(
height: 160,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Padding(
padding: EdgeInsets.fromLTRB(20, 23, 20, 8),
child: Text(
"만들기",
style: TextStyle(color: Colors.white, fontSize: 16),
),
),
InkWell(
onTap: () {
getVideo(ImageSource.gallery);
Navigator.of(context).pop();
},
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 8, 8),
child: Row(
children: [
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: AppColor.grayColor,
shape: BoxShape.circle,
),
child: SvgPicture.asset(
'assets/icons/ic_home_upload_gallery.svg',
width: 16,
height: 16,
fit: BoxFit.scaleDown,
),
),
const SizedBox(
width: 20,
),
const Text(
"갤러리에서 가져오기",
style: TextStyle(color: Colors.white, fontSize: 14),
),
],
),
),
),
InkWell(
onTap: () {
getVideo(ImageSource.camera);
Navigator.of(context).pop();
},
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 8, 8, 8),
child: Row(
children: [
Container(
width: 36,
height: 36,
decoration: BoxDecoration(
color: AppColor.grayColor,
shape: BoxShape.circle,
),
child: SvgPicture.asset(
'assets/icons/ic_home_upload_camera.svg',
width: 16,
height: 16,
fit: BoxFit.scaleDown,
),
),
const SizedBox(
width: 20,
),
const Text(
"직접 촬영하기",
style: TextStyle(color: Colors.white, fontSize: 14),
),
],
),
),
),
],
),
);
},
),
},
borderRadius: const BorderRadius.all(
Radius.circular(90.0),
),
child: Container(
padding: const EdgeInsets.all(14),
child: SvgPicture.asset('assets/icons/ic_home_upload.svg')),
);
}
}
3. 동영상 미리보기+무한재생 코드
// ... 생략
class UploadScreen extends StatefulWidget {
const UploadScreen({super.key, required this.uploadFile});
final File uploadFile;
@override
State<UploadScreen> createState() => _UploadScreenState();
}
class _UploadScreenState extends State<UploadScreen> {
VideoPlayerController? _videoPlayerController;
// ...
@override
void initState() {
_initVideoPlayer();
super.initState();
}
@override
void dispose() {
_videoPlayerController?.dispose();
super.dispose();
}
Future _initVideoPlayer() async {
if (_videoPlayerController == null) {
// 선택한 영상을
_videoPlayerController = VideoPlayerController.file(widget.uploadFile);
await _videoPlayerController!.initialize();
await _videoPlayerController!.setLooping(true); // 무한 재생
await _videoPlayerController!.play(); // 재생 시작
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: _buildAppBar(context),
body: SafeArea(
child: Stack(
children: [
Container(
color: AppColor.purpleColor,
height: 3,
),
Padding(
padding: const EdgeInsets.fromLTRB(0, 3, 0, 134),
child: VideoPlayer(_videoPlayerController!),
),
],
),
),
);
}
AppBar _buildAppBar(BuildContext context) {
//...
}
}
4. 최종 결과 화면
참고
- 영상 촬영 및 미리보기 코드
- 태그 라이브러리
갤러리에서 동영상 선택/카메라로 동영상 촬영 코드를 하나의 함수로 만들 수 있다는 게 너무 대박적...
너무 깔끔하다 덜덜
반응형
'코딩 일기장 > Flutter' 카테고리의 다른 글
[Flutter] 소켓 연결 + 구독 + 채팅 보내기/Socket connect + subscribe + send message (0) | 2023.08.22 |
---|---|
[Flutter] Base Response 사용하여 Response 받기 + build_runner(중복 코드 줄이기) (0) | 2023.07.27 |
[Flutter] OnBoarding Screen/온보딩 화면/introduction_screen custom (0) | 2023.07.13 |
[Flutter] Permission Request/권한 요청 (0) | 2023.07.13 |
[Flutter] Splash + 로고 잘림 현상 해결 (0) | 2023.07.12 |
Comments