Flutter Example : Photo Sticker – Step 1

image_picker, GestureDetector, Positioned를 활용하여 사진에 스티커를 붙이는 기능을 가진 Flutter APP를 만들어 보겠습니다.

이번 단계에서는 사진을 선택해서 배경 이미지를 적용하고 스티커 이미지를 선택할 수 있는 UI와 선택한 1개의 스티커를 배경 사진 위에 표시하는 것 까지 구현해 보기로 합니다.

구현

pubspec.yaml

...
dependencies:
  flutter:
    sdk: flutter

  image_picker: ^1.0.7
...
# The following section is specific to Flutter packages.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg
  assets:
    - assets/stickers/

main.dart

import 'dart:io'; // File 클래스를 사용하기 위해 필요
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '스티커 앱',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const StickerEditorPage(),
    );
  }
}

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

  @override
  State<StickerEditorPage> createState() => _StickerEditorPageState();
}

class _StickerEditorPageState extends State<StickerEditorPage> {
  File? _selectedImage;
  final ImagePicker _picker = ImagePicker();

  // --- 스티커 관련 상태 변수 추가 ---
  String? _selectedSticker; // 선택된 스티커의 경로 저장
  final List<String> _stickerPaths = [ // 사용 가능한 스티커 목록
    'assets/stickers/emoticon_1.png',
    'assets/stickers/emoticon_2.png',
    'assets/stickers/emoticon_3.png',
    'assets/stickers/emoticon_4.png',
    'assets/stickers/emoticon_5.png',
    'assets/stickers/emoticon_6.png',
    'assets/stickers/emoticon_7.png',
    // 필요에 따라 더 많은 스티커 경로 추가
  ];
  // ------------------------------------

  Future<void> _pickImageFromGallery() async {
    final XFile? pickedFile = await _picker.pickImage(source: ImageSource.gallery);

    if (pickedFile != null) {
      setState(() {
        _selectedImage = File(pickedFile.path);
        _selectedSticker = null; // 새 이미지 선택 시 스티커 선택 초기화
      });
    }
  }

  // --- 스티커 선택 함수 추가 ---
  void _selectSticker(String stickerPath) {
    setState(() {
      _selectedSticker = stickerPath;
    });
  }
  // ---------------------------

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('스티커 붙이기'),
      ),
      body: Column( // 전체 레이아웃을 Column으로 변경
        children: <Widget>[
          // 1. 이미지 및 스티커 표시 영역 (Expanded 사용)
          Expanded(
            child: Center(
              child: _selectedImage == null
                  ? const Text('사진을 선택해주세요.')
                  : Stack( // 이미지 위에 스티커를 올리기 위해 Stack 위젯 사용
                alignment: Alignment.center, // Stack 내 자식들 중앙 정렬
                children: <Widget>[
                  Image.file(_selectedImage!),
                  if (_selectedSticker != null)
                    Positioned( // 스티커 위치를 정밀하게 제어 (여기서는 중앙)
                      // 이 부분을 GestureDetector로 감싸서 드래그 기능 추가 가능
                      child: Image.asset(
                        _selectedSticker!,
                        width: 100, // 스티커 크기 조절
                        height: 100,
                      ),
                    ),
                ],
              ),
            ),
          ),

          // 2. 스티커 선택 UI (화면 하단에 표시)
          if (_selectedImage != null) // 이미지가 선택된 경우에만 스티커 목록 표시
            Container(
              height: 120, // 스티커 목록의 높이
              padding: const EdgeInsets.symmetric(vertical: 8.0),
              color: Colors.grey[200],
              child: ListView.builder(
                scrollDirection: Axis.horizontal, // 가로 스크롤
                itemCount: _stickerPaths.length,
                itemBuilder: (context, index) {
                  final stickerPath = _stickerPaths[index];
                  return GestureDetector(
                    onTap: () => _selectSticker(stickerPath),
                    child: Padding(
                      padding: const EdgeInsets.symmetric(horizontal: 8.0),
                      child: Container(
                        decoration: BoxDecoration(
                          border: _selectedSticker == stickerPath
                              ? Border.all(color: Colors.blue, width: 2) // 선택된 스티커 강조
                              : null,
                          borderRadius: BorderRadius.circular(8),
                        ),
                        child: Image.asset(
                          stickerPath,
                          width: 80,
                          height: 80,
                          fit: BoxFit.contain,
                        ),
                      ),
                    ),
                  );
                },
              ),
            ),

          // 3. 사진 선택 버튼
          Padding(
            padding: const EdgeInsets.all(16.0),
            child: ElevatedButton(
              onPressed: _pickImageFromGallery,
              child: Text(_selectedImage == null ? '사진 선택하기' : '다른 사진 선택'),
            ),
          ),
        ],
      ),
    );
  }
}

image_picker 패키지

ImagePicker 클래스를 사용하여 갤러리(ImageSource.gallery) 또는 카메라(ImageSource.camera)에서 이미지를 가져올 수 있습니다.

pickImage() 메서드는 XFile 객체를 반환하며, 이를 File 객체로 변환하여 사용합니다.

_selectedImage 변수

사용자가 선택한 이미지 파일을 저장합니다. File? 타입으로, 처음에는 null 값을 가집니다.

_pickImageFromGallery() 함수

ImagePicker를 사용하여 갤러리에서 이미지를 선택합니다.

이미지가 성공적으로 선택되면 setState를 호출하여 UI를 업데이트하고 _selectedImage에 선택된 이미지 파일을 할당합니다.

build() 메서드

_selectedImage가 null이 아니면 Image.file() 위젯을 사용하여 선택된 이미지를 화면에 표시합니다.

“사진 선택하기” 버튼을 누르면 _pickImageFromGallery() 함수가 호출됩니다


Reference

https://heavenly.tistory.com/entry/Flutter-기본-기능-as-show-hide-변수나-함수-클래스명이-같은-경우-해결-방법-example

“Flutter Example : Photo Sticker – Step 1”에 대한 1개의 생각

댓글 남기기