앱/Flutter

[Flutter] Sliver에서 Expanded처럼 나머지 공간 차지하게 하기 : SliverFillRemaining

paran21 2023. 8. 8. 08:57

최초작성일: 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...
그렇지만 ExpandedColumn, 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에서 막힐 때 문제해결하는데 훨씬 더 시간이 많이 드는 것 같다.
일단 위젯이 많이 중첩되어 있는 상황에서 어디가 문제의 원인인지 찾는데 시간이 많이 걸리고, 정작 "여기서 문제다!" 라고 열심히 수정을 했는데 원인은 다른데 있는 경우도 많았다.

위젯들이 어떤 속성을 기본적으로 가지고 있는지 살펴보면서 위젯을 사용하는게 중요한 것 같다.

문서를 다 뜯어보지는 못해도, 이렇게 문제 상황을 만났을 때 관련된 위젯들을 꼼꼼히 봐야겠다.