![[Flutter 심화] 7. Sliver (5) - CustomScrollView](https://image.inblog.dev?url=https%3A%2F%2Finblog.ai%2Fapi%2Fog%3Ftitle%3D%255BFlutter%2520%25EC%258B%25AC%25ED%2599%2594%255D%25207.%2520Sliver%2520%285%29%2520-%2520CustomScrollView%2520%26logoUrl%3Dhttps%253A%252F%252Finblog.ai%252Finblog_logo.png%26blogTitle%3Djay0628&w=2048&q=75)
커스텀스크롤뷰 = 외부 스크롤
탭바뷰 내부의 ListView = 내부 스크롤
1층 스크롤과 2층 스크롤이 있다고 이해
모든 스크롤은 자신의 스크롤을 가짐
내부 스크롤과 외부 스크롤이 따로 반응 = CustomScrollView의 문제
CustomScrollView 내부의 SliverList는 스크롤이 없음 → CustomScrollView에만 스크롤 있음
⇒ SliverList로 해결X
TabBar는 좀 특이함
탭바 내부에 스크롤 존재
처음에는 내부 스크롤을 잡아도 1층인 외부 스크롤이 잡혀야 됨
1층이 다 사라지면 2층이 잡힘
올라갈때는 2층부터 잡히고 2층 다 내려오면 1층이 잡혀야 됨
내가 잡은 스크롤의 커서가 바뀌어야 됨
원래는 스크롤이벤트로 내가 직접 처리 해야됨
위로 스크롤할 때 if 1층이 minExtent가 아니면 (어딜 잡든)
minExtent가 되면 스크롤이 2층으로 변경되어야 함
if 반대 방향이면, if maxExtent가 아니면 다시 1층으로
⇒ NestedScrollView는 이걸 다 알아서 해줌 1층 = header 2층 = body(SliverFillRemaining으로 구현되어있음)
scroll의 listener는 onTap에 두면 안되고 build 후에 되도록 근데 rebuild 되면 리셋되니까 stateful로 해서 init에 넣어두면 됨
1층에는 Sliver 타입만 넣고
2층은 일반 위젯을 넣음( → SliverFillRemaining은 삭제하자)
2층의 특징 : body에 넣으면 높이가 자동으로 잡힘
NestedScrollView
header~ = 1층 → Sliver 타입들만
body = 2층 → 탭바뷰 (자동으로 SliverFillRemaining 같은 기능이 잡혀있음)
스크롤 이벤트의 제어권 뺏어가는 거임
고치기 전
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
// CustomScrollView 내부에 TabBarView를 직접 넣어 중첩 스크롤 버그를 발생시키는 디자인입니다.
// CustomScrollView와 TabBarView가 각각 스크롤을 처리하려 하기 때문에 충돌이 발생합니다.
body: CustomScrollView(
slivers: [
SliverAppBar(
title: const Text("중첩 스크롤 버그"),
expandedHeight: 200,
pinned: true,
flexibleSpace: const FlexibleSpaceBar(
background: Placeholder(), // 앱바 배경
),
// CustomScrollView의 헤더 부분에 TabBar를 추가합니다.
bottom: TabBar(
controller: _tabController,
tabs: const [
Tab(text: '탭 1'),
Tab(text: '탭 2'),
Tab(text: '탭 3'),
],
),
),
// SliverToBoxAdapter를 사용하여 TabBarView를 CustomScrollView 안에 넣습니다.
// 이 부분이 스크롤 충돌의 원인이 됩니다.
SliverFillRemaining(
// SliverFillRemain이 더 나음
child: TabBarView(
controller: _tabController,
children: [
// TabBarView 내부의 리스트입니다.
// 이 리스트는 스크롤이 되어야 하지만, 상위 CustomScrollView 때문에 제대로 동작하지 않습니다.
ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text('탭 1의 항목 $index'),
);
},
),
ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text('탭 2의 항목 $index'),
);
},
),
ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text('탭 3의 항목 $index'),
);
},
),
],
),
),
],
),
);
}
}
nestedScrollView 적용 후
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin {
late TabController _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
}
@override
void dispose() {
_tabController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
// CustomScrollView 내부에 TabBarView를 직접 넣어 중첩 스크롤 버그를 발생시키는 디자인입니다.
// CustomScrollView와 TabBarView가 각각 스크롤을 처리하려 하기 때문에 충돌이 발생합니다.
body: NestedScrollView(
headerSliverBuilder: (context, innerBoxIsScrolled) {
return [
SliverAppBar(
title: const Text("중첩 스크롤 버그"),
expandedHeight: 200,
pinned: true,
flexibleSpace: const FlexibleSpaceBar(
background: Placeholder(), // 앱바 배경
),
// CustomScrollView의 헤더 부분에 TabBar를 추가합니다.
bottom: TabBar(
controller: _tabController,
tabs: const [
Tab(text: '탭 1'),
Tab(text: '탭 2'),
Tab(text: '탭 3'),
],
),
),
];
},
body: TabBarView(
controller: _tabController,
children: [
// TabBarView 내부의 리스트입니다.
// 이 리스트는 스크롤이 되어야 하지만, 상위 CustomScrollView 때문에 제대로 동작하지 않습니다.
ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text('탭 1의 항목 $index'),
);
},
),
ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text('탭 2의 항목 $index'),
);
},
),
ListView.builder(
itemCount: 50,
itemBuilder: (context, index) {
return ListTile(
title: Text('탭 3의 항목 $index'),
);
},
),
],
),
),
);
}
}
Share article