본문 바로가기

DEV/FLUTTER

[FLUTTER] 프로필 화면 만들기 #1 ( Image Picker 이용하여 카메라 및 라이브러리 사용)

반응형

Image picker 라이브러리를 추가해준다. 링크

flutter pub add image_picker

iOS 에뮬레이터의 경우 카메라 기능을 사용할 수 없다.

 

그리고 iOS의 경우 <project root>/ios/Runner/Info.plist 파일에 내용을 추가하여 권한 설정을 해주어야한다.

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

아래는 전체 파일의 내용

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>$(DEVELOPMENT_LANGUAGE)</string>
	<key>CFBundleDisplayName</key>
	<string>Youtube Camera</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>youtube_camera</string>
	<key>CFBundlePackageType</key>
	<string>APPL</string>
	<key>CFBundleShortVersionString</key>
	<string>$(FLUTTER_BUILD_NAME)</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>$(FLUTTER_BUILD_NUMBER)</string>
	<key>LSRequiresIPhoneOS</key>
	<true/>
    <key>NSCameraUsageDescription</key>
    <string>카메라 사용에 필요합니다.</string>
    <key>NSMicrophoneUsageDescription</key>
    <string>마이크 사용에 필요합니다.</string>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>사진라이브러리 사용에 필요합니다.</string>
	<key>UILaunchStoryboardName</key>
	<string>LaunchScreen</string>
	<key>UIMainStoryboardFile</key>
	<string>Main</string>
	<key>UISupportedInterfaceOrientations</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UISupportedInterfaceOrientations~ipad</key>
	<array>
		<string>UIInterfaceOrientationPortrait</string>
		<string>UIInterfaceOrientationPortraitUpsideDown</string>
		<string>UIInterfaceOrientationLandscapeLeft</string>
		<string>UIInterfaceOrientationLandscapeRight</string>
	</array>
	<key>UIViewControllerBasedStatusBarAppearance</key>
	<false/>
	<key>CADisableMinimumFrameDurationOnPhone</key>
	<true/>
</dict>
</plist>

안드로이드의 경우 따로 설정해줄 필요는 없습니다.

 

먼저 기본 화면을 디자인해줍니다.

 

이미지를 선택하기 전에 기본 이미지 아이콘을 만들어줍니다.

 

import 'package:flutter/material.dart';

class Example1Page extends StatefulWidget {
  const Example1Page({Key? key}) : super(key: key);

  @override
  State<Example1Page> createState() => _Example1PageState();
}

class _Example1PageState extends State<Example1Page> {

  @override
  Widget build(BuildContext context) {

    final imageSize = MediaQuery.of(context).size.width / 4;

    return Scaffold(
      appBar: AppBar(title: const Text('Example1'),),
      body: Column(
        children: [
          Container(
            constraints: BoxConstraints(
              minHeight: MediaQuery.of(context).size.width,
              minWidth: MediaQuery.of(context).size.width,
            ),
            child: Center(
              child: Icon(
                Icons.account_circle,
                size: imageSize,
              ),
            ),
          ),
        ],
      ),
    );
  }
}

 

 

기본 프로필 이미지

 

 

사진 아이콘을 누를경우 카메라와, 사진 라이브러리에 접근할수 있도록 bottomModalSheet 불러오는 이벤트를 만들어주고 bottomModalSheet에 버튼을 만들어줍니다.

 

import 'package:flutter/material.dart';

class Example1Page extends StatefulWidget {
  const Example1Page({Key? key}) : super(key: key);

  @override
  State<Example1Page> createState() => _Example1PageState();
}

class _Example1PageState extends State<Example1Page> {
  @override
  Widget build(BuildContext context) {
    final _imageSize = MediaQuery.of(context).size.width / 4;

    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Example1'),
        ),
        body: Column(
          children: [
            Container(
              constraints: BoxConstraints(
                minHeight: _imageSize,
                minWidth: _imageSize,
              ),
              child: GestureDetector(
                onTap: () {
                  _showBottomSheet();
                },
                child: Center(
                  child: Icon(
                    Icons.account_circle,
                    size: imageSize,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  _showBottomSheet() {
    return showModalBottomSheet(
      context: context,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(
          top: Radius.circular(25),
        ),
      ),
      builder: (context) {
        return Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const SizedBox(
              height: 20,
            ),
            ElevatedButton(
              onPressed: () {},
              child: const Text('사진찍기'),
            ),
            const SizedBox(
              height: 10,
            ),
            const Divider(
              thickness: 3,
            ),
            const SizedBox(
              height: 10,
            ),
            ElevatedButton(
              onPressed: () {},
              child: const Text('라이브러리에서 불러오기'),
            ),
            const SizedBox(
              height: 20,
            ),
          ],
        );
      },
    );
  }
}

 

 

프로필 페이지 bottomModalSheet

 

반응형

 

기본적인 UI작업은 끝이 났다 이제 카메라나 라이브러리 이미지를 불러와서 사용할 부분을 구현해보자.

 

먼저 파일을 받을 변수를 하나 선언해주자

 

XFile? _pickedFile;

해당 변수가 null인경우 이전에 만들어논 아이콘 이미지를 보여주고 해당 변수에 값이 들어오면 해당하는 이미지를 보여주는 부분을 구현해보자.

 

body: Column(
  children: [
    if (_pickedFile == null)
      Container(
        constraints: BoxConstraints(
          minHeight: _imageSize,
          minWidth: _imageSize ,
        ),
        child: GestureDetector(
          onTap: () {
            _showBottomSheet();
          },
          child: Center(
            child: Icon(
              Icons.account_circle,
              size: imageSize,
            ),
          ),
        ),
      )
    else
      Container(
        width: _imageSize,
        height: _imageSize,
        decoration: BoxDecoration(
          shape: BoxShape.circle,
          border: Border.all(
              width: 2, color: Theme.of(context).colorScheme.primary),
          image: DecorationImage(
              image: FileImage(File(_pickedFile!.path)),
              fit: BoxFit.cover),
        ),
      ),
  ],
),

이제 카메라를 불러오는 부분을 구현해보자 iOS는 에뮬레이터에서 카메라가 작동안하기 때문에 안드로이드로 테스트를 하였다.

 

카메라 연동하기를 구현한다.

_getCameraImage() async {
  final pickedFile = await ImagePicker().pickImage(source: ImageSource.camera);
  if (pickedFile != null) {
    setState(() {
      _pickedFile = pickedFile;
    });
  } else {
    if (kDebugMode) {
      print('이미지 선택안함');
    }
  }
}

 

갤러리 불러오기를 구현한다.

 

_getPhotoLibraryImage() async {
  final pickedFile =
      await ImagePicker().pickImage(source: ImageSource.gallery);
  if (pickedFile != null) {
    setState(() {
      _pickedFile = _pickedFile;
    });
  } else {
    if (kDebugMode) {
      print('이미지 선택안함');
    }
  }
}

해당 함수를 버튼과 연결해준다.

 

_showBottomSheet() {
  return showModalBottomSheet(
    context: context,
    shape: const RoundedRectangleBorder(
      borderRadius: BorderRadius.vertical(
        top: Radius.circular(25),
      ),
    ),
    builder: (context) {
      return Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          const SizedBox(
            height: 20,
          ),
          ElevatedButton(
            onPressed: () => _getCameraImage(),
            child: const Text('사진찍기'),
          ),
          const SizedBox(
            height: 10,
          ),
          const Divider(
            thickness: 3,
          ),
          const SizedBox(
            height: 10,
          ),
          ElevatedButton(
            onPressed: () => _getPhotoLibraryImage(),
            child: const Text('라이브러리에서 불러오기'),
          ),
          const SizedBox(
            height: 20,
          ),
        ],
      );
    },
  );
}

 

 

최종 결과

 

최종  소스 코드 

 

import 'dart:io';

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

class Example1Page extends StatefulWidget {
  const Example1Page({Key? key}) : super(key: key);

  @override
  State<Example1Page> createState() => _Example1PageState();
}

class _Example1PageState extends State<Example1Page> {
  XFile? _pickedFile;

  @override
  Widget build(BuildContext context) {
    final _imageSize = MediaQuery.of(context).size.width / 4;

    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Example1'),
        ),
        body: Column(
          children: [
            const SizedBox(height: 20,),
            if (_pickedFile == null)
              Container(
                constraints: BoxConstraints(
                  minHeight: _imageSize,
                  minWidth: _imageSize,
                ),
                child: GestureDetector(
                  onTap: () {
                    _showBottomSheet();
                  },
                  child: Center(
                    child: Icon(
                      Icons.account_circle,
                      size: _imageSize,
                    ),
                  ),
                ),
              )
            else
              Center(
                child: Container(
                  width: _imageSize,
                  height: _imageSize,
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    border: Border.all(
                        width: 2, color: Theme.of(context).colorScheme.primary),
                    image: DecorationImage(
                        image: FileImage(File(_pickedFile!.path)),
                        fit: BoxFit.cover),
                  ),
                ),
              ),
          ],
        ),
      ),
    );
  }

  _showBottomSheet() {
    return showModalBottomSheet(
      context: context,
      shape: const RoundedRectangleBorder(
        borderRadius: BorderRadius.vertical(
          top: Radius.circular(25),
        ),
      ),
      builder: (context) {
        return Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const SizedBox(
              height: 20,
            ),
            ElevatedButton(
              onPressed: () => _getCameraImage(),
              child: const Text('사진찍기'),
            ),
            const SizedBox(
              height: 10,
            ),
            const Divider(
              thickness: 3,
            ),
            const SizedBox(
              height: 10,
            ),
            ElevatedButton(
              onPressed: () => _getPhotoLibraryImage(),
              child: const Text('라이브러리에서 불러오기'),
            ),
            const SizedBox(
              height: 20,
            ),
          ],
        );
      },
    );
  }

  _getCameraImage() async {
    final pickedFile =
        await ImagePicker().pickImage(source: ImageSource.camera);
    if (pickedFile != null) {
      setState(() {
        _pickedFile = pickedFile;
      });
    } else {
      if (kDebugMode) {
        print('이미지 선택안함');
      }
    }
  }

  _getPhotoLibraryImage() async {
    final pickedFile =
        await ImagePicker().pickImage(source: ImageSource.gallery);
    if (pickedFile != null) {
      setState(() {
        _pickedFile = _pickedFile;
      });
    } else {
      if (kDebugMode) {
        print('이미지 선택안함');
      }
    }
  }  

 

해당 소스는 깃허브에서 확인해 볼수 있다.

깃허브 링크 

반응형