Flutter로 UI를 만들면서 많은 시행착오를 겪었던 동적인 높이를 가진 SliverAppBar를 구현하는 방법을 정리했다.
UI 설명
| 기본 화면 | 스크롤 됐을 때 |
![]() |
![]() |
구현하고자 하는 UI는 기본적으로 Header, TabBar, TabBarView가 모두 보이다가 아래로 스크롤 하면 Header가 사라지고, 다시 위로 스크롤 하면 Header가 나타나는 구조다.
기본적인 방법
Widget build(BuildContext context) {
return NestedScrollView(
headerSliverBuilder: (_, __) => [
SliverAppBar(title: _Header()),
_TabBar(), // SliverPersistentHeader를 이용하여 pinned를 true로 세팅해야 함
],
body: _TabBarView(),
);
}
NestedScrollView를 이용하면 원하는 UI를 어렵지 않게 구현할 수 있다.
Header의 높이가 동적이라면?
Header를 감싸는 SliverAppBar는 toolbarHeight 속성으로 높이를 설정할 수 있는데 Header의 높이가 동적이라면 Header의 높이에 맞게 toobarHeight 속성을 지속적으로 변경해줘야 한다.
class MeasuredSize extends StatefulWidget {
const MeasuredSize({
Key? key,
required this.onChanged,
required this.child,
}) : super(key: key);
final ValueChanged<Size> onChanged;
final Widget child;
@override
State<MeasuredSize> createState() => _MeasuredSizeState();
}
class _MeasuredSizeState extends State<MeasuredSize> {
final widgetKey = GlobalKey();
Size? oldSize;
@override
Widget build(BuildContext context) {
SchedulerBinding.instance?.addPostFrameCallback(_postFrameCallback);
return Container(
key: widgetKey,
child: widget.child,
);
}
void _postFrameCallback(_) {
var context = widgetKey.currentContext;
if (context == null) return;
final newSize = context.size;
if (newSize == null || oldSize == newSize) return;
oldSize = newSize;
widget.onChanged(newSize);
}
}
자식 위젯의 크기를 감지해 변경될 때마다 onChanged(Size size)를 호출하는 MeasuredSize 위젯을 정의했다.
class SomethingWidget extends StatefulWidget {
const SomethingWidget({Key? key}) : super(key: key);
@override
_SomethingWidgetState createState() => _SomethingWidgetState();
}
class _SomethingWidgetState extends State<SomethingWidget> {
double toolbarHeight = 0.0;
@override
Widget build(BuildContext context) {
return NestedScrollView(
headerSliverBuilder: (_, __) => [
SliverAppBar(
title: MeasuredSize(
onChanged: (size) => setState(() => toolbarHeight = size.height),
child: _Header(),
),
toolbarHeight: toolbarHeight,
),
_TabBar(),
],
body: _TabBarView(),
);
}
}
앞서 정의한 MeasuredSize 위젯과 StatefulWidget의 setState()를 이용해서 동적인 Header의 높이를 SliverAppBar의 toobarHeight에 전달할 수 있다.

