Sliver 위젯이란 무엇이며 어디에 사용하나요?
질문
Flutter의 Sliver 위젯이 무엇인지 설명해주세요. 일반 스크롤 위젯과 비교하여 Sliver의 장점과 주요 Sliver 위젯들의 사용 방법에 대해 알려주세요.
답변
Flutter에서 Sliver 위젯은 스크롤 가능한 영역을 효율적으로 관리하고 고급 스크롤 효과를 구현하기 위한 저수준 위젯 그룹입니다. 'Sliver'란 용어는 '작은 조각'을 의미하며, 큰 스크롤 가능 영역의 일부분을 나타냅니다.
1. Sliver의 개념과 장점
1.1 메모리 및 성능 최적화
Sliver 위젯은 화면에 보이는 요소만 렌더링하여 메모리를 효율적으로 사용합니다:
// 일반 ListView - 미리 많은 항목을 준비
ListView.builder(
itemCount: 1000,
itemBuilder: (context, index) => ListTile(title: Text('항목 $index')),
)
// SliverList - 화면에 보이는 항목만 렌더링
CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('항목 $index')),
childCount: 1000,
),
),
],
)
1.2 다양한 스크롤 효과
Sliver는 다음과 같은 고급 스크롤 효과를 구현할 수 있습니다:
- 접히는 앱바 (Collapsing App Bar)
- 고정 헤더 (Sticky Headers)
- 가변 크기 그리드 (Variable-sized Grids)
- 복합 스크롤 뷰 (여러 유형의 스크롤 위젯 결합)
1.3 복합 스크롤 뷰
여러 유형의 스크롤 가능한 위젯을 하나의 스크롤 뷰로 결합할 수 있습니다:
CustomScrollView(
slivers: [
SliverAppBar(...), // 접히는 앱바
SliverList(...), // 목록
SliverGrid(...), // 그리드
],
)
2. 주요 Sliver 위젯
2.1 SliverAppBar
스크롤에 따라 크기가 변하는 앱바를 구현합니다:
SliverAppBar(
expandedHeight: 200.0,
floating: true, // 스크롤 업 시 바로 나타남
pinned: true, // 스크롤 다운 시에도 상단에 고정됨
flexibleSpace: FlexibleSpaceBar(
title: Text('접히는 앱바'),
background: Image.asset('assets/image.jpg', fit: BoxFit.cover),
),
)
2.2 SliverList
스크롤 가능한 목록을 구현합니다:
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ListTile(
title: Text('항목 $index'),
subtitle: Text('부제목'),
);
},
childCount: 50,
),
)
2.3 SliverGrid
그리드 레이아웃을 구현합니다:
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 1.0,
),
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return Container(
color: Colors.primaries[index % Colors.primaries.length],
child: Center(child: Text('$index')),
);
},
childCount: 30,
),
)
2.4 SliverPadding
다른 Sliver 위젯에 패딩을 추가합니다:
SliverPadding(
padding: EdgeInsets.all(16.0),
sliver: SliverList(...),
)
2.5 SliverToBoxAdapter
일반 위젯을 Sliver 컨텍스트 내에서 사용할 수 있게 합니다:
SliverToBoxAdapter(
child: Container(
height: 100.0,
color: Colors.amber,
child: Center(child: Text('일반 위젯')),
),
)
2.6 SliverPersistentHeader
스크롤 시 크기가 변하고 선택적으로 고정되는 헤더:
SliverPersistentHeader(
pinned: true, // 스크롤 시 상단에 고정
delegate: _SliverHeaderDelegate(
minHeight: 60.0,
maxHeight: 120.0,
child: Container(
color: Colors.lightBlue,
child: Center(child: Text('섹션 헤더')),
),
),
)
// 필요한 delegate 구현
class _SliverHeaderDelegate extends SliverPersistentHeaderDelegate {
_SliverHeaderDelegate({
required this.minHeight,
required this.maxHeight,
required this.child,
});
final double minHeight;
final double maxHeight;
final Widget child;
@override
double get minExtent => minHeight;
@override
double get maxExtent => maxHeight;
@override
Widget build(
BuildContext context,
double shrinkOffset,
bool overlapsContent
) {
return SizedBox.expand(child: child);
}
@override
bool shouldRebuild(_SliverHeaderDelegate oldDelegate) {
return maxHeight != oldDelegate.maxHeight ||
minHeight != oldDelegate.minHeight ||
child != oldDelegate.child;
}
}
2.7 SliverFillRemaining
남은 스크롤 공간을 채우는 위젯:
SliverFillRemaining(
hasScrollBody: false,
child: Container(
color: Colors.green[200],
child: Center(child: Text('남은 공간')),
),
)
3. 실용적인 예제
3.1 고급 스크롤 UI 구현
CustomScrollView(
slivers: <Widget>[
// 1. 접히는 앱바
SliverAppBar(
expandedHeight: 200.0,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: Text('고급 UI'),
background: Image.network('https://picsum.photos/800/400'),
),
),
// 2. 소개 섹션
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(16.0),
child: Text('Sliver 위젯 예제입니다'),
),
),
// 3. 고정 헤더
SliverPersistentHeader(
pinned: true,
delegate: _SliverHeaderDelegate(
minHeight: 50.0,
maxHeight: 70.0,
child: Container(
color: Colors.blue,
child: Center(child: Text('인기 항목')),
),
),
),
// 4. 그리드 섹션
SliverGrid(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
),
delegate: SliverChildBuilderDelegate(
(context, index) {
return Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.star),
Text('항목 $index'),
],
),
);
},
childCount: 4,
),
),
// 5. 또 다른 헤더
SliverPersistentHeader(
delegate: _SliverHeaderDelegate(
minHeight: 50.0,
maxHeight: 50.0,
child: Container(
color: Colors.orange,
child: Center(child: Text('목록 항목')),
),
),
),
// 6. 목록 섹션
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return ListTile(
leading: CircleAvatar(child: Text('${index+1}')),
title: Text('목록 항목 ${index+1}'),
subtitle: Text('부제목'),
);
},
childCount: 10,
),
),
],
),
3.2 SliverAppBar와 TabBar 조합
DefaultTabController(
length: 3,
child: Scaffold(
body: CustomScrollView(
slivers: [
SliverAppBar(
expandedHeight: 200.0,
pinned: true,
flexibleSpace: FlexibleSpaceBar(
title: Text('Sliver와 TabBar'),
background: Image.network('https://picsum.photos/800/400'),
),
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.photo), text: '사진'),
Tab(icon: Icon(Icons.video_library), text: '비디오'),
Tab(icon: Icon(Icons.music_note), text: '음악'),
],
),
),
SliverFillRemaining(
child: TabBarView(
children: [
// 사진 탭
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
),
itemBuilder: (context, index) {
return Image.network(
'https://picsum.photos/200?random=$index',
fit: BoxFit.cover,
);
},
itemCount: 30,
),
// 비디오 탭
ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.video_file),
title: Text('비디오 ${index+1}'),
);
},
),
// 음악 탭
ListView.builder(
itemCount: 15,
itemBuilder: (context, index) {
return ListTile(
leading: Icon(Icons.music_note),
title: Text('음악 ${index+1}'),
);
},
),
],
),
),
],
),
),
)
4. SliverList vs ListView 비교
// 1. 일반 ListView - 독립적인 스크롤 뷰
ListView.builder(
itemCount: 100,
itemBuilder: (context, index) => ListTile(title: Text('항목 $index')),
)
// 2. SliverList - CustomScrollView 내에서 사용
CustomScrollView(
slivers: [
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(title: Text('항목 $index')),
childCount: 100,
),
),
],
)
주요 차이점:
용도: ListView는 독립적인 스크롤 뷰로 사용되지만, SliverList는 CustomScrollView 내에서 다른 Sliver 위젯과 함께 사용됩니다.
구성: ListView는 내부적으로 Sliver를 사용하여 구현됩니다.
유연성: SliverList는 다른 Sliver 위젯과 결합하여 복잡한 스크롤 효과를 만들 수 있습니다.
사용 방식: ListView는 바로 사용할 수 있지만, SliverList는 반드시 CustomScrollView 내에서 사용해야 합니다.
5. Sliver 사용 시 고려사항
복잡성: Sliver 위젯은 일반 스크롤 위젯보다 설정이 복잡합니다.
적합한 상황:
- 여러 유형의 스크롤 영역이 필요할 때
- 접히는 앱바가 필요할 때
- 스크롤에 따라 변하는 헤더가 필요할 때
성능 고려: 매우 긴 목록이나 복잡한 UI에서는 Sliver 위젯이 성능 이점을 제공합니다.
결론
Sliver 위젯은 Flutter에서 고급 스크롤 효과와 성능 최적화를 위한 강력한 도구입니다. 일반 스크롤 위젯보다 더 많은 제어와 유연성을 제공하며, 복잡한 스크롤 인터페이스를 구현하는 데 이상적입니다.
단순한 스크롤 UI에는 ListView나 GridView 같은 기본 위젯을 사용하는 것이 더 간단하지만, 여러 유형의 스크롤 영역을 결합하거나 고급 스크롤 효과가 필요한 경우 Sliver 위젯을 사용하는 것이 좋습니다.