import 'package:camera/camera.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:tiktok_clone/constants/gaps.dart';
import 'package:tiktok_clone/constants/sizes.dart';
class VideoRecordingScreen extends StatefulWidget {
const VideoRecordingScreen({super.key});
State<VideoRecordingScreen> createState() => _VideoRecordingScreenState();
}
class _VideoRecordingScreenState extends State<VideoRecordingScreen>
with TickerProviderStateMixin {
bool _hasPermission = false;
bool _isSelfieMode = false;
// 애니메이션 컨트롤러와 애니메이션 값을 관리합니다.
late final AnimationController _buttonAnimationController =
AnimationController(
vsync: this,
duration: const Duration(milliseconds: 200),
);
late final Animation<double> _buttonAnimation =
Tween(begin: 1.0, end: 1.3).animate(_buttonAnimationController);
late final AnimationController _progressAnimationController =
AnimationController(
vsync: this,
duration: const Duration(seconds: 10),
lowerBound: 0.0,
upperBound: 1.0,
);
late FlashMode _flashMode;
late CameraController _cameraController;
Future<void> initCamera() async {
final cameras = await availableCameras();
if (cameras.isEmpty) {
return;
}
_cameraController = CameraController(
cameras[_isSelfieMode ? 1 : 0],
ResolutionPreset.ultraHigh,
);
await _cameraController.initialize();
_flashMode = _cameraController.value.flashMode;
}
Future<void> initPermissions() async {
final cameraPermission = await Permission.camera.request();
final micPermission = await Permission.microphone.request();
if (!(cameraPermission.isDenied || cameraPermission.isPermanentlyDenied) &&
!(micPermission.isDenied || micPermission.isPermanentlyDenied)) {
_hasPermission = true;
await initCamera();
setState(() {});
}
}
void initState() {
super.initState();
initPermissions();
// 녹화 진행 상태에 따라 UI를 업데이트합니다.
_progressAnimationController.addListener(() {
setState(() {});
});
// 녹화가 완료되면 자동으로 녹화를 중지합니다.
_progressAnimationController.addStatusListener((status) {
if (status == AnimationStatus.completed) {
_stopRecording();
}
});
}
Future<void> _toggleSelfieMode() async {
_isSelfieMode = !_isSelfieMode;
await initCamera();
setState(() {});
}
Future<void> _setFlashMode(FlashMode newFlashMode) async {
await _cameraController.setFlashMode(newFlashMode);
_flashMode = newFlashMode;
setState(() {});
}
// 녹화를 시작합니다. 버튼을 누를 때 애니메이션을 실행합니다.
void _startRecording(TapDownDetails _) {
_buttonAnimationController.forward();
_progressAnimationController.forward();
}
// 녹화를 중지합니다. 버튼에서 손을 뗄 때 애니메이션을 되돌립니다.
void _stopRecording() {
_buttonAnimationController.reverse();
_progressAnimationController.reset();
}
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SizedBox(
width: MediaQuery.of(context).size.width,
child: !_hasPermission || !_cameraController.value.isInitialized
? Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Text("Initializing...",
style: TextStyle(color: Colors.white, fontSize: Sizes.size20)),
Gaps.v20,
CircularProgressIndicator.adaptive()
],
)
: Stack(
alignment: Alignment.center,
children: [
CameraPreview(_cameraController),
Positioned(
bottom: Sizes.size40,
child: GestureDetector(
onTapDown: _startRecording, // 녹화 시작
onTapUp: (details) => _stopRecording(), // 녹화 중지
child: ScaleTransition(
scale: _buttonAnimation, // 버튼 크기 변화 애니메이션
child: Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: Sizes.size80 + Sizes.size14,
height: Sizes.size80 + Sizes.size14,
child: CircularProgressIndicator(
color: Colors.red.shade400,
strokeWidth: Sizes.size6,
value: _progressAnimationController.value, // 녹화 진행률 표시
),
),
Container(
width: Sizes.size80,
height: Sizes.size80,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.red.shade400,
),
),
],
),
),
),
)
],
),
),
);
}
}
이 코드는 Flutter의 camera
패키지를 사용하여 카메라의 비디오 녹화 기능을 구현하는 예제입니다. 코드에는 녹화 시작과 중지 시 애니메이션 효과를 추가하는 로직이 포함되어 있습니다. 아래에서 이 로직의 주요 부분을 설명합니다:
_buttonAnimationController
는 녹화 버튼의 크기 변화 애니메이션을 관리합니다. 녹화 시작 시 버튼이 커지고, 중지 시 원래 크기로 돌아갑니다._progressAnimationController
는 녹화 진행 상태를 나타내는 원형 진행률 표시기(CircularProgressIndicator)의 애니메이션을 관리합니다. 녹화 시간에 따라 진행률이 증가합니다._starRecording
메서드는 사용자가 녹화 버튼을 탭할 때 호출됩니다. 이 메서드는 _buttonAnimationController
를 앞으로 재생하여 버튼 크기를 키우고, _progressAnimationController
를 시작하여 녹화 진행률 표시기를 증가시킵니다._stopRecording
메서드는 녹화를 중지할 때 호출됩니다. 이 메서드는 _buttonAnimationController
를 뒤로 재생하여 버튼을 원래 크기로 돌리고, _progressAnimationController
를 리셋합니다.ScaleTransition
위젯을 사용하여 녹화 버튼에 크기 변화 애니메이션을 적용합니다. 이는 _buttonAnimation
에 정의된 크기 변화 값을 기반으로 합니다.CircularProgressIndicator
위젯에 _progressAnimationController.value
를 value
속성으로 전달하여, 녹화 진행 상태에 따른 시각적 피드백을 제공합니다._starRecording
이 호출되어 녹화가 시작되고, 버튼이 커지며 진행률 표시기가 시작됩니다._stopRecording
이 호출되어 녹화가 중지되고, 버튼과 진행률 표시기가 초기 상태로 돌아갑니다.이 로직을 통해 사용자는 카메라 앱 내에서 녹화 시작과 중지를 직관적으로 제어할 수 있으며, 시각적으로도 녹화 상태를 명확하게 인식할 수 있습니다.
GestureDetector
위젯의 onTapDown
과 onTapUp
콜백은 사용자가 화면을 탭할 때 발생하는 이벤트를 감지하고 처리하는 데 사용됩니다. 이 두 콜백은 사용자의 상호작용을 좀 더 세밀하게 다룰 수 있게 해줍니다.
onTapDown
: 사용자가 화면의 특정 위치를 탭하고 손가락이 화면에 닿는 순간에 호출됩니다. TapDownDetails
객체를 통해 탭이 발생한 위치와 같은 세부 정보에 접근할 수 있습니다. 이를 사용하여 사용자가 버튼을 누르는 순간의 피드백을 제공하거나, 특정 액션을 시작하는 데 사용할 수 있습니다.
onTapUp
: 사용자가 탭한 후 손가락을 화면에서 떼는 순간에 호출됩니다. TapUpDetails
객체를 통해 탭이 끝난 위치와 같은 세부 정보에 접근할 수 있습니다. 이 콜백은 사용자가 버튼을 누르고 있던 동작을 완료하거나, 떼는 순간의 액션을 처리하는 데 사용할 수 있습니다.
예를 들어, 녹화 버튼을 구현할 때 onTapDown
을 사용하여 녹화를 시작하고, 사용자의 손가락이 버튼에서 떼어지는 순간인 onTapUp
을 사용하여 녹화를 중지할 수 있습니다. 이를 통해 사용자가 버튼을 누르고 있는 동안에만 녹화가 진행되도록 할 수 있습니다.
GestureDetector(
onTapDown: (TapDownDetails details) {
// 사용자가 버튼을 누르는 순간의 로직 처리
print("Tap down on the screen at ${details.globalPosition}");
},
onTapUp: (TapUpDetails details) {
// 사용자가 버튼에서 손을 떼는 순간의 로직 처리
print("Tap up on the screen at ${details.globalPosition}");
},
child: Container(
// 위젯 구성
),
)
이와 같은 방식으로 GestureDetector
를 사용하면 사용자의 탭 동작에 대해 더 세밀한 제어가 가능해져, 앱의 상호작용성을 향상시킬 수 있습니다.