최초작성일: 2023년 1월 16일
0. Slivers 안에서 Expanded가 사용하고 싶었다.
앱에서 현재 사용하고 있는 공통 Layout widget을 사용해야 했고, 또CustomScrollView
를 사용하고 있기 때문에 반드시 Sliver를 사용해서 ui를 그려야 했다.
내가 그리고 싶었던 것은 데이터가 없는 경우에 화면 중앙에 다른 위젯을 그려주는 것이었다.
정리를 해보면,
- 데이터가 있는 경우 : 스크롤 위젯 =>
SliverList
- 데이터가 없는 경우 : 스크롤X 위젯 => ??
이런 상황이었다.
1. SliverToBoxAdapter를 사용하기
플러터에서 ui를 그릴 때 남은 공간을 모두 차지하게 하기
위해 Expanded
를 자주 사용하게 된다.
(구체적인 예제는 공식문서 : Expanded example 를 참고!)
그래서 처음에 의도했던 방법은, Slivers 안에 다른 위젯들을 사용할 수 있게 해주는 SliverToBoxAdapter
를 사용하고 그 안에 Column
을 사용하는 것이었다.
...
return CustomScrollView(
slivers : [
...
SliverToBoxAdapter(
child: Column(
children: [
// 화면 중앙에 놓고 싶은 위젯들
],
),
),
...
],
);
...
이렇게만 했을 때는 Column
을 화면 중앙에 위치시킬 수 없다.Column
안에 있는 위젯의 크기만큼만 범위를 차지 하기 때문이다.
그래서 이 위젯이 남은 공간을 모두 차지하게 하기
위해 Expanded
를 사용하려 했다.
Incorrect use of parent data widget...
그렇지만Expanded
는Column
,Row
,Flex
와 같은 위젯의 child로만 사용할 수 있고(StackOverFlow 참고),
"RenderFlex children have non-zero flex but incoming height constraints are unbounded...Expanded
의 parent에 높이를 제한해주는 위젯이 필요하다.
Slivers 안에서 구현을 해야하는 상황이 아니었다면 다른 방법을 사용할 수도 있었지만,
Slivers 안에는 Sliver 위젯들만 위치할 수 있기 때문에 Expanded
의 부모 위젯으로 사용할 수 있는 위젯의 종류에는 한계가 있었다.
2. SliverFillRemaining
이런 경우에 유용하게 사용할 수 있는 위젯으로 SliverFillRemaining
이 있다!
이름처럼 Sliver이면서, 남은 공간을 모두 차지하는 위젯이다.
이 위젯을 사용하면 SliverToBoxAdapter
가 child의 위젯만큼만 범위를 차지하는 것과 달리, 남은 공간을 모두 차지한다.
...
return CustomScrollView(
slivers : [
...
// 남은 공간을 전부 차지!
SliverFillRemaining(
child: Column(
children: [
// 화면 중앙에 놓고 싶은 위젯들
],
),
),
...
],
);
...
그래서 slivers에 다른 위젯이 없고 SliverFillRemaining
만 있다면 appbar를 제외하고 남은 공간 전부를 차지하게 된다.
그래서 내가 의도한대로 화면 중앙에 위젯이 위치하도록 만들 수 있었다.
3. SliverFillRemaining의 hasScrollBody
그런데 남은 공간만큼만 차지해서 스크롤이 되지 않을 거라고 생각했는데, 미묘하게 남은 공간보다 조금 더 범위를 차지해서 스크롤이 되었다.
const SliverFillRemaining({
super.key,
this.child,
this.hasScrollBody = true,
this.fillOverscroll = false,
}) : assert(hasScrollBody != null),
assert(fillOverscroll != null);
SliverFillRemaining
에는 hasScrollBody
속성이 있는데 기본값이 true여서 우선 false로 변경하였는데도 여전히 스크롤이 되는 상황이었다.
/// Setting this value to false will allow the child to fill the remainder of
/// the viewport and not extend further. However, if the
/// [SliverConstraints.precedingScrollExtent] and/or the [child]'s
/// extent exceeds the size of the viewport, the sliver will defer to the
/// child's size rather than overriding it.
final bool hasScrollBody;
설명을 참고하면 child의 size가 더 큰 경우에는 hasScrollBody
의 값과 상관없이 스크롤이 된다.
내 경우에는 SliverFillRemaining
의 parent로 SliverPadding
이 있어서 padding이 위 아래로 들어가 있었기 때문에 padding + SliverFillRemaining
의 범위가 남은 화면의 크기를 벗어나서 스크롤이 되는 상황이었다.
SliverFillRemaining
의 child가 화면 크기를 넘어간건 아니지만 이 위젯에서 hasScrollBody
를 false로 해도 parent에서 화면 크기를 넘어가기 때문에 이 속성과 상관없이 ScrollBody가 만들어졌다.
...
return CustomScrollView(
slivers : [
...
// padding + SliverFillRemaining의 범위는 남은 화면의 크기를 벗어남 => scrollable!
SliverPadding(
padding : const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
// 남은 공간을 전부 차지!
child: SliverFillRemaining(
child: Column(
children: [
// 화면 중앙에 놓고 싶은 위젯들
],
),
),
),
...
],
);
...
그래서 SliverFillRemaining
를 사용하는 경우에는 padding을 0으로 수정하였고, 의도한대로 스크롤이 없이 화면 중앙에 원하는 위젯을 위치시킬 수 있었다.
4. 느낀점
처음에 플러터를 시작할 때는 ui를 그리는데 크게 어려움을 느끼지 않았는데, 여러 요구상황에서 개발을 하면서 ui에서 막힐 때 문제해결하는데 훨씬 더 시간이 많이 드는 것 같다.
일단 위젯이 많이 중첩되어 있는 상황에서 어디가 문제의 원인인지 찾는데 시간이 많이 걸리고, 정작 "여기서 문제다!" 라고 열심히 수정을 했는데 원인은 다른데 있는 경우도 많았다.
위젯들이 어떤 속성을 기본적으로 가지고 있는지 살펴보면서 위젯을 사용하는게 중요한 것 같다.
문서를 다 뜯어보지는 못해도, 이렇게 문제 상황을 만났을 때 관련된 위젯들을 꼼꼼히 봐야겠다.