DotIndicator | CoUI

DotIndicator

페이지나 슬라이드 위치를 점으로 표시하는 인디케이터 컴포넌트

DotIndicator#

캐러셀, 온보딩 화면 등에서 현재 페이지 위치를 점으로 표시하는 인디케이터 컴포넌트입니다.

Live Preview#

Web
Flutter
Loading Flutter...
class DotIndicatorDefaultExample extends StatefulComponent {
  const DotIndicatorDefaultExample({super.key});

  @override
  State<DotIndicatorDefaultExample> createState() =>
      _DotIndicatorDefaultExampleState();
}

class _DotIndicatorDefaultExampleState
    extends State<DotIndicatorDefaultExample> {
  int _index = 2;

  @override
  Component build(BuildContext context) {
    return CoDotIndicator(
      index: _index,
      length: 5,
      onChanged: (v) => setState(() => _index = v),
    );
  }
}
class DotIndicatorDefaultExample extends StatefulWidget {
  const DotIndicatorDefaultExample({super.key});

  @override
  State<DotIndicatorDefaultExample> createState() =>
      _DotIndicatorDefaultExampleState();
}

class _DotIndicatorDefaultExampleState
    extends State<DotIndicatorDefaultExample> {
  int _index = 2;

  @override
  Widget build(BuildContext context) {
    return CoDotIndicator(
      index: _index,
      length: 5,
      onChanged: (v) => setState(() => _index = v),
    );
  }
}

사용 시기 (When to Use)#

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

  • 캐러셀이나 PageView에서 현재 페이지 위치를 표시할 때
  • 온보딩 화면에서 전체 단계 중 현재 위치를 안내할 때
  • 슬라이드나 갤러리에서 탐색 위치 표시와 함께 클릭으로 직접 이동을 제공할 때

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

  • Steps: 완료/진행 중/미완료 상태가 있는 단계별 진행 상황을 표시할 때
  • Progress: 진행률을 퍼센트로 표시할 때
  • Pagination: 페이지 번호 목록 탐색이 필요할 때

기본 사용법 (Basic Usage)#

// 기본 인디케이터
CoDotIndicator(
  count: 5,
  activeIndex: currentPage,
)

// 색상 지정
CoDotIndicator(
  count: 3,
  activeIndex: currentPage,
  activeColor: Colors.blue,
  inactiveColor: Colors.grey.shade300,
)

// 크기 지정
CoDotIndicator(
  count: 4,
  activeIndex: currentPage,
  size: 10,
  activeSize: 14,
)

// PageView와 연동
PageView(
  onPageChanged: (index) => setState(() => currentPage = index),
  children: pages,
),
CoDotIndicator(
  count: pages.length,
  activeIndex: currentPage,
)
// 기본 인디케이터 (index/length 파라미터 사용)
CoDotIndicator(
  index: currentPage,
  length: 5,
)

// 커스텀 색상
CoDotIndicator(
  index: currentPage,
  length: 3,
  activeColor: '#3b82f6',   // blue-500
  inactiveColor: '#d1d5db', // gray-300
)

// 크기 스타일 지정
CoDotIndicator(
  index: currentPage,
  length: 4,
  style: [DotIndicator.lg],
)

// 페이지 연동 (onChanged 콜백)
CoDotIndicator(
  index: currentPage,
  length: pages.length,
  onChanged: (newIndex) => handlePageChange(newIndex),
  style: [DotIndicator.md, DotIndicator.horizontal],
)

Props / Parameters#

속성타입기본값설명
countint필수총 페이지(점) 수
activeIndexint필수현재 활성 인덱스
activeColor Color? null 활성 점 색상 (기본값: 테마 primary)
inactiveColor Color? null 비활성 점 색상 (기본값: 테마 outline)
size double 8.0 비활성 점 크기 (px)
activeSize double? null 활성 점 크기 (기본값: size와 동일)
spacingdouble6.0점 간격 (px)
onDotTap void Function(int index)? null 점 클릭 시 페이지 이동 핸들러

변형 (Variants)#

기본형#

동일한 크기의 점으로 활성/비활성을 색상으로 구분합니다.

CoDotIndicator(count: 4, activeIndex: 0)

크기 강조형#

활성 점의 크기를 키워 위치를 강조합니다.

CoDotIndicator(
  count: 4,
  activeIndex: 0,
  size: 8,
  activeSize: 12,
)

동작 스펙 (Behavior)#

인터랙션#

  • 점 클릭: onDotTap이 있으면 해당 인덱스로 페이지 이동 콜백 호출
  • activeIndex 변경: 외부에서 상태 변경 시 활성 점 업데이트

상태 전환#

  • 활성 → 비활성: activeIndex 변경 시 즉시 또는 애니메이션으로 전환
  • 비활성 → 활성: 색상 또는 크기 변화

애니메이션#

  • 활성 점 전환: 색상/크기 변화 200ms ease-in-out
  • AnimatedContainer를 통한 부드러운 크기 변화 지원

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

✅ Do#

PageView와 함께 연동

Column(
  children: [
    Expanded(
      child: PageView(
        onPageChanged: (index) => setState(() => _currentPage = index),
        children: onboardingPages,
      ),
    ),
    Padding(
      padding: EdgeInsets.only(bottom: 24),
      child: CouiCoDotIndicator(
        count: onboardingPages.length,
        activeIndex: _currentPage,
        onDotTap: (index) => pageController.animateToPage(
          index,
          duration: Duration(milliseconds: 300),
          curve: Curves.easeInOut,
        ),
      ),
    ),
  ],
)

PageView와 연동하여 현재 페이지를 실시간으로 반영하고 점 클릭으로 직접 이동할 수 있게 합니다.


❌ Don't#

10개 이상의 점 사용 금지

// ❌ 너무 많은 점
CouiCoDotIndicator(
  count: 15, // 너무 많음
  activeIndex: currentPage,
)

점이 너무 많으면 현재 위치 파악이 어렵고 터치 타겟이 작아집니다. 5개 이하를 권장하며, 그 이상은 Pagination 사용을 고려하세요.

✅ Do#

크기 강조형으로 활성 상태를 명확하게 표시

CouiCoDotIndicator(
  count: 4,
  activeIndex: currentPage,
  size: 8,
  activeSize: 14,
  activeColor: Theme.of(context).colorScheme.primary,
  inactiveColor: Theme.of(context).colorScheme.outlineVariant,
)

크기와 색상을 모두 변경하면 색각 이상 사용자도 활성 상태를 인식할 수 있습니다.


❌ Don't#

onDotTap 없이 점만 표시 시 탭 영역을 작게 두지 않기

// ❌ 작은 점을 클릭 가능하게 만들 때 충분한 터치 영역 미확보
CouiCoDotIndicator(
  count: 4,
  activeIndex: currentPage,
  size: 4, // 너무 작음
  onDotTap: handleDotTap,
)

터치 타겟이 너무 작으면 모바일에서 클릭하기 어렵습니다. onDotTap이 있을 때는 size를 최소 8px 이상으로 설정하세요.

✅ Do#

현재 페이지를 명확히 강조하세요.

CouiCoDotIndicator(
  count: totalPages,
  currentIndex: currentPageIndex,
  activeDotSize: 12,
  inactiveDotSize: 8,
  activeColor: Theme.of(context).colorScheme.primary,
)

활성 도트를 크기와 색상으로 강조하면 사용자가 현재 위치를 한눈에 파악할 수 있습니다.


❌ Don't#

도트 개수가 너무 많으면 DotIndicator를 사용하지 마세요.

// ❌ 20개 페이지에 도트 인디케이터
CouiCoDotIndicator(
  count: 20,  // 너무 많은 도트
  currentIndex: currentIndex,
)

도트가 너무 많으면 UI가 지저분해지고 정보 전달이 어렵습니다. 5개 이상이면 숫자(1/10) 형태의 인디케이터를 고려하세요.

접근성 (Accessibility)#

키보드 인터랙션#

동작
/ (onDotTap 있을 때) 이전/다음 점으로 포커스 이동
Enter / Space포커스된 점 클릭
Tab다음 인터랙티브 요소로 이동

스크린 리더#

  • Flutter: Semantics로 현재 위치 전달 ('페이지 2 / 5')
  • Web: role="tablist" + 각 점에 role="tab", aria-selected 적용

터치 타겟#

  • onDotTap 있을 때 각 점의 최소 터치 영역: 44x44dp (패딩으로 보장)

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

항목FlutterWeb
클래스명CouiDotIndicatorDotIndicator
애니메이션AnimatedContainerCSS transition
클릭 이벤트GestureDetector.onTaponClick
  • Carousel: 내장 인디케이터가 있는 캐러셀 컴포넌트 (DotIndicator 통합)
  • Steps: 단계 완료 상태가 있는 진행 표시가 필요할 때
  • Progress: 퍼센트 기반 진행률 표시에 사용

조합 예제#

// DotIndicator + Carousel 외부 연동
Stack(
  alignment: Alignment.bottomCenter,
  children: [
    CouiCarousel(
      items: items,
      showIndicator: false,
      onChanged: (index) => setState(() => currentPage = index),
    ),
    Positioned(
      bottom: 16,
      child: CouiCoDotIndicator(
        count: items.length,
        activeIndex: currentPage,
        activeColor: Colors.white,
        inactiveColor: Colors.white.withOpacity(0.5),
      ),
    ),
  ],
)