Switcher#
여러 자식 뷰 중 하나를 한 번에 보여주고, 활성 인덱스가 바뀌면 슬라이드 / 페이드 전환을 애니메이션하는 컴포넌트입니다. 사용자는 드래그하여 인접한 뷰 사이를 스크럽할 수 있습니다.
Switcher는 레거시 Flutter Switcher와 Web Switcher를 통일한
컴포넌트입니다. Flutter와 Web이 동일한 파라미터 이름·동작·시각을 갖습니다 —
네 방향 슬라이드, 드래그 제스처, 동일한 전환 시간.
Live Preview#
class SwitcherDefaultExample extends StatefulComponent {
const SwitcherDefaultExample({super.key});
@override
State<SwitcherDefaultExample> createState() =>
_SwitcherDefaultExampleState();
}
class _SwitcherDefaultExampleState extends State<SwitcherDefaultExample> {
int _index = 0;
static const List<String> _labels = ['View A', 'View B', 'View C'];
@override
Component build(BuildContext context) {
return div(
[
div(
[
Switcher(
index: _index,
onIndexChanged: (next) => setState(() => _index = next),
items: [
for (final label in _labels)
div(
[Text(label).titleMedium],
classes: 'flex items-center justify-center h-full',
),
],
),
],
styles: const Styles(
raw: {'width': '16rem', 'height': '6rem'},
),
),
Gap.space16(),
div(
[
for (var idx = 0; idx < _labels.length; idx++)
Button(
variant: idx == _index
? CoreButtonVariant.primary
: CoreButtonVariant.outline,
onPressed: () => setState(() => _index = idx),
child: Text(_labels[idx]),
),
],
classes: 'flex flex-row flex-wrap justify-center gap-${CoreSpace.scale.space8}',
),
],
classes: 'flex flex-col items-center',
);
}
}
class SwitcherDefaultExample extends StatefulWidget {
const SwitcherDefaultExample({super.key});
@override
State<SwitcherDefaultExample> createState() =>
_SwitcherDefaultExampleState();
}
class _SwitcherDefaultExampleState extends State<SwitcherDefaultExample> {
int _index = 0;
static const List<String> _labels = ['View A', 'View B', 'View C'];
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: CoreSize.size256,
height: CoreSize.size96,
child: Switcher(
index: _index,
onIndexChanged: (next) => setState(() => _index = next),
items: [
for (final label in _labels)
Center(child: Text(label).titleMedium),
],
),
),
const Gap.space16(),
Wrap(
alignment: WrapAlignment.center,
children: [
for (var idx = 0; idx < _labels.length; idx++)
Padding(
padding: const EdgeInsets.symmetric(
horizontal: CoreSpace.space4,
),
child: Button(
variant: idx == _index
? CoreButtonVariant.primary
: CoreButtonVariant.outline,
onPressed: () => setState(() => _index = idx),
child: Text(_labels[idx]),
),
),
],
),
],
);
}
}
사용 시기 (When to Use)#
이 컴포넌트를 사용하세요:
- 온보딩 단계처럼 순차적 뷰 사이를 슬라이드 전환할 때
- 목록/그리드 뷰 모드 전환에 부드러운 애니메이션이 필요할 때
- 드래그로 인접 뷰를 스크럽하는 인터랙션이 필요할 때
대신 다른 컴포넌트를 사용하세요:
Tabs: 라벨 기반의 콘텐츠 섹션 전환에Carousel: 자동 재생 / 다수 아이템 슬라이드쇼에Swiper: 카드 스택 형태의 스와이프 인터랙션에
기본 사용법 (Basic Usage)#
Switcher(
index: _index,
direction: CoreSwitcherDirection.left,
onIndexChanged: (next) => setState(() => _index = next),
items: const [
Text('Page 1'),
Text('Page 2'),
Text('Page 3'),
],
)
Switcher(
index: _index,
direction: CoreSwitcherDirection.left,
onIndexChanged: (next) => setState(() => _index = next),
items: [
div([text('Page 1')]),
div([text('Page 2')]),
div([text('Page 3')]),
],
)
Props / Parameters#
Switcher#
| 속성 | 타입 | 기본값 | 설명 |
|---|---|---|---|
items | List<W> | 필수 | 전환 대상 자식 뷰 목록 |
index | int | 0 | 현재 보이는 자식 인덱스 |
direction |
CoreSwitcherDirection |
left |
새 뷰가 슬라이드해 들어오는 방향 |
gestureEnabled |
bool |
true |
드래그 / 스와이프 내비게이션 활성화 여부 |
onIndexChanged |
void Function(int)? |
null |
드래그가 다른 뷰에 안착했을 때 새 인덱스로 호출 |
switcherStyle |
CoreSwitcherStyle? |
null |
인스턴스별 전환 스타일 |
CoreSwitcherDirection은 up / down / left / right
네 방향을
지원합니다.
스타일 시스템 (Style System)#
Switcher의 전환 chrome은 단일 CoreSwitcherStyle 슬롯으로 흐릅니다
(Epic #1415 option D).
시맨틱 vs 스타일#
-
시맨틱 / behaviour: 위젯 파라미터로 직접 (
index,direction,gestureEnabled,items) - chrome / dimensional:
switcherStyle: CoreSwitcherStyle?한 곳으로
Resolve chain#
CoreSwitcherStyle.defaultDuration // 디자인 시스템 기본값 (300ms)
→ CoreSwitcherTheme.style // 프로젝트 공통
→ widget.switcherStyle // 인스턴스별
CoreSwitcherStyle 필드#
| 필드 | 타입 | 적용 영역 |
|---|---|---|
duration | Duration? | 슬라이드 / 페이드 전환 시간 |
Switcher(
switcherStyle: const CoreSwitcherStyle(
duration: Duration(milliseconds: 200),
),
items: const [Text('A'), Text('B')],
)
변형 (Variants)#
슬라이드 방향#
direction으로 새 뷰가 들어오는 방향을 지정합니다.
Switcher(direction: CoreSwitcherDirection.up, items: [...])
Switcher(direction: CoreSwitcherDirection.right, items: [...])
드래그 비활성화#
gestureEnabled: false로 드래그를 막고 프로그래매틱 전환만 허용합니다.
Switcher(gestureEnabled: false, items: [...])
동작 스펙 (Behavior)#
인터랙션#
- 프로그래매틱 전환:
index변경 시 새 뷰가direction방향으로 슬라이드 - 드래그:
gestureEnabled가true면 마우스 / 터치 드래그로 인접 뷰 스크럽 - 스냅: 드래그 해제 시 가장 가까운 뷰로 스냅하고
onIndexChanged호출
애니메이션#
- 슬라이드 + 페이드 전환: 기본 300ms (
CoreDuration.ms300),ease-in-out - 드래그 중에는 전환이 비활성화되어 포인터를 1:1로 따라감
사용 가이드라인 (Usage Guidelines)#
✅ Do#
onIndexChanged로 드래그 결과를 상위 상태에 반영
Switcher(
index: _index,
onIndexChanged: (next) => setState(() => _index = next),
items: const [Text('A'), Text('B')],
)
드래그가 안착한 인덱스를 상태에 반영해야 다음 빌드에서 일관된 뷰가 보인다.
❌ Don't#
라벨 기반 콘텐츠 섹션 전환에 Switcher 사용
// ❌ 라벨 탭 UI 가 필요한 경우
Switcher(
items: const [Text('Account'), Text('Password')],
)
라벨 탭이 필요하면 Tabs가 적합하다. Switcher 는 슬라이드 전환이 핵심이다.
접근성 (Accessibility)#
- 비활성 뷰는
aria-hidden="true"(Web) 로 표시 - 루트 컨테이너는
role="group"(Web)
크로스 플랫폼 차이점 (Platform Differences)#
Switcher는 Flutter와 Web이 동일한 items / index / direction
/
gestureEnabled / onIndexChanged / switcherStyle
API와 동작을 갖습니다.
Flutter는 커스텀 RenderObject로 자식 크기를 보간하고, Web은 CSS transform
트랙 슬라이드로 동일한 시각 결과를 냅니다. 전환 곡선은 양쪽 모두
ease-in-out입니다.