RefreshTrigger#
스크롤 가능한 콘텐츠를 아래로 당겨서 새로고침을 트리거하는 컴포넌트입니다. iOS와 Android 스타일의 당겨서 새로고침 패턴을 구현합니다.
Live Preview#
Web
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)#
idle→pulling: 최상단에서 아래로 드래그 시작pulling:minExtent초과 시 라벨이releaseText로 전환pulling→refreshing: 드래그 해제 후onRefresh실행refreshing→completed→idle: 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)#
| 항목 | Flutter | Web |
|---|---|---|
| 클래스명 | RefreshTrigger | RefreshTrigger |
| stage 구동 | 스크롤 제스처로 자동 + stage 명시 시 controlled |
항상 controlled (stage 호스트 제어) |
| 제스처 감지 | ScrollNotification overscroll/pull |
native pull 제스처 없음 — 호스트가 스크롤 위치 감지 |
| 인디케이터 | surface 카드 + 스피너/체크마크 | surface 카드 + 텍스트/화살표 |
관련 컴포넌트 (Related Components)#
조합 예제#
// 피드 화면에 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]),
),
),
)