RefreshTrigger | CoUI

RefreshTrigger

스크롤을 당겨서 콘텐츠를 새로고침하는 Pull-to-Refresh 컴포넌트

RefreshTrigger#

스크롤 가능한 콘텐츠를 아래로 당겨서 새로고침을 트리거하는 컴포넌트입니다. iOS와 Android 스타일의 당겨서 새로고침 패턴을 구현합니다.

Live Preview#

Web
Release to refresh
Inbox Drafts Sent Archive
Flutter
Loading Flutter...
class RefreshTriggerDefaultExample extends StatelessComponent {
  const RefreshTriggerDefaultExample({super.key});

  @override
  Component build(BuildContext context) {
    return RefreshTrigger(
      stage: CoreRefreshStage.pulling,
      child: div(
        const [
          Text('Inbox'),
          Text('Drafts'),
          Text('Sent'),
          Text('Archive'),
        ],
        classes: 'flex flex-col gap-${CoreSpace.scale.space8}',
      ),
    );
  }
}
class RefreshTriggerDefaultExample extends StatelessWidget {
  const RefreshTriggerDefaultExample({super.key});

  @override
  Widget build(BuildContext context) {
    return RefreshTrigger(
      onRefresh: () async {
        await Future<void>.delayed(AnimationConstants.xl);
      },
      child: ListView(
        children: const [
          Text('Inbox'),
          Text('Drafts'),
          Text('Sent'),
          Text('Archive'),
        ],
      ),
    );
  }
}

사용 시기 (When to Use)#

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

  • 소셜 피드, 뉴스 목록처럼 최신 데이터를 수동으로 불러와야 할 때
  • 모바일 앱에서 네이티브 pull-to-refresh 패턴을 구현할 때
  • 스크롤 가능한 영역의 내용을 사용자가 직접 갱신할 수 있어야 할 때

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

  • Loading: 자동 새로고침 또는 페이지 로딩 중 표시에
  • Progress: 작업 진행 상태를 퍼센트로 표시할 때

기본 사용법 (Basic Usage)#

Flutter와 Web 모두 동일한 RefreshTrigger API를 사용합니다.

// 기본 당겨서 새로고침 — Flutter는 스크롤 제스처로 stage가 자동 구동됩니다.
RefreshTrigger(
  onRefresh: () async {
    await fetchLatestData();
  },
  child: ListView(
    children: const [
      Text('Inbox'),
      Text('Drafts'),
      Text('Sent'),
    ],
  ),
)

// 당김 거리 / 인디케이터 스타일 커스터마이징
RefreshTrigger(
  onRefresh: () async => fetchLatestData(),
  minExtent: 60,
  maxExtent: 200,
  refreshTriggerStyle: const CoreRefreshTriggerStyle(
    refreshingTextColor: CoreColor.token(CoreColors.primary),
  ),
  child: ListView(children: items),
)
// Web은 native pull 제스처가 없어 stage를 호스트가 제어합니다.
RefreshTrigger(
  stage: CoreRefreshStage.pulling,
  child: div(
    const [
      Text('Inbox'),
      Text('Drafts'),
      Text('Sent'),
    ],
    classes: 'flex flex-col gap-${CoreSpace.scale.space8}',
  ),
)

// 인디케이터 스타일 커스터마이징
RefreshTrigger(
  stage: _stage,
  refreshTriggerStyle: const CoreRefreshTriggerStyle(
    refreshingTextColor: CoreColor.token(CoreColors.primary),
  ),
  child: contentList,
)

Props / Parameters#

속성타입기본값설명
child Widget / Component 필수 새로고침 대상 스크롤 가능한 콘텐츠
stage CoreRefreshStage idle 인디케이터 lifecycle 단계 (controlled)
direction CoreRefreshDirection vertical 당김 제스처 축
reverse bool false 반대쪽 가장자리(하단/우측)에서 시작
pullText String 'Pull to refresh' idle 단계 라벨
releaseText String 'Release to refresh' 임계값 초과 pulling 단계 라벨
refreshingText String 'Refreshing...' refreshing 단계 라벨
completedText String 'Refresh complete' completed 단계 라벨
minExtent double 75 새로고침 트리거 최소 당김 거리(px, Flutter 제스처)
maxExtent double 150 최대 당김 거리(px, Flutter 제스처)
onRefresh Future<void> Function()? null 새로고침 트리거 시 호출되는 비동기 핸들러
refreshTriggerStyle CoreRefreshTriggerStyle? null 인디케이터 chrome / 치수 / 애니메이션 오버라이드

변형 (Variants)#

기본 (Default)#

디자인 시스템 기본 인디케이터를 사용합니다.

RefreshTrigger(
  onRefresh: () async => fetchLatestData(),
  child: ListView(children: items),
)

커스텀 인디케이터 스타일#

refreshTriggerStyle로 인디케이터 배경 / 텍스트 색상을 변경합니다.

RefreshTrigger(
  onRefresh: () async => fetchLatestData(),
  refreshTriggerStyle: const CoreRefreshTriggerStyle(
    refreshingTextColor: CoreColor.token(CoreColors.primary),
  ),
  child: ListView(children: items),
)

넓은 당김 거리#

인디케이터가 더 많이 당겨져야 새로고침이 트리거됩니다.

RefreshTrigger(
  onRefresh: () async => fetchLatestData(),
  minExtent: 120,
  child: ListView(children: items),
)

동작 스펙 (Behavior)#

인터랙션#

  • 당기기: 스크롤 최상단에서 아래로 당기면 인디케이터 표시
  • 충분히 당기기: minExtent 이상 당기면 새로고침 트리거 표시
  • 해제: 당기기를 놓으면 onRefresh() 호출, 완료까지 인디케이터 표시
  • 중단: minExtent 미만에서 놓으면 복귀

상태 전환 (CoreRefreshStage)#

  • idlepulling: 최상단에서 아래로 드래그 시작
  • pulling: minExtent 초과 시 라벨이 releaseText로 전환
  • pullingrefreshing: 드래그 해제 후 onRefresh 실행
  • refreshingcompletedidle: Future 완료 후 완료 표시를 거쳐 복귀

애니메이션#

  • 인디케이터 등장: 드래그 거리에 비례한 회전 애니메이션
  • 새로고침 중: 무한 회전 스피너
  • 복귀: 스프링 효과 300ms

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

✅ Do#

onRefresh를 async/await로 올바르게 구현

Future<void> handleRefresh() async {
  final newData = await fetchLatestData();
  setState(() {
    _items = newData;
  });
  // Future가 완료되면 인디케이터 자동 숨김
}

RefreshTrigger(
  onRefresh: handleRefresh,
  child: itemList,
)

onRefresh가 올바르게 완료되어야 인디케이터가 자동으로 숨겨진다.


❌ Don't#

스크롤할 수 없는 위젯에 RefreshTrigger 사용

// ❌ 스크롤 불가능한 위젯에 사용
RefreshTrigger(
  onRefresh: handleRefresh,
  child: Container(
    child: Text('스크롤 없는 콘텐츠'),
  ),
)

스크롤 가능한 위젯이 없으면 pull 제스처가 작동하지 않는다.

✅ Do#

브랜드 색상으로 인디케이터 색상 지정

RefreshTrigger(
  onRefresh: handleRefresh,
  refreshTriggerStyle: const CoreRefreshTriggerStyle(
    refreshingTextColor: CoreColor.token(CoreColors.primary),
  ),
  child: feedList,
)

브랜드 색상의 인디케이터는 앱의 일관된 시각 언어를 유지한다.


❌ Don't#

새로고침 중에도 UI가 완전히 차단되지 않도록 주의

// ❌ 새로고침 중 전체 화면을 가리는 로딩 오버레이
RefreshTrigger(
  onRefresh: () async {
    showFullScreenLoader(); // 중복 로딩 표시
    await fetchData();
    hideFullScreenLoader();
  },
  child: feedList,
)

RefreshTrigger 자체가 로딩 인디케이터를 표시하므로 별도 전체 화면 로딩은 중복이다.

✅ Do#

새로고침 완료 후 명확한 피드백을 제공하세요.

RefreshTrigger(
  onRefresh: () async {
    await dataRepository.refresh();
    // 새로고침 완료 후 스낵바 또는 업데이트 시간 표시
    showRefreshCompleted();
  },
  child: ContentList(),
)

새로고침이 완료되면 사용자가 데이터가 업데이트되었음을 인식할 수 있도록 명확한 피드백을 제공하세요.


❌ Don't#

새로고침 중 추가 인터랙션을 허용하지 마세요.

// ❌ 새로고침 중 버튼 클릭 가능
RefreshTrigger(
  onRefresh: handleRefresh,
  child: Column(
    children: [
      ContentList(),
      Button(
        onPressed: handleLoadMore,  // 새로고침 중에도 활성화됨
        child: Text('더 보기'),
      ),
    ],
  ),
)

새로고침 중에는 다른 데이터 요청을 막아야 데이터 충돌이나 중복 요청을 방지할 수 있습니다.

접근성 (Accessibility)#

키보드 인터랙션#

동작
해당 없음제스처 기반 컴포넌트; 키보드 대안은 새로고침 버튼으로 제공 권장

스크린 리더#

  • Flutter: 새로고침 상태 변경 시 SemanticsService.announce("새로고침 완료") 호출
  • Web: aria-live="polite" 영역에 새로고침 상태 메시지 업데이트

터치 타겟#

  • 제스처 기반 컴포넌트로 별도 터치 타겟 없음
  • 접근성을 위해 화면 상단에 새로고침 버튼을 추가로 제공 권장

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

항목FlutterWeb
클래스명RefreshTriggerRefreshTrigger
stage 구동 스크롤 제스처로 자동 + stage 명시 시 controlled 항상 controlled (stage 호스트 제어)
제스처 감지 ScrollNotification overscroll/pull native pull 제스처 없음 — 호스트가 스크롤 위치 감지
인디케이터surface 카드 + 스피너/체크마크surface 카드 + 텍스트/화살표
  • Loading: 페이지 또는 섹션 로딩 중 스피너/스켈레톤 표시
  • Progress: 데이터 로딩 진행률을 퍼센트로 표시

조합 예제#

// 피드 화면에 RefreshTrigger 적용
Scaffold(
  appBar: AppBar(
    title: const Text('피드'),
    actions: [
      // 키보드 접근성을 위한 새로고침 버튼
      IconButton(
        onPressed: handleManualRefresh,
        icon: const Icon(Icons.refresh),
        tooltip: '새로고침',
      ),
    ],
  ),
  body: RefreshTrigger(
    onRefresh: handleRefresh,
    child: ListView.builder(
      itemCount: feedItems.length,
      itemBuilder: (context, index) => FeedCard(item: feedItems[index]),
    ),
  ),
)