Swiper | CoUI

Swiper

스와이프 제스처로 아이템을 해제하거나 전환하는 컴포넌트

Swiper#

스와이프 제스처로 아이템을 옆으로 밀어내거나 다음/이전 아이템으로 전환하는 컴포넌트입니다. 알림 해제, 카드 덱 전환 등에 활용됩니다.

Live Preview#

Web
Swipe to reveal actions
Flutter
Loading Flutter...
Swiper(
  position: SwiperPosition.bottom,
  child: text('Swipe content'),
)
// Swiper requires gesture context
Swiper(
  position: SwiperPosition.bottom,
  child: Text('Swipe content'),
)

사용 시기 (When to Use)#

이 컴포넌트를 사용하세요:

  • 알림이나 목록 항목을 스와이프하여 해제할 때
  • 카드 스택에서 스와이프로 카드를 건너뛸 때
  • 모바일 환경에서 자연스러운 스와이프 제스처 인터랙션이 필요할 때

대신 다른 컴포넌트를 사용하세요:

  • Carousel: 스와이프로 콘텐츠를 탐색하는 슬라이드쇼에
  • Drawer: 화면 가장자리에서 슬라이드하여 열리는 사이드 패널에

기본 사용법 (Basic Usage)#

// 수평 스와이프로 해제
Swiper(
  onDismiss: handleDismissed,
  child: NotificationCard(
    title: '새 메시지',
    body: '홍길동님이 메시지를 보냈습니다.',
  ),
)

// 방향 및 임계값 설정
Swiper(
  direction: SwipeDirection.horizontal,
  threshold: 0.4,
  onSwipe: handleSwiped,
  onDismiss: handleDismissed,
  child: Card(
    child: ListTile(title: Text('스와이프 가능한 아이템')),
  ),
)

// 세로 스와이프
Swiper(
  direction: SwipeDirection.vertical,
  onDismiss: handleDismissed,
  child: ImageCard(imageUrl: 'https://example.com/image.png'),
)
// 수평 스와이프 오버레이 — 좌측에서 열리는 드로어 방식
Swiper(
  position: SwiperPosition.left,
  handler: SwiperHandler.drawer,
  open: isMenuOpen,
  overlayContent: nav([
    text('메뉴 항목 1'),
    text('메뉴 항목 2'),
  ]),
  child: mainContent,
)

// 세로 스와이프 — 아래에서 올라오는 시트 방식
Swiper(
  position: SwiperPosition.bottom,
  handler: SwiperHandler.sheet,
  open: isSheetOpen,
  showDragHandle: true,
  barrierDismissible: true,
  overlayContent: div([
    text('바텀 시트 콘텐츠'),
  ]),
  child: pageContent,
)

// 방향 제어 — 우측 오버레이, 드래그 핸들 없음
Swiper(
  position: SwiperPosition.right,
  handler: SwiperHandler.drawer,
  open: isDetailOpen,
  showDragHandle: false,
  overlayContent: DetailPanel(),
  child: listContent,
)

Props / Parameters#

속성타입기본값설명
childWidget필수스와이프 대상 위젯
onSwipe void Function(SwipeDirection)? null 스와이프 발생 시 콜백
onDismiss void Function()? null 임계값 초과 후 해제 시 콜백
direction SwipeDirection horizontal 스와이프 허용 방향
threshold double 0.3 해제 트리거 임계값 (0.0 ~ 1.0)

변형 (Variants)#

수평 스와이프 (Horizontal)#

좌우 방향으로 스와이프합니다. 알림 해제, 카드 스택에 적합합니다.

Swiper(
  direction: SwipeDirection.horizontal,
  onDismiss: handleDismissed,
  child: NotificationTile(title: '알림'),
)

수직 스와이프 (Vertical)#

상하 방향으로 스와이프합니다. 피드 건너뛰기 등에 적합합니다.

Swiper(
  direction: SwipeDirection.vertical,
  onDismiss: handleDismissed,
  child: FeedCard(content: '피드 콘텐츠'),
)

모든 방향 (Any)#

모든 방향으로 스와이프를 허용합니다.

Swiper(
  direction: SwipeDirection.any,
  onSwipe: handleSwiped,
  onDismiss: handleDismissed,
  child: PhotoCard(imageUrl: 'https://example.com/photo.png'),
)

동작 스펙 (Behavior)#

인터랙션#

  • 드래그 시작: 터치/마우스 드래그 시 위젯이 따라 이동
  • 임계값 미만 해제: 드래그를 놓으면 원래 위치로 스프링 복귀
  • 임계값 초과 해제: 방향으로 밀려나가며 onDismiss 호출
  • 스와이프 방향 감지: onSwipe(direction) 으로 방향 전달

상태 전환#

  • idledragging: 드래그 시작
  • draggingdismissed: 임계값 초과 후 해제
  • draggingidle: 임계값 미만에서 드래그 해제

애니메이션#

  • 복귀 애니메이션: 스프링 효과 300ms
  • 해제 애니메이션: 방향으로 슬라이드 아웃 + 페이드 200ms

사용 가이드라인 (Usage Guidelines)#

✅ Do#

스와이프 가능함을 시각적 힌트로 안내

CouiSwiper(
  onDismiss: handleDismissed,
  child: Stack(
    children: [
      NotificationCard(title: '새 알림'),
      Positioned(
        right: 8,
        top: 0,
        bottom: 0,
        child: Center(child: Icon(Icons.swipe, color: Colors.grey)),
      ),
    ],
  ),
)

스와이프 가능함을 알지 못하는 사용자를 위해 아이콘이나 애니메이션 힌트를 제공한다.


❌ Don't#

threshold를 너무 낮게 설정하지 않기

// ❌ 가볍게 건드리기만 해도 해제됨
CouiSwiper(
  threshold: 0.05,
  onDismiss: handleDismissed,
  child: ImportantCard(),
)

임계값이 너무 낮으면 의도치 않은 해제가 발생해 사용자 실수를 유발한다.

✅ Do#

해제 취소(undo) 기능을 함께 제공

CouiSwiper(
  onDismiss: () {
    handleNotificationDismissed();
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: const Text('알림이 삭제되었습니다.'),
        action: SnackBarAction(
          label: '취소',
          onPressed: handleUndoDismiss,
        ),
      ),
    );
  },
  child: NotificationTile(notification: notification),
)

실수로 해제한 경우 복구할 수 없으면 사용자 불만이 발생한다. Toast + undo 패턴으로 실수를 만회할 기회를 제공한다.


❌ Don't#

중요한 콘텐츠에 실수 방지 없이 Swiper 사용

// ❌ 확인 없이 바로 삭제
CouiSwiper(
  onDismiss: handlePermanentDelete, // 복구 불가
  child: ImportantDocument(),
)

복구 불가능한 동작은 확인 Dialog나 undo 기능 없이는 스와이프로만 처리하면 안 된다.

✅ Do#

슬라이드 전환 방향을 일관되게 유지하세요.

CouiSwiper(
  direction: Axis.horizontal,  // 일관된 수평 스와이프
  items: pageItems,
  onPageChanged: handlePageChanged,
)

슬라이드 방향이 일관되면 사용자가 스와이프 제스처를 자연스럽게 예측하고 사용할 수 있습니다.


❌ Don't#

자동 재생 중 수동 스와이프 충돌을 방치하지 마세요.

// ❌ 자동 재생 중 수동 스와이프 시 경쟁 조건 발생
CouiSwiper(
  items: items,
  autoPlay: true,
  interval: Duration(seconds: 2),  // 수동 조작 후에도 타이머 계속
  // pauseOnDrag: false (기본)
)

자동 재생 중 사용자가 수동으로 스와이프하면 타이머와 충돌할 수 있습니다. pauseOnDrag: true로 수동 조작 시 자동 재생을 일시 정지하세요.

접근성 (Accessibility)#

키보드 인터랙션#

동작
Delete / Backspace포커스된 항목 해제
Tab다음 포커스 가능 요소로 이동

스크린 리더#

  • Flutter: Semantics(customSemanticsActions: ...) 으로 "스와이프하여 삭제" 액션 추가
  • Web: 별도 버튼(삭제 버튼)을 함께 제공하여 키보드/스크린 리더 사용자도 해제 가능하게

터치 타겟#

  • 스와이프 영역은 전체 위젯 크기
  • 최소 높이: 48dp

크로스 플랫폼 차이점 (Platform Differences)#

항목FlutterWeb
클래스명CouiSwiperSwiper
제스처 감지 GestureDetector + DragUpdateDetails Pointer Events API
애니메이션 AnimationController + spring CSS transition + JS 애니메이션
터치 지원네이티브 터치 지원touchstart/touchmove 이벤트
  • Carousel: 스와이프로 슬라이드를 순환 탐색할 때 사용
  • Drawer: 화면 가장자리에서 슬라이드하여 열리는 패널

조합 예제#

// 알림 목록에 스와이프 해제 적용
ListView.builder(
  itemCount: notifications.length,
  itemBuilder: (context, index) {
    final notification = notifications[index];
    return CouiSwiper(
      key: ValueKey(notification.id),
      onDismiss: () => handleNotificationDismissed(notification.id),
      child: NotificationTile(notification: notification),
    );
  },
)