[Flutter] 그룹 채팅 아바타 위젯 만들기

FlutterPyo·2026년 1월 28일

들어가며

채팅 앱을 만들다 보면 그룹 채팅방 목록에서 참여자들의 프로필 사진을 겹쳐서 보여주는 UI가 필요하다. 카카오톡, WhatsApp, Telegram, 슬랙 같은 앱에서 흔히 볼 수 있는 그 UI다.

단순히 이미지 하나 보여주는 게 아니라, 참여자 수에 따라 레이아웃이 바뀌어야 한다. 2명이면 대각선으로 겹치고, 3명이면 삼각형, 4명 이상이면 그리드 형태로 보여주는 식이다.

React Native나 네이티브(Android, iOS)에서는 이런 스택 아바타 라이브러리가 꽤 있다. 그런데 Flutter에서는 마땅한 게 없었다. 찾아보면 있긴 한데 기능이 부족하거나 커스터마이징이 안 되거나 유지보수가 안 되고 있었다. 그래서 직접 만들었다.


다른 플랫폼은 어떤가

React Native

RN 생태계에서는 react-native-stacked-avatar, react-native-group-avatar 같은 패키지들이 있다. npm에서 "group avatar", "stacked avatar"로 검색하면 꽤 많이 나온다. 다운로드 수도 많고, 옵션도 다양하다.

겹침 정도 조절, 최대 표시 개수, "+N" 배지, 테두리 색상 등 왠만한 커스터마이징은 다 된다.

네이티브 (Android/iOS)

Android에서는 AvatarView, StackedAvatarView, GroupAvatarView 같은 라이브러리가 있다. iOS도 Swift 패키지나 CocoaPods으로 비슷한 게 있다.

네이티브 쪽은 아무래도 생태계가 오래되다 보니 이런 UI 컴포넌트도 잘 갖춰져 있다.

Flutter

Flutter에서 "group avatar", "stacked avatar", "chat avatar"로 pub.dev를 검색해봤다. 있긴 있는데 문제가 있었다.

  • 어떤 건 레이아웃이 고정되어 있어서 멤버 수에 따른 자동 조정이 안 됨
  • 어떤 건 원형만 지원하고 정사각형이나 둥근 정사각형은 안 됨
  • 어떤 건 마지막 커밋이 2년 전이라 유지보수가 안 되고 있음
  • 어떤 건 의존성이 너무 많음

채팅 앱에서 쓰려면 이런 게 다 필요한데, 하나로 해결되는 패키지가 없었다.


직접 만들기로 결정

기존 패키지를 포크해서 수정할까도 생각했는데, 차라리 처음부터 만드는 게 나을 것 같았다. 내가 원하는 기능을 다 넣으면서 구조도 깔끔하게 가져갈 수 있으니까.

그래서 chat_group_avatar를 만들었다.


chat_group_avatar 소개

그룹 채팅 아바타를 표시하는 Flutter 패키지다. 카카오톡, WhatsApp, Telegram 같은 채팅 앱의 그룹 아바타 UI를 쉽게 구현할 수 있다.

핵심 기능

1. 적응형 레이아웃

멤버 수에 따라 레이아웃이 자동으로 바뀐다. 개발자가 조건문으로 분기할 필요 없이, 이미지 URL 리스트만 넘기면 알아서 처리된다.

2. 다양한 모양 지원

원형뿐만 아니라 정사각형, 둥근 정사각형도 지원한다. 앱 디자인에 맞게 선택할 수 있다.

3. 스택/그리드 레이아웃

이미지를 겹쳐서 보여주는 스택 레이아웃과, 격자로 보여주는 그리드 레이아웃을 선택할 수 있다. 겹침 정도도 조절 가능하다.

4. 카운터 배지

멤버가 많을 때 일부만 보여주고 "+N" 형태로 나머지 인원을 표시할 수 있다.

5. 캐시 이미지

cached_network_image를 내장해서 네트워크 이미지를 효율적으로 로드한다. 별도 설정 없이 메모리/디스크 캐싱이 적용된다.

6. 제스처 지원

탭, 롱프레스 콜백을 지원한다. 그룹 아바타를 눌렀을 때 멤버 목록을 보여주는 등의 인터랙션을 구현할 수 있다.


설치

dependencies:
  chat_group_avatar: ^0.1.0
flutter pub get

의존성은 cached_network_image 하나뿐이다. 불필요한 의존성을 최소화했다.


기본 사용법

import 'package:chat_group_avatar/chat_group_avatar.dart';

GroupAvatar(
  imageUrls: [
    'https://example.com/user1.jpg',
    'https://example.com/user2.jpg',
    'https://example.com/user3.jpg',
  ],
  size: 56,
)

이미지 URL 리스트와 크기만 넘기면 된다. 멤버 수에 따라 알아서 레이아웃이 결정된다.


레이아웃 동작 방식

멤버 수에 따라 자동으로 레이아웃이 결정된다. 이게 이 패키지의 핵심 기능이다.

1명일 때

단일 이미지로 표시된다. 일반적인 프로필 이미지와 동일하다.

    ┌─────┐
    │     │
    │  1  │
    │     │
    └─────┘

1:1 채팅방이나 멤버가 한 명만 남은 그룹 채팅방에서 이렇게 보인다.

2명일 때

대각선으로 겹쳐서 표시된다. 왼쪽 위에 첫 번째 이미지, 오른쪽 아래에 두 번째 이미지가 배치된다.

    ┌─────┐
    │  1  │───┐
    └───┬─│ 2 │
        └─────┘

두 명이 대화하는 느낌을 준다.

3명일 때

삼각형 형태로 배치된다. 위에 한 명, 아래에 두 명이 배치된다.

       ┌───┐
       │ 1 │
       └───┘
    ┌───┐ ┌───┐
    │ 2 │ │ 3 │
    └───┘ └───┘

세 명이 모여있는 느낌을 준다.

4명 이상일 때

2x2 그리드로 표시된다. 4명을 초과하면 마지막 자리에 "+N" 카운터가 표시된다.

    ┌───┐ ┌───┐
    │ 1 │ │ 2 │
    └───┘ └───┘
    ┌───┐ ┌───┐
    │ 3 │ │+2 │
    └───┘ └───┘

그룹 채팅방에서 참여자가 많을 때 이런 식으로 보인다.


커스터마이징 옵션

모양 변경

기본은 원형이고, 정사각형이나 둥근 정사각형으로 바꿀 수 있다.

// 원형 (기본값)
GroupAvatar(
  imageUrls: imageUrls,
  size: 56,
  shape: AvatarShape.circle,
)

// 정사각형
GroupAvatar(
  imageUrls: imageUrls,
  size: 56,
  shape: AvatarShape.square,
)

// 둥근 정사각형
GroupAvatar(
  imageUrls: imageUrls,
  size: 56,
  shape: AvatarShape.roundedSquare,
)

앱 디자인에 따라 원형이 어울리는 곳도 있고, 정사각형이 어울리는 곳도 있다. 슬랙 같은 앱은 둥근 정사각형을 쓴다.

스택 레이아웃 (겹침)

멤버가 여러 명일 때 이미지를 겹쳐서 보여줄 수 있다.

GroupAvatar(
  imageUrls: imageUrls,
  size: 56,
  layout: GroupAvatarLayout.stack,
  overlapRatio: 0.3,  // 겹침 정도 (0.0 ~ 1.0)
)

overlapRatio가 0이면 안 겹치고, 1에 가까울수록 많이 겹친다. 0.3 정도가 적당하다. 너무 많이 겹치면 얼굴이 안 보이고, 너무 적게 겹치면 공간을 많이 차지한다.

최대 표시 개수 제한

이미지가 많을 때 일부만 보여주고 나머지는 "+N"으로 표시할 수 있다.

GroupAvatar(
  imageUrls: imageUrls,  // 10개라고 가정
  size: 56,
  maxVisible: 4,  // 최대 4개만 표시
  showCounter: true,  // "+6" 배지 표시
)

그룹 채팅방에 멤버가 수십 명일 수도 있으니까, 적당히 잘라서 보여주는 게 좋다. 보통 3~4개 정도로 제한한다.

카운터 배지 커스터마이징

"+N" 배지의 스타일도 바꿀 수 있다.

GroupAvatar(
  imageUrls: imageUrls,
  size: 56,
  maxVisible: 4,
  showCounter: true,
  counterBackgroundColor: Colors.grey[800],
  counterStyle: TextStyle(
    color: Colors.white,
    fontSize: 12,
    fontWeight: FontWeight.bold,
  ),
)

앱 테마에 맞게 배경색이나 글자 스타일을 조절할 수 있다.

테두리 추가

각 아바타에 테두리를 줄 수 있다. 겹칠 때 경계가 잘 보이게 하려면 테두리를 주는 게 좋다.

GroupAvatar(
  imageUrls: imageUrls,
  size: 56,
  borderWidth: 2,
  borderColor: Colors.white,
)

배경이 어두우면 흰색 테두리, 배경이 밝으면 회색 테두리를 주면 된다.

플레이스홀더

이미지 로딩 중이거나 로드 실패했을 때 보여줄 위젯을 지정할 수 있다.

GroupAvatar(
  imageUrls: imageUrls,
  size: 56,
  placeholderIcon: Icon(Icons.person, color: Colors.grey[600]),
  backgroundColor: Colors.grey[300],
)

네트워크 상태가 안 좋거나 이미지 URL이 잘못됐을 때 빈 화면 대신 적절한 대체 UI를 보여줄 수 있다.

제스처 콜백

탭이나 롱프레스 이벤트를 받을 수 있다.

GroupAvatar(
  imageUrls: imageUrls,
  size: 56,
  onTap: () {
    // 그룹 아바타 탭 시 멤버 목록 모달 띄우기
    showMemberListModal(context);
  },
  onLongPress: () {
    // 롱프레스 시 채팅방 설정
    showChatRoomSettings(context);
  },
)

그룹 아바타를 눌렀을 때 참여자 목록을 보여주거나, 롱프레스했을 때 채팅방 설정을 여는 등의 인터랙션을 구현할 수 있다.


실제 사용 예시

채팅방 목록

채팅 앱에서 가장 흔하게 쓰는 케이스다.

class ChatRoomListTile extends StatelessWidget {
  final ChatRoom chatRoom;

  const ChatRoomListTile({required this.chatRoom});

  
  Widget build(BuildContext context) {
    return ListTile(
      leading: GroupAvatar(
        imageUrls: chatRoom.memberProfileUrls,
        size: 48,
        maxVisible: 4,
        showCounter: true,
        borderWidth: 1.5,
        borderColor: Colors.white,
        shape: AvatarShape.circle,
      ),
      title: Text(
        chatRoom.isGroupChat ? chatRoom.name : chatRoom.memberNames.first,
        maxLines: 1,
        overflow: TextOverflow.ellipsis,
      ),
      subtitle: Text(
        chatRoom.lastMessage,
        maxLines: 1,
        overflow: TextOverflow.ellipsis,
      ),
      trailing: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        crossAxisAlignment: CrossAxisAlignment.end,
        children: [
          Text(chatRoom.formattedTime),
          if (chatRoom.unreadCount > 0)
            Badge(label: Text('${chatRoom.unreadCount}')),
        ],
      ),
      onTap: () => context.push('/chat/${chatRoom.id}'),
    );
  }
}

채팅방 헤더

채팅방 안에서 상단 AppBar에 그룹 아바타를 보여줄 수도 있다.

AppBar(
  leading: BackButton(),
  title: Row(
    children: [
      GroupAvatar(
        imageUrls: chatRoom.memberProfileUrls,
        size: 36,
        maxVisible: 3,
      ),
      SizedBox(width: 12),
      Expanded(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(chatRoom.name, style: TextStyle(fontSize: 16)),
            Text('${chatRoom.memberCount}명', style: TextStyle(fontSize: 12)),
          ],
        ),
      ),
    ],
  ),
)

멤버 초대 화면

그룹 채팅에 멤버를 초대할 때 선택된 멤버들을 미리보기로 보여줄 수 있다.

Column(
  children: [
    GroupAvatar(
      imageUrls: selectedMembers.map((m) => m.profileUrl).toList(),
      size: 80,
      maxVisible: 5,
      showCounter: true,
    ),
    SizedBox(height: 8),
    Text('${selectedMembers.length}명 선택됨'),
  ],
)

왜 cached_network_image를 사용했나

네트워크 이미지를 로드할 때 캐싱이 중요하다.

채팅방 목록은 사용자가 앱을 켤 때마다 보는 화면이다. 매번 네트워크 요청을 하면 로딩이 느려지고, 데이터도 낭비된다. 특히 모바일에서는 네트워크 상태가 불안정할 수도 있어서 캐싱이 필수다.

cached_network_image는 메모리 캐시와 디스크 캐시를 모두 지원한다.

  • 메모리 캐시: 앱이 실행 중일 때 빠르게 이미지를 가져옴
  • 디스크 캐시: 앱을 재시작해도 이전에 로드한 이미지를 디스크에서 가져옴

이걸 직접 구현하려면 꽤 복잡한데, cached_network_image가 다 해결해준다. 그래서 이 패키지를 의존성으로 넣었다.


성능 고려사항

이미지 크기

size 파라미터로 표시 크기를 지정하는데, 이건 위젯 크기일 뿐이고 실제 이미지 해상도와는 다르다. 서버에서 적절한 크기의 썸네일을 내려주는 게 좋다.

예를 들어 48x48 크기로 표시할 건데 원본 이미지가 1000x1000이면 낭비다. 서버에서 100x100 정도의 썸네일 URL을 제공하는 게 좋다.

리스트에서 사용 시

ListViewGridView에서 사용할 때는 itemExtent를 지정하거나 ListView.builder를 사용하는 게 좋다. Flutter가 보이는 영역만 렌더링하도록 최적화해준다.

ListView.builder(
  itemCount: chatRooms.length,
  itemBuilder: (context, index) {
    return ChatRoomListTile(chatRoom: chatRooms[index]);
  },
)

지원 플랫폼

플랫폼지원
AndroidO
iOSO
WebO
macOSO
WindowsO
LinuxO

Flutter가 지원하는 모든 플랫폼에서 동작한다. 특별히 네이티브 코드를 쓰지 않아서 플랫폼 제약이 없다.


정리

직접 구현chat_group_avatar
레이아웃 자동 조정조건문으로 직접 구현내장
모양 변경ClipRRect 등으로 직접 구현shape 파라미터
스택/그리드Stack, GridView로 직접 구현layout 파라미터
겹침 정도Positioned로 계산overlapRatio 파라미터
캐시 이미지cached_network_image 별도 사용내장
카운터 배지별도 위젯으로 구현showCounter 파라미터
테두리BoxDecoration으로 직접 구현borderWidth, borderColor

Flutter에서 그룹 채팅 아바타 UI가 필요한데 마땅한 패키지가 없어서 직접 만들었다. RN이나 네이티브에는 이런 라이브러리가 있는데 Flutter에는 없어서 좀 아쉬웠다.

멤버 수에 따른 레이아웃 자동 조정, 모양 커스터마이징, 스택/그리드 레이아웃, 카운터 배지 등 채팅 앱에서 필요한 기능들을 넣었다.

비슷한 UI가 필요한 분들에게 도움이 됐으면 좋겠다.


링크

질문이나 버그 리포트는 GitHub Issue로 남겨주시면 됩니다.

1개의 댓글

comment-user-thumbnail
2026년 1월 28일

오 깔끔한 정리 덕분에 플러터 입문자가 이해하기 쉬웠습ㄴ디ㅏ!

답글 달기