Camera(Video Recording)

샤워실의 바보·2024년 2월 15일
0
post-thumbnail
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 패키지를 사용하여 카메라의 비디오 녹화 기능을 구현하는 예제입니다. 코드에는 녹화 시작과 중지 시 애니메이션 효과를 추가하는 로직이 포함되어 있습니다. 아래에서 이 로직의 주요 부분을 설명합니다:

1. 애니메이션 컨트롤러 초기화

  • _buttonAnimationController는 녹화 버튼의 크기 변화 애니메이션을 관리합니다. 녹화 시작 시 버튼이 커지고, 중지 시 원래 크기로 돌아갑니다.
  • _progressAnimationController는 녹화 진행 상태를 나타내는 원형 진행률 표시기(CircularProgressIndicator)의 애니메이션을 관리합니다. 녹화 시간에 따라 진행률이 증가합니다.

2. 녹화 시작 및 중지 로직

  • _starRecording 메서드는 사용자가 녹화 버튼을 탭할 때 호출됩니다. 이 메서드는 _buttonAnimationController를 앞으로 재생하여 버튼 크기를 키우고, _progressAnimationController를 시작하여 녹화 진행률 표시기를 증가시킵니다.
  • _stopRecording 메서드는 녹화를 중지할 때 호출됩니다. 이 메서드는 _buttonAnimationController를 뒤로 재생하여 버튼을 원래 크기로 돌리고, _progressAnimationController를 리셋합니다.

3. 애니메이션 효과 적용

  • ScaleTransition 위젯을 사용하여 녹화 버튼에 크기 변화 애니메이션을 적용합니다. 이는 _buttonAnimation에 정의된 크기 변화 값을 기반으로 합니다.
  • CircularProgressIndicator 위젯에 _progressAnimationController.valuevalue 속성으로 전달하여, 녹화 진행 상태에 따른 시각적 피드백을 제공합니다.

4. 사용자 상호작용

  • 사용자가 녹화 버튼을 탭하면 _starRecording이 호출되어 녹화가 시작되고, 버튼이 커지며 진행률 표시기가 시작됩니다.
  • 사용자가 버튼에서 손을 떼면 _stopRecording이 호출되어 녹화가 중지되고, 버튼과 진행률 표시기가 초기 상태로 돌아갑니다.

이 로직을 통해 사용자는 카메라 앱 내에서 녹화 시작과 중지를 직관적으로 제어할 수 있으며, 시각적으로도 녹화 상태를 명확하게 인식할 수 있습니다.

5. OnTapDown, onTapUp(GestureDetector)

GestureDetector 위젯의 onTapDownonTapUp 콜백은 사용자가 화면을 탭할 때 발생하는 이벤트를 감지하고 처리하는 데 사용됩니다. 이 두 콜백은 사용자의 상호작용을 좀 더 세밀하게 다룰 수 있게 해줍니다.

  • 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를 사용하면 사용자의 탭 동작에 대해 더 세밀한 제어가 가능해져, 앱의 상호작용성을 향상시킬 수 있습니다.

profile
공부하는 개발자

0개의 댓글