Flutter - image_picker - 카메라로 사진 찍고 앱에 보여주기, 갤러리에서 사진 가져오기 (1/2)

 

Overview

이번 포스팅에서는...

  • image_picker 라이브러리를 사용하여
  • 카메라로 촬영된 사진과
  • 갤러리에서 선택한 사진을
  • 보다 쉽게 화면에 출력

하는 것을 다룹니다.

 

  • 카메라 촬영 후 앱에 보여주기

 

  • 갤러리에서 사진 선택후 앱에 보여주기


image_picker 라이브러리 선택 이유

카메라 사용을 위해 현재 pub.dev 에서 가장 좋아요 수가 많은(most likes) 카메라 관련 라이브러리로 선택하여 포스팅을 작성하였습니다.


image_picker

  • 이미지 라이브러리에서 이미지를 선택하기 위한 iOS 및 Android용 Flutter 플러그인
  • 카메라로 새로운 사진을 찍거나 갤러리의 사진을 이용할 수 있음

라이브러리(종속성) 추가

(터미널에서) flutter pub add image_picker

flutter pub add image_picker


카메라를 사용하기 위한 플랫폼 설정

iOS

  • (프로젝트 디렉토리 내에서) ios - Runner - Info.plist

 

  • Info.plist 파일에 아래의 내용을 추가
    • 비디오 녹화가 필요하지 않은 경우 NSMicrophoneUsageDescription 은 제외해도 상관 없음
	<key>NSPhotoLibraryUsageDescription</key>
	<string>갤러리를 사용하기 위한 권한</string>
	<key>NSCameraUsageDescription</key>
	<string>카메라를 사용하기 위한 권한</string>
	<key>NSMicrophoneUsageDescription</key>
	<string>카메라에서 비디오 녹화 시 마이크를 사용하기 위한 권한</string>

Android

기존에는 AndroidManifest.xml에 권한을 추가해주어야 했지만 지금은 따로 추가하지 않아도 됩니다.


Windows, macOS, Linux

image_picker 라이브러리는 데스크톱 플랫폼에서는 제한적으로 지원합니다.

포스팅에서는 모바일을 기준으로 작성하였기 때문에 제한적 지원되는 기능에 대해서는 설명하지 않습니다.

skip~ XD


예제

아래의 예제는 flutter image_picker 공식 문서에서 발췌한 내용입니다.

final ImagePicker picker = ImagePicker();
// Pick an image.
final XFile? image = await picker.pickImage(source: ImageSource.gallery);
// Capture a photo.
final XFile? photo = await picker.pickImage(source: ImageSource.camera);
// Pick a video.
final XFile? galleryVideo =
    await picker.pickVideo(source: ImageSource.gallery);
// Capture a video.
final XFile? cameraVideo = await picker.pickVideo(source: ImageSource.camera);
// Pick multiple images.
final List<XFile> images = await picker.pickMultiImage();
// Pick singe image or video.
final XFile? media = await picker.pickMedia();
// Pick multiple images and videos.
final List<XFile> medias = await picker.pickMultipleMedia();

코딩

아래의 코드 테스트는 안드로이드를 기준으로 테스팅되었습니다.

 

파일 생성 및 기본 설정

  • 카메라 기능을 구현할 파일을 생성해줍니다.
    • cameraView.dart

 

  • image_picker 사용을 위해 최상단에 라이브러리를 호출합니다.
import 'package:image_picker/image_picker.dart';

 

  • 이미지를 불러올 수 있도록 위젯의 상태를 변경 가능하도록 StatefulWidget을 상속합니다.
class CameraView extends StatefulWidget {
  const CameraView({super.key});

  @override
  State<CameraView> createState() => _CameraViewState();
}

class _CameraViewState extends State<CameraView> {
  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}

UI 디자인

  • 카메라와 앨범의 기능을 테스트하기 위하여 위와 같은 화면을 구성합니다.
    • 버튼 클릭 이벤트의 'getImage'
      • 새로 정의할 함수명이며 갤러리나 카메라를 통해 이미지를 호출하는 기능을 합니다.
    • Column - SizedBox의 속해있는 _buildPhotoArea
      • 불러온 이미지를 출력하는 영역입니다.
      • 다른 위젯을 정의하여 상황에 따라 이미지가 있고 없고를 보여주기 위함입니다.
  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Container(
        color: Colors.white,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              SizedBox(
                width: 300,
                height: 300,
                child: _buildPhotoArea(),
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  // 카메라 선택 버튼
                  ElevatedButton(
                    onPressed: () {
                      getImage(ImageSource.camera);
                    },
                    child: const Text("카메라"),
                  ),
                  const Padding(padding: EdgeInsets.all(10)),
                  // 앨범 선택 버튼
                  ElevatedButton(
                    onPressed: () {
                      getImage(ImageSource.gallery);
                    },
                    child: const Text("앨범"),
                  ),
                ],
              )
            ],
          ),
        ),
      ),
    );
  }

 

  • 이미지를 보여주기 위한 위젯 정의
  Widget _buildPhotoArea() {
    // 불러온 이미지가 있는지 없는지 확인
    return _imageFile != null
        // 불러온 이미지가 있으면 출력
        ? Center(
            child: Image.file(
              File(_imageFile!.path),
            ),
          )
        // 불러온 이미지가 없으면 텍스트 출력
        : const Center(
            child: Text("불러온 이미지가 없습니다."),
          );
  }

기능 구현

주석에 모두 적어놓았지만 기본적인 절차를 서술하자면,

'카메라' 혹은 '앨범' 버튼을 클릭하면 상황에 맞는 imageSource 객체가 getImage 파라미터로 사용됩니다.

imageSource 객체는 위의 UI 설계에서 정의해놓은 ElevatedButton에서 입력되는 파라미터를 의미합니다.

  • 버튼에서의 파라미터
    • 카메라 버튼 : image_picker의 ImageSource.camera 속성을 사용하여 카메라를 보여줍니다.
    • 앨범 버튼 : image_picker의 ImageSource.gallery 속성을 사용하여 갤러리(앨범)을 보여줍니다.
  • getImage 함수에서의 파라미터
    • 카메라일 경우 : 카메라로 촬영한 사진을 imageSource로 사용합니다.
    • 앨범일 경우 : 앨범에서 선택한 사진을 imageSource로 사용합니다.
  // Image Picker 인스턴스 생성
  final ImagePicker picker = ImagePicker();

  // 카메라 또는 갤러리의 이미지를 저장할 변수
  XFile? _imageFile;

  // 이미지를 가져오는 함수
  Future<void> getImage(ImageSource imageSource) async {
    try {
      // 카메라 또는 갤러리의 이미지
      final XFile? imageFile = await picker.pickImage(
          source: imageSource, maxHeight: 300, maxWidth: 300);

      if (imageFile != null) {
        // 이미지를 화면에 출력하기 위해 앱의 상태 변경
        setState(() {
          _imageFile = imageFile;
        });
      }
    } catch (e) {
      print("디버깅용 이미지 호출 에러 : $e");
    }
  }

메인 함수와 연결

  • import 부분은 각자의 프로젝트에 맞게 이름을 넣어주세요.
import 'package:flutter/material.dart';
import 'package:flutter_zzicsu/cameraView.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(
    const MaterialApp(
      home: cameraView(),
    ),
  );
}

카메라 기능 구현 전체 소스 코드

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';

class CameraView extends StatefulWidget {
  const CameraView({super.key});

  @override
  State<CameraView> createState() => _CameraViewState();
}

class _CameraViewState extends State<CameraView> {
  // Image Picker 인스턴스 생성
  final ImagePicker picker = ImagePicker();

  // 카메라 또는 갤러리의 이미지를 저장할 변수
  XFile? _imageFile;

  // 이미지를 가져오는 함수
  Future<void> getImage(ImageSource imageSource) async {
    try {
      // 카메라 또는 갤러리의 이미지
      final XFile? imageFile = await picker.pickImage(
          source: imageSource, maxHeight: 300, maxWidth: 300);

      if (imageFile != null) {
        // 이미지를 화면에 출력하기 위해 앱의 상태 변경
        setState(() {
          _imageFile = imageFile;
        });
      }
    } catch (e) {
      print("디버깅용 이미지 호출 에러 : $e");
    }
  }

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Container(
        color: Colors.white,
        child: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              SizedBox(
                width: 300,
                height: 300,
                child: _buildPhotoArea(),
              ),
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  // 카메라 선택 버튼
                  ElevatedButton(
                    onPressed: () {
                      getImage(ImageSource.camera);
                    },
                    child: const Text("카메라"),
                  ),
                  const Padding(padding: EdgeInsets.all(10)),
                  // 앨범 선택 버튼
                  ElevatedButton(
                    onPressed: () {
                      getImage(ImageSource.gallery);
                    },
                    child: const Text("앨범"),
                  ),
                ],
              )
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildPhotoArea() {
    // 불러온 이미지가 있는지 없는지 확인
    return _imageFile != null
        // 불러온 이미지가 있으면 출력
        ? Center(
            child: Image.file(
              File(_imageFile!.path),
            ),
          )
        // 불러온 이미지가 없으면 텍스트 출력
        : const Center(
            child: Text("불러온 이미지가 없습니다."),
          );
  }
}

다음 포스팅

다음 포스팅에서는 불러온 이미지를 자르거나 확대/축소, 회전하는 것을 다룹니다.

아래의 파란 글씨를 누르면 다음 포스팅으로 이동합니다.


참고