본문 바로가기

Flutter

[Flutter] image_picker로 사진 업로드 화면 만들기

image_picker를 사용하면 간단하게 기기에서 사진 파일을 가져올 수 있습니다

image_picker를 사용해서 사진을 업로드하는 화면을 구현해보겠습니다

 

1. image_picker를 설치

Pubspec.yaml 파일 dependencies: 에서 image_picker: ^0.8.7 플러그인 추가

dependencies:
  flutter:
    sdk: flutter
  flutter_localizations:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  image_picker: ^0.8.7

추가하고 Pub get을 눌러주세요

 

2. ios > Runner > Info.plist 파일에 아래 코드를 추가해주세요.

<key>NSCameraUsageDescription</key>
<string>카메라 사용에 필요합니다.</string>
<key>NSMicrophoneUsageDescription</key>
<string>마이크 사용에 필요합니다.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>사진 라이브러리 사용에 필요합니다.</string>
<key>UILaunchStoryboardName</key>

ios > Runner > Info.plist

<?xml version="1.0" encoding="UTF-8"?>
http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
   .
   .
   .
   // 추가
<key>NSCameraUsageDescription</key>
<string>카메라 사용에 필요합니다.</string>
<key>NSMicrophoneUsageDescription</key>
<string>마이크 사용에 필요합니다.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>사진 라이브러리 사용에 필요합니다.</string>
<key>UILaunchStoryboardName</key>
.
.
.
</dict>
</plist>

안드로이드에서는 별도의 설정을 하실 필요가 없습니다.

 

3. 화면을 만들 페이지에서 아래 내용을 import 해주세요

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

 

4. StatefulWidget으로 페이지를 만들고 아래 변수들을 선언해주세요

final picker = ImagePicker();
XFile? image; // 카메라로 촬영한 이미지를 저장할 변수
List<XFile?> multiImage = []; // 갤러리에서 여러 장의 사진을 선택해서 저장할 변수
List<XFile?> images = []; // 가져온 사진들을 보여주기 위한 변수

 

5. 아이콘 버튼 2개를 만들어줍니다(카메라로 촬영하기, 갤러리에서 가져오기)

 

 

 

//카메라로 촬영하기
Container(
    margin: EdgeInsets.all(10),
    padding: EdgeInsets.all(5),
    decoration: BoxDecoration(color: Colors.lightBlueAccent, borderRadius: BorderRadius.circular(5),
      boxShadow: [
        BoxShadow(color: Colors.grey.withOpacity(0.5), spreadRadius: 0.5, blurRadius: 5)
      ],
    ),
    child: IconButton(
        onPressed: () async {
          image = await picker.pickImage(source: ImageSource.camera);
          //카메라로 촬영하지 않고 뒤로가기 버튼을 누를 경우, null값이 저장되므로 if문을 통해 null이 아닐 경우에만 images변수로 저장하도록 합니다
          if (image != null) {
            setState(() {
              images.add(image);
            });
          }
        },
        icon: Icon(Icons.add_a_photo, size: 30, color: Colors.white,)
    )
),

//갤러리에서 가져오기
Container(
    margin: EdgeInsets.all(10),
    padding: EdgeInsets.all(5),
    decoration: BoxDecoration(color: Colors.lightBlueAccent, borderRadius: BorderRadius.circular(5),
      boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.5), spreadRadius: 0.5, blurRadius: 5)],
    ),
    child: IconButton(
        onPressed: () async {multiImage = await picker.pickMultiImage();
          setState(() {
            //multiImage를 통해 갤러리에서 가지고 온 사진들은 리스트 변수에 저장되므로 addAll()을 사용해서 images와 multiImage 리스트를 합쳐줍니다. 
            images.addAll(multiImage);
          });
        },
        icon: Icon(
          Icons.add_photo_alternate_outlined,
          size: 30,
          color: Colors.white,
        )
    )
),

 

아이콘을 클릭하면 각각 카메라 촬영화면, 갤러리로 넘어가는 것을 확인할 수 있습니다.

 

 

 

6. 가져온 사진 보여주기

카메라로 촬영하거나 갤러리에서 가져온 사진들을 모두 images 변수에 저장되도록 했습니다.

이제 GridView.builder 위젯을 사용해서 사진들을 그리드 모양으로 보여주도록 하겠습니다.

 

 

 

Container(
  margin: EdgeInsets.all(10),
  child: GridView.builder(padding: EdgeInsets.all(0),
    shrinkWrap: true,
    itemCount: images.length, //보여줄 item 개수. images 리스트 변수에 담겨있는 사진 수 만큼.
    gridDelegate:
    SliverGridDelegateWithFixedCrossAxisCount(
      crossAxisCount: 3, //1 개의 행에 보여줄 사진 개수
      childAspectRatio:
      1 / 1, //사진 의 가로 세로의 비율
      mainAxisSpacing: 10, //수평 Padding
      crossAxisSpacing: 10, //수직 Padding
    ),
    itemBuilder: (BuildContext context, int index) {
    // 사진 오른 쪽 위 삭제 버튼을 표시하기 위해 Stack을 사용함
      return Stack(
        alignment: Alignment.topRight,
        children: [
          Container(
            decoration: BoxDecoration(
                borderRadius: BorderRadius.circular(5),
                image:
                DecorationImage(
                    fit: BoxFit.cover,  //사진 크기를 Container 크기에 맞게 조절
                    image: FileImage(File(images[index]!.path   // images 리스트 변수 안에 있는 사진들을 순서대로 표시함
                    ))
                )
            ),
          ),
          Container(
              decoration: BoxDecoration(
                color: Colors.black,
                borderRadius:
                BorderRadius.circular(5),
              ),
              //삭제 버튼
              child: IconButton(
                padding: EdgeInsets.zero,
                constraints: BoxConstraints(),
                icon: Icon(Icons.close,
                    color: Colors.white,
                    size: 15),
                onPressed: () {
                  //버튼을 누르면 해당 이미지가 삭제됨
                  setState(() {
                    images.remove(images[index]);
                  });
                },
              )
          )
        ],
      );
    },
  ),
),

 

이렇게 하면 가져온 사진들이 정상적으로 보이는 것을 확인하실 수 있습니다. 이제 버튼을 만들어서 사진들을 전송하거나 DB에 저장하는 등의 방법으로 활용하시면 되겠습니다.

 

 

※ 전체 코드

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

class ImageUpload extends StatefulWidget {

  @override
  State<ImageUpload> createState() => ImageUploadState();
}

final picker = ImagePicker();
XFile? image; // 카메라로 촬영한 이미지를 저장할 변수
List<XFile?> multiImage = []; // 갤러리에서 여러장의 사진을 선택해서 저장할 변수
List<XFile?> images = []; // 가져온 사진들을 보여주기 위한 변수

class ImageUploadState extends State<ImageUpload> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        padding: EdgeInsets.all(10),
        child: Column(
          children: [
            SizedBox(
              height: 50,
            ),
            Row(
              children: [
                //카메라로 촬영하기
                Container(
                    margin: EdgeInsets.all(10),
                    padding: EdgeInsets.all(5),
                    decoration: BoxDecoration(color: Colors.lightBlueAccent, borderRadius: BorderRadius.circular(5),
                      boxShadow: [
                        BoxShadow(color: Colors.grey.withOpacity(0.5), spreadRadius: 0.5, blurRadius: 5)
                      ],
                    ),
                    child: IconButton(
                        onPressed: () async {
                          image = await picker.pickImage(source: ImageSource.camera);
                          //카메라로 촬영하지 않고 뒤로가기 버튼을 누를 경우, null값이 저장되므로 if문을 통해 null이 아닐 경우에만 images변수로 저장하도록 합니다
                          if (image != null) {
                            setState(() {
                              images.add(image);
                            });
                          }
                        },
                        icon: Icon(Icons.add_a_photo, size: 30, color: Colors.white,)
                    )
                ),
                //갤러리에서 가져오기
                Container(
                    margin: EdgeInsets.all(10),
                    padding: EdgeInsets.all(5),
                    decoration: BoxDecoration(color: Colors.lightBlueAccent, borderRadius: BorderRadius.circular(5),
                      boxShadow: [BoxShadow(color: Colors.grey.withOpacity(0.5), spreadRadius: 0.5, blurRadius: 5)],
                    ),
                    child: IconButton(
                        onPressed: () async {multiImage = await picker.pickMultiImage();
                          setState(() {
                            //갤러리에서 가지고 온 사진들은 리스트 변수에 저장되므로 addAll()을 사용해서 images와 multiImage 리스트를 합쳐줍니다.
                            images.addAll(multiImage);
                          });
                        },
                        icon: Icon(
                          Icons.add_photo_alternate_outlined,
                          size: 30,
                          color: Colors.white,
                        )
                    )
                ),
              ],
            ),
            Container(
              margin: EdgeInsets.all(10),
              child: GridView.builder(padding: EdgeInsets.all(0),
                shrinkWrap: true,
                itemCount: images.length, //보여줄 item 개수. images 리스트 변수에 담겨있는 사진 수 만큼.
                gridDelegate:
                SliverGridDelegateWithFixedCrossAxisCount(
                  crossAxisCount: 3, //1 개의 행에 보여줄 사진 개수
                  childAspectRatio:
                  1 / 1, //사진 의 가로 세로의 비율
                  mainAxisSpacing: 10, //수평 Padding
                  crossAxisSpacing: 10, //수직 Padding
                ),
                itemBuilder: (BuildContext context, int index) {
                // 사진 오른 쪽 위 삭제 버튼을 표시하기 위해 Stack을 사용함
                  return Stack(
                    alignment: Alignment.topRight,
                    children: [
                      Container(
                        decoration: BoxDecoration(
                            borderRadius: BorderRadius.circular(5),
                            image:
                            DecorationImage(
                                fit: BoxFit.cover,  //사진을 크기를 상자 크기에 맞게 조절
                                image: FileImage(File(images[index]!.path   // images 리스트 변수 안에 있는 사진들을 순서대로 표시함
                                ))
                            )
                        ),
                      ),
                      Container(
                          decoration: BoxDecoration(
                            color: Colors.black,
                            borderRadius:
                            BorderRadius.circular(5),
                          ),
                          //삭제 버튼
                          child: IconButton(
                            padding: EdgeInsets.zero,
                            constraints: BoxConstraints(),
                            icon: Icon(Icons.close,
                                color: Colors.white,
                                size: 15),
                            onPressed: () {
                              //버튼을 누르면 해당 이미지가 삭제됨
                              setState(() {
                                images.remove(images[index]);
                              });
                            },
                          )
                      )
                    ],
                  );
                },
              ),
            ),
          ],
        )
      )
    );
  }
}