Camera App - GlobalMediaBridge/cosmetic-server GitHub Wiki

Flutter๋ฅผ ์ด์šฉํ•ด ์นด๋ฉ”๋ผ ์•ฑ ๋งŒ๋“ค๊ธฐ

์นด๋ฉ”๋ผ ์•ฑ์„ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด์„œ flutter๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์—ฌ๋Ÿฌ ํ”Œ๋Ÿฌ๊ทธ์ธ์„ ์‚ฌ์šฉํ•ด์•ผํ•œ๋‹ค.

  • ์นด๋ฉ”๋ผ ์žฅ์น˜๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•œ camera
  • ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•  ๊ฒฝ๋กœ๋ฅผ ์ฐพ๊ธฐ์œ„ํ•œ path_provider
  • ์–ด๋Š ํ”Œ๋žซํผ์—์„œ๋“  ์˜ฌ๋ฐ”๋ฅผ ๊ฒฝ๋กœ๋ฅผ ์ฐพ๊ธฐ์œ„ํ•œ path

๊ฐ ํ”Œ๋Ÿฌ๊ทธ์ธ ์„ค์น˜ ํŽ˜์ด์ง€์—์„œ ์•Œ๋งž์€ ๋ฒ„์ „์„ pubspec.yamlํŒŒ์ผ์— ์ž‘์„ฑํ•ด์ค€๋‹ค. (2020-02-21 ๊ธฐ์ค€)

dependencies:

  camera: ^0.5.7+3
  path_provider: ^1.6.1
  path: ^1.6.4

visual studio code์—์„œ ์ž‘์„ฑ์ค‘์ด๋ผ๋ฉด ์ €์žฅ์‹œ ๋ฐ”๋กœ ํ”Œ๋Ÿฌ๊ทธ์ธ์ด ์„ค์น˜๋  ๊ฒƒ์ด๋‹ค.

์ด ํ›„ ์นด๋ฉ”๋ผ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ๋ช‡๊ฐ€์ง€ ์„ค์ •์„ ๋” ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

ios

ios/Runner/Info.plist ํŒŒ์ผ์— <dict> ์•ˆ์ชฝ์— ์•„๋ž˜ ๋‚ด์šฉ์„ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค.

<dict>
    <key>NSCameraUsageDescription</key>
    <string>Can I use the camera please?</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>Can I use the mic please?</string>

</dict>

android

android/app/build.gradle ํŒŒ์ผ์—์„œ minSdkVersion์„ 21๋กœ ์ˆ˜์ •ํ•ด์ค€๋‹ค.

android {
    defaultConfig {
        minSdkVersion 21
    }
}

flutter ๊ธฐ๋ณธ ๊ตฌ์„ฑ

MaterialApp์„ ์ด์šฉํ•˜๊ณ  ์‚ฌ์ง„์ดฌ์˜์„ ์œ„ํ•œ TakePictureScreen์„ ์ƒํƒœ๋ฅผ ๊ฐ–๋Š” ์œ„์ ฏ์œผ๋กœ ์ƒ์„ฑํ•œ๋‹ค. ์นด๋ฉ”๋ผ๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋Š” ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ƒํƒœ ๋ณ€๊ฒฝ์ด ๊ฐ€๋Šฅํ•œ ์œ„์ ฏ์ด ํ•„์š”ํ•˜๋‹ค. visual studio code์—์„œ๋Š” stf๋ฅผ ์ž…๋ ฅํ•˜๋ฉด ์ƒํƒœ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅํ•œ ์œ„์ ฏ์„ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

import 'package:flutter/material.dart';

void main() {
  runApp(
    MaterialApp(
      theme: ThemeData.dark(),
      home: TakePictureScreen(),
    ),
  );
}

class TakePictureScreen extends StatefulWidget {
  @override
  _TakePictureScreenState createState() => _TakePictureScreenState();
}


class _TakePictureScreenState extends State<TakePictureScreen> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Take a picture')),
      body: Center(child:Text('camera'))
    );
  }
}

์นด๋ฉ”๋ผ ๊ธฐ๋ณธ ๊ตฌ์„ฑ

์นด๋ฉ”๋ผ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ ์นด๋ฉ”๋ผ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์™€์•ผํ•œ๋‹ค. runApp()ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜๊ธฐ ์ „์— ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ฒƒ์ด ์žˆ๋‹ค๋ฉด WidgetsFlutterBinding.ensureInitialized() ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•ด์ฃผ์–ด์•ผํ•œ๋‹ค. ์นด๋ฉ”๋ผ ๋ชฉ๋ก์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ํ•จ์ˆ˜๋Š” ์‹œ๊ฐ„์ด ์˜ค๋ž˜๊ฑธ๋ฆฐ๋‹ค. ์‹œ๊ฐ„์ด ์˜ค๋ž˜๊ฑธ๋ฆฌ๋Š” ์ž‘์—…์€ Future ๋ผ๋Š” ๊ฒƒ์œผ๋กœ ๊ฐ์‹ธ์ฃผ์–ด์•ผํ•˜๊ณ  ์ด๋ฅผ ๊ธฐ๋‹ค๋ฆฌ๊ธฐ ์œ„ํ•ด์„œ๋Š” ํ•ด๋‹น ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๋Š” ํ•จ์ˆ˜์— async๋ฅผ ์“ฐ๊ณ  ํ•ด๋‹น ํ•จ์ˆ˜ ์•ž์— await์„ ์จ์ฃผ๋ฉด ๋œ๋‹ค. availableCameras()ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ๊ฐ€๋Šฅํ•œ ์นด๋ฉ”๋ผ ๋ชฉ๋ก์„ ๋ฐ›์•„์„œ ๊ทธ์ค‘ ์ฒซ๋ฒˆ์งธ ์นด๋ฉ”๋ผ๋ฅผ TakePictureScreen์— ์ „๋‹ฌํ•ด ์ฃผ๊ฒ ๋‹ค.

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  final cameras = await availableCameras();

  final firstCamera = cameras.first;

  runApp(
    MaterialApp(
      theme: ThemeData.dark(),
      home: TakePictureScreen(
        camera: firstCamera,
      ),
    ),
  );
}

๊ธฐ์กด์˜ mainํ•จ์ˆ˜๋ฅผ ์œ„์™€ ๊ฐ™์ด ๋ณ€๊ฒฝํ•ด์ฃผ๋ฉด ๋œ๋‹ค. Future๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์นด๋ฉ”๋ผ๋ฅผ ์“ฐ๊ธฐ์œ„ํ•ด์„œ ํ”Œ๋Ÿฌ๊ทธ์ธ๋“ค์„ ๋ถˆ๋Ÿฌ์™€์•ผํ•˜๋Š”๋ฐ ์ž‘์—…์—์„œ ์“ฐ์ผ ํ”Œ๋Ÿฌ๊ทธ์ธ๋“ค์„ ๋ฏธ๋ฆฌ ๋ถˆ๋Ÿฌ์™€์ฃผ๋„๋ก ํ•œ๋‹ค.

import 'dart:async';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:path/path.dart' show join;
import 'package:path_provider/path_provider.dart';

์นด๋ฉ”๋ผ๊ฐ€ TakePictureScreen๋กœ ์ „๋‹ฌ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ๊ฑธ ๋ฐ›์•„์˜ค๋Š” ์ฝ”๋“œ๊ฐ€ ํ•„์š”ํ•˜๋‹ค. TakePictureScreen ํด๋ž˜์Šค ์•ˆ์— ์ž‘์„ฑํ•ด์ค€๋‹ค.

  final CameraDescription camera;

  const TakePictureScreen({
    Key key,
    @required this.camera,
  }) : super(key: key);

์ด์ œ ์ƒํƒœ๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๋Š”_TakePictureScreenState ํด๋ž˜์Šค์—์„œ ์นด๋ฉ”๋ผ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ƒ์„ฑํ•ด์„œ ์„ค์ •ํ•ด์ฃผ๋ฉด ๋œ๋‹ค. initState์—์„œ ์นด๋ฉ”๋ผ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋งŒ๋“ค์–ด์ฃผ๋Š”๋ฐ ์•„๊นŒ ๋ฐ›์•„์˜จ ์นด๋ฉ”๋ผ๋ฅผ widget.camera๋กœ ์ „๋‹ฌํ•ด์ฃผ๋ฉด๋œ๋‹ค. ์ถ”๊ฐ€์ ์œผ๋กœ ํ•ด์ƒ๋„๋ฅผ medium์œผ๋กœ ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์‹œ์ž‘ํ•ด์„œ Future์•ˆ์— ๋‹ด์•„๋‘์—ˆ๋‹ค. ์ด ์—ญ์‹œ ์‹œ๊ฐ„์ด ์˜ค๋ž˜๊ฑธ๋ฆฌ๊ธฐ ๋•Œ๋ฌธ์— ์ผ๋‹จ _initializeControllerFutre์•ˆ์— ๋‹ด์•„๋‘๊ฒ ๋‹ค. async, awaitํ‚ค์›Œ๋“œ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜๊ธฐ ๋•Œ๋ฌธ์— ์•„์ง ๊ธฐ๋‹ค๋ฆฌ์ง€ ์•Š๋Š”๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ™”๋ฉด์ด ์‚ฌ๋ผ์ง€๋Š” ๊ฒฝ์šฐ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์ง€์›Œ์ฃผ๊ธฐ ์œ„ํ•ด dispose๋„ ์ž‘์„ฑํ•ด์ค€๋‹ค.

  CameraController _controller;
  Future<void> _initializeControllerFuture;

  @override
  void initState() {
    super.initState();
    _controller = CameraController(
      widget.camera,
      ResolutionPreset.medium,
    );

    _initializeControllerFuture = _controller.initialize();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

ํ™”๋ฉด์„ ๋‚˜ํƒ€๋‚ด์ฃผ๋Š” build์—์„œ ์ปจํŠธ๋กค๋Ÿฌ์˜ ์‹œ์ž‘์„ ๊ธฐ๋‹ค๋ฆฐ ํ›„ ์œ„์ ฏ์„ ์ƒ์„ฑํ•˜๊ธฐ์œ„ํ•ด์„œ FutureBuilder๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค. ๊ธฐ๋‹ค๋ฆด ๊ฒƒ์„ future๋กœ ์ „๋‹ฌํ•˜๊ณ  ๊ธฐ๋‹ค๋ฆผ์ด ๋๋‚œ ๋’ค ๋งŒ๋“ค ์œ„์ ฏ์„ builder๋กœ ์ „๋‹ฌํ•ด์ฃผ๋ฉด๋œ๋‹ค.

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Take a picture')),
      body: FutureBuilder<void>(
        future: _initializeControllerFuture,
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.done) {
            return CameraPreview(_controller);
          } else {
            return Center(child: CircularProgressIndicator());
          }
        },
      )
    );
  }

์œ„ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. _initializeControllerFuture๋ฅผ ๊ธฐ๋‹ค๋ ค์„œ ์™„๋ฃŒ๊ฐ€ ๋˜๋ฉด ๊ทธ ๊ฐ’์„ builder์— snapshot์œผ๋กœ ์ „๋‹ฌํ•œ๋‹ค.
  2. snapshot์˜ ์—ฐ๊ฒฐ์ƒํƒœ๊ฐ€ done์ธ๊ฒฝ์šฐ CameraPreview๋ฅผ ๋ณด์—ฌ์ค€๋‹ค.
  3. ์—ฐ๊ฒฐ์ƒํƒœ๊ฐ€ done์ด ์•„๋‹ˆ๋ฉด ์ค‘์‹ฌ์— CircularProgressIndicator๋ฅผ ๋ณด์—ฌ์ค€๋‹ค. CameraPreview๋Š” ์นด๋ฉ”๋ผ๋ฅผ ์ฐ๊ณ  ์žˆ์„ ๋•Œ ์นด๋ฉ”๋ผ๊ฐ€ ๋ณด๊ณ  ์žˆ๋Š” ํ™”๋ฉด์„ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ฏธ๋ฆฌ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•œ ์œ„์ ฏ์ด๋‹ค. ํ•ด๋‹น ์œ„์ ฏ์—๋Š” ์นด๋ฉ”๋ผ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

์ด๋ฏธ์ง€ ์ดฌ์˜

์ด๋ฏธ์ง€ ์ดฌ์˜์„ ์œ„ํ•ด ์šฐ์„  ์ดฌ์˜๋ฒ„ํŠผ์„ ๋งŒ๋“ค์–ด์ค€๋‹ค. MaterialApp์„ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฐ„๋‹จํ•˜๊ฒŒ FloatingActionButton์„ ์ด์šฉํ•˜๋„๋ก ํ•œ๋‹ค. Scaffold์•ˆ์— ๋งŒ๋“ค์–ด์ฃผ๋„๋กํ•œ๋‹ค.

    Scaffold(
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.camera_alt),
        onPressed: () { },
      ),
      floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
    );

MaterialApp์—์„œ ์ œ๊ณตํ•˜๋Š” ์นด๋ฉ”๋ผ ์•„์ด์ฝ˜์„ ์‚ฌ์šฉํ•˜๊ณ  ๋ฒ„ํŠผ์˜ ์œ„์น˜๋ฅผ ์ค‘์•™์œผ๋กœ ์˜ฎ๊ฒจ์ฃผ์—ˆ๋‹ค. ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ ์‹คํ–‰๋  onPressed ๋ถ€๋ถ„์—์„œ ์ด๋ฏธ์ง€๋ฅผ ์ž„์‹œ๋กœ ์ €์žฅํ•˜๋„๋ก ํ•œ๋‹ค. ์‚ฌ์ง„์„ ์ฐ๊ณ  ์ €์žฅํ•˜๋Š” ์ผ์€ ์˜ค๋ž˜๊ฑธ๋ฆฌ๋Š” ์ผ์ด๊ธฐ ๋•Œ๋ฌธ์— async์™€ await์„ ์ด์šฉํ•ด ๊ธฐ๋‹ค๋ ค์ฃผ๋„๋ก ํ•œ๋‹ค. ๋˜ํ•œ ์˜ค๋ฅ˜๊ฐ€ ์‰ฝ๊ฒŒ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— try์•ˆ์—์„œ ์ž‘์„ฑํ•˜๊ณ  ์˜ค๋ฅ˜๊ฐ€ ์ƒ๊ธฐ๋ฉด catchํ•˜๋„๋ก ํ•œ๋‹ค.

        onPressed: () {
          try {
            await _initializeControllerFuture;

            final path = join(
              (await getTemporaryDirectory()).path,
              '${DateTime.now()}.png',
            );

            await _controller.takePicture(path);

          } catch (e) {
            // If an error occurs, log the error to the console.
            print(e);
          }
        },

์œ„ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. ์นด๋ฉ”๋ผ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์‹คํ–‰ํ•ด์ค€๋‹ค.
  2. ์ž„์‹œ๋กœ ์ €์žฅํ•  ๊ณต๊ฐ„์„ ์–ป์–ด์„œ ํ˜„์žฌ ์‹œ๊ฐ„์„ ์ œ๋ชฉ์œผ๋กœ png ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด์„œ path์— ๊ธฐ์–ต์‹œํ‚จ๋‹ค.
  3. ์ปจํŠธ๋กค๋Ÿฌ๋กœ takePictureํ•จ์ˆ˜๋ฅผ ์‹คํ–‰์‹œ์ผœ ์ดฌ์˜ํ•˜๊ณ  ์–ป์€ ์ด๋ฏธ์ง€๋ฅผ path์˜ ์œ„์น˜์— ์ €์žฅํ•œ๋‹ค.
  4. ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์ถœ๋ ฅํ•œ๋‹ค.

์ดฌ์˜๋œ ์ด๋ฏธ์ง€ ๋ณด์—ฌ์ฃผ๊ธฐ

path์œ„์น˜์— ์ €์žฅ๋œ ์ด๋ฏธ์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด ์ƒˆ๋กœ์šด ํŽ˜์ด์ง€๋ฅผ ๋งŒ๋“ ๋‹ค. ๋‹จ์ˆœํžˆ ๋ณด์—ฌ์ฃผ๊ธฐ๋งŒ ํ•˜๋ฉด ๋˜๊ธฐ ๋•Œ๋ฌธ์— ์ƒํƒœ๊ฐ€ ์—†๋Š” ์œ„์ ฏ์œผ๋กœ ๋งŒ๋“ค์–ด์ค€๋‹ค. visual studio code๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด stl ํ‚ค์›Œ๋“œ๋กœ ์‰ฝ๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.

class DisplayPictureScreen extends StatelessWidget {
  final String imagePath;

  const DisplayPictureScreen({Key key, this.imagePath}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Display the Picture')),
      body: Image.file(File(imagePath)),
    );
  }
}

imagePath๋ผ๋Š” ๊ณณ์œผ๋กœ ํŒŒ์ผ ์œ„์น˜๋ฅผ ๋„˜๊ฒจ๋ฐ›์•„์„œ ๋ณด์—ฌ์ฃผ๋Š” ํ™”๋ฉด์ด๋‹ค. ์ด์ œ ์ดฌ์˜ํ•ด์„œ ์ด๋ฏธ์ง€ ์ €์žฅ์ด ๋๋‚˜๋ฉด ์ƒˆ๋กœ์šด ํŽ˜์ด์ง€๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด onPressed์˜ takePictureํ•จ์ˆ˜ ์•„๋ž˜์— ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์ค€๋‹ค.

            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => DisplayPictureScreen(imagePath: path),
              ),
            );

์‹คํ–‰ ํ™”๋ฉด

์‹คํ–‰ ์ดฌ์˜

โš ๏ธ **GitHub.com Fallback** โš ๏ธ