이미지와 아이콘 — Asset 관리부터 CachedNetworkImage까지

앱에서 이미지는 필수 요소입니다. Flutter에서 로컬 에셋 이미지, 네트워크 이미지, 아이콘을 다루는 방법과 성능 최적화를 정리해보겠습니다.


로컬 에셋 이미지

에셋 등록

YAML
# pubspec.yaml
flutter:
  assets:
    - assets/images/          # 폴더 전체 등록
    - assets/images/logo.png  # 개별 파일 등록

프로젝트 루트에 assets/images/ 폴더를 만들고 이미지를 넣으면 됩니다.

해상도별 이미지 제공

PLAINTEXT
assets/
  images/
    logo.png          # 1x (기본)
    2.0x/
      logo.png        # 2x
    3.0x/
      logo.png        # 3x

Flutter가 디바이스 해상도에 맞는 이미지를 자동으로 선택합니다.

사용

DART
// 기본 사용
Image.asset('assets/images/logo.png')

// 크기 지정
Image.asset(
  'assets/images/logo.png',
  width: 200,
  height: 100,
  fit: BoxFit.contain,
)

네트워크 이미지

DART
// 기본 네트워크 이미지
Image.network(
  'https://example.com/photo.jpg',
  width: 300,
  height: 200,
  fit: BoxFit.cover,
  // 로딩 중 표시
  loadingBuilder: (context, child, loadingProgress) {
    if (loadingProgress == null) return child;
    return Center(
      child: CircularProgressIndicator(
        value: loadingProgress.expectedTotalBytes != null
            ? loadingProgress.cumulativeBytesLoaded /
                loadingProgress.expectedTotalBytes!
            : null,
      ),
    );
  },
  // 에러 시 표시
  errorBuilder: (context, error, stackTrace) {
    return const Icon(Icons.error, size: 48);
  },
)

BoxFit 옵션

설명
BoxFit.contain비율 유지, 전체가 보이도록
BoxFit.cover비율 유지, 영역을 꽉 채움 (잘릴 수 있음)
BoxFit.fill비율 무시, 영역에 맞게 늘림
BoxFit.fitWidth너비에 맞춤
BoxFit.fitHeight높이에 맞춤
BoxFit.none원본 크기 그대로
BoxFit.scaleDowncontain과 비슷, 축소만 함

실무에서는 프로필 사진에 cover, 로고에 contain을 가장 많이 씁니다.


CachedNetworkImage — 캐싱 적용

네트워크 이미지를 매번 다운로드하면 비효율적입니다. cached_network_image 패키지로 디스크 캐싱을 적용하세요.

YAML
# pubspec.yaml
dependencies:
  cached_network_image: ^3.3.0
DART
import 'package:cached_network_image/cached_network_image.dart';

CachedNetworkImage(
  imageUrl: 'https://example.com/photo.jpg',
  width: 300,
  height: 200,
  fit: BoxFit.cover,
  // 로딩 중 플레이스홀더
  placeholder: (context, url) => const Center(
    child: CircularProgressIndicator(),
  ),
  // 에러 시
  errorWidget: (context, url, error) => const Icon(Icons.error),
  // 캐시 기간 설정 (기본 30일)
  cacheManager: CacheManager(
    Config(
      'customCacheKey',
      stalePeriod: const Duration(days: 7),
    ),
  ),
)

ClipRRect — 둥근 모서리 이미지

DART
// 둥근 모서리
ClipRRect(
  borderRadius: BorderRadius.circular(12),
  child: Image.network(
    'https://example.com/photo.jpg',
    width: 200,
    height: 200,
    fit: BoxFit.cover,
  ),
)

// 원형 이미지
ClipOval(
  child: Image.network(
    'https://example.com/avatar.jpg',
    width: 80,
    height: 80,
    fit: BoxFit.cover,
  ),
)

// CircleAvatar (프로필 이미지에 최적)
CircleAvatar(
  radius: 40,
  backgroundImage: NetworkImage('https://example.com/avatar.jpg'),
  // 로딩/에러 시 대체
  child: const Icon(Icons.person),
)

아이콘

Material Icons

DART
// 기본 아이콘
Icon(
  Icons.favorite,
  color: Colors.red,
  size: 32,
)

// 아이콘 버튼
IconButton(
  icon: const Icon(Icons.share),
  onPressed: () {},
  tooltip: '공유하기',
)

커스텀 아이콘 (SVG)

YAML
# pubspec.yaml
dependencies:
  flutter_svg: ^2.0.0
DART
import 'package:flutter_svg/flutter_svg.dart';

// SVG 에셋 표시
SvgPicture.asset(
  'assets/icons/custom_icon.svg',
  width: 32,
  height: 32,
  colorFilter: const ColorFilter.mode(
    Colors.blue,
    BlendMode.srcIn,
  ),
)

// SVG 네트워크 이미지
SvgPicture.network(
  'https://example.com/icon.svg',
  width: 32,
  height: 32,
)

DecorationImage — Container 배경 이미지

DART
Container(
  width: double.infinity,
  height: 200,
  decoration: BoxDecoration(
    borderRadius: BorderRadius.circular(12),
    image: const DecorationImage(
      image: AssetImage('assets/images/background.jpg'),
      fit: BoxFit.cover,
      // 어둡게 오버레이
      colorFilter: ColorFilter.mode(
        Colors.black45,
        BlendMode.darken,
      ),
    ),
  ),
  child: const Center(
    child: Text(
      '배경 이미지 위의 텍스트',
      style: TextStyle(
        color: Colors.white,
        fontSize: 24,
        fontWeight: FontWeight.bold,
      ),
    ),
  ),
)

FadeInImage — 로딩 전환 효과

DART
FadeInImage(
  placeholder: const AssetImage('assets/images/placeholder.png'),
  image: const NetworkImage('https://example.com/photo.jpg'),
  fadeInDuration: const Duration(milliseconds: 300),
  fit: BoxFit.cover,
  width: 300,
  height: 200,
)

// 메모리 플레이스홀더 (투명 이미지)
FadeInImage.memoryNetwork(
  placeholder: kTransparentImage,
  image: 'https://example.com/photo.jpg',
  fit: BoxFit.cover,
)

정리

  • 로컬 이미지는 pubspec.yaml에 에셋 등록 후 Image.asset() 사용
  • 해상도별 이미지(1x, 2x, 3x)를 제공하면 Flutter가 자동 선택합니다
  • 네트워크 이미지는 cached_network_image로 캐싱하세요
  • 프로필 이미지에는 CircleAvatar, 둥근 모서리에는 ClipRRect
  • SVG는 flutter_svg 패키지로 처리합니다
  • BoxFit.coverBoxFit.contain의 차이를 이해하고 용도에 맞게 사용하세요
댓글 로딩 중...