Flutter Overscroll 시에 위 아래 각각 다른 색깔로 배경색 지정하기

koeyhoyh·2024년 10월 22일
1

App_Flutter

목록 보기
22/22
post-thumbnail

내가 현재 개발하고 있는 앱은 아래와 같은 디자인을 가지고 있기 때문에, iOS 에서 Scroll bounce가 나타날 때 위와 아래의 배경색이 달라야 했다.

Scroll bounce 란?

위나 아래 끝으로 스크롤했을 때 튕기는듯한 느낌을 주는 효과를 Scroll bounce 라고 한다.

이 부분을 해결하는데 시간이 생각보다 오래 걸려서, 어떤 방법을 시도했고 결국 어떻게 해결했는지를 기록하기 위해 글을 썼다.


시도 방법

0. physics: ClampingScrollPhysics() 를 적용해 스크롤 막아버리기 (채택 X)

물리적으로 스크롤을 막아버려 배경 색깔을 확인할 수 없게 만드는 방법이다. 적용할 수는 있지만, 내가 느끼기에도 너무 동작이 어색하다고 생각해 해당 방법은 적용하지 않기로 했다.

찾아본 iOS 대부분의 앱이 Scroll bounce를 지원했고, 위에서 아래로 드래그했을 때 '새로고침' 등의 기능을 특색있게 사용자에게 제공했다.

특이하게도 요기요 앱은 홈 탭에만 Scroll bounce 를 막아두었는데, 왜 막아두었는지 의도는 잘 모르겠어서 물을 수 있다면 의도에 대해 묻고 싶었다.

1. 배경 색깔에 Gradient 넣기 (채택 X)

가장 생각하기 쉬운 방법이었지만 실제로 적용해보고 해당 방법을 적용하는 것을 포기했다.

해당 Stack overflow 링크

Scaffold(
    body: Container(
        decoration: BoxDecoration(
            gradient: LinearGradient(
                begin: Alignment.topCenter,
                end: Alignment.bottomCenter,
                colors: [
                    Colors.white,
                    Colors.red,
                ],
                stops: [0.49, 0.51],
            ),
        ),
        child: // Your code
    ),
);

위와 같이 적용했을 때, 결국 스크롤이 중앙을 넘으면 다른 배경색이 나오게 되어 굉장히 어색한 모습을 보여준다. (아래 동영상 참고)

2. NotificationListener 를 이용해 스크롤 위치 추적, 배경색 변경 (해결 가능한 솔루션이지만 채택 X)

잘 동작한 코드 중 하나이다.

현재 스크롤의 위치(notification.metrics.pixels)와 스크롤의 시작점(notification.metrics.minScrollExtent), 스크롤 할 수 있는 마지막 지점(notification.metrics.maxScrollExtenrt)을 비교해 배경 색을 변경해주었다.

유의할 점

NotificationListener는 하위 위젯의 이벤트를 전부 가져오기 때문에 아래 코드에서는 조건에 depth를 추가해 내가 원하는 깊이의 위젯 스크롤만 받아 처리하게 해두었다.

실제 코드

body: NotificationListener<ScrollNotification>(
          onNotification: (ScrollNotification notification) {
            if (notification is ScrollUpdateNotification) {
              if (notification.metrics.pixels <=
                      notification.metrics.minScrollExtent &&
                  notification.depth == 0) {
                ref.read(_backgroundColorProvider.notifier).state =
                    AppColors.cyanShade600;
              } else if (notification.metrics.pixels >=
                      notification.metrics.maxScrollExtent &&
                  notification.depth == 0) {
                ref.read(_backgroundColorProvider.notifier).state =
                    AppColors.white;
              }
            }

            return true;
          },

위와 같이 NotificationListener를 사용하면, 여러 형태의 notification을 받을 수 있다.

notification을 찍어보면 아래와 같은 결과물이 나오는데 대부분 이름을 보고 이해하기 쉬운 형태라서 구현할 때 어떤 notification을 사용할지 판단하기 편했다.

  • ScrollStartNotification: 스크롤 시작
  • UserScrollNotification: 사용자가 스크롤하는 방향, 스크롤 멈춘 정보 출력
  • ScrollUpdateNotification: 스크롤 위치가 변경될 때마다 출력
  • ScrollEndNotification: 스크롤 끝
// print 를 이용해 notification을 찍어보았을 때, 여러 형태의 notification이 출력된다.
flutter: ScrollStartNotification(depth: 0 (local), FixedScrollMetrics(229.0..[730.0]..0.0), DragStartDetails(Offset(189.3, 464.3)))
flutter: UserScrollNotification(depth: 0 (local), FixedScrollMetrics(229.0..[730.0]..0.0), direction: ScrollDirection.forward)
flutter: ScrollUpdateNotification(depth: 0 (local), FixedScrollMetrics(227.8..[730.0]..1.2), scrollDelta: -1.1666666666666572, DragUpdateDetails(Offset(0.0, 6.0)))
...
flutter: ScrollEndNotification(depth: 0 (local), FixedScrollMetrics(0.0..[730.0]..229.0))
flutter: UserScrollNotification(depth: 0 (local), FixedScrollMetrics(0.0..[730.0]..229.0), direction: ScrollDirection.idle)

다만, 하위 위젯의 모든 스크롤 이벤트를 가져온다는 점이 내가 개발하려는 기능에 비해 너무 크다고 느꼈다. 위 방법으로도 문제를 해결할 수 있었지만, 나는 원하는 위젯의 스크롤 이벤트만 가져와 배경색만 변경해주면 되었기 때문에, 3. ScrollController를 이용한 스크롤 위치 측정, 배경색 변경 방법을 채택해 사용했다.

3. ScrollController를 이용한 스크롤 위치 추적, 배경색 변경 (채택 O)

실제 코드

	final backgroundColor = useState<Color>(AppColors.white);
	
	final scrollController = useScrollController();

    useEffect(() {
      void handleScroll() {
        if (scrollController.position.pixels <=
            scrollController.position.minScrollExtent) {
          backgroundColor.value = AppColors.cyanShade600;
        } else if (scrollController.position.pixels >=
            scrollController.position.maxScrollExtent) {
          backgroundColor.value = AppColors.white;
        }
      }

      scrollController.addListener(handleScroll);

      return () => scrollController.removeListener(handleScroll);
    }, [scrollController]);
    
    ...
    
    SingleChildScrollView(
            controller: scrollController,

위 코드가 내가 채택한 방법의 코드이다. 간단히 설명하자면,

  1. flutter_hooks 패키지를 사용하고 있기 때문에 사전 정의된 useScrollController훅을 이용해 ScrollController를 생성해주었다.
  2. useEffect 훅 내에 handleScroll 함수를 만들어 리스너에 추가해주었다. 이제, 스크롤의 위치가 변경될 때마다 배경색이 변경될 것이다.
  3. 내가 스크롤 위치의 추적을 원하는 위젯인 SingleChildScrollView에 controller를 붙여주었다. 이제, 해당 위젯의 스크롤 위치가 추적된다.

2번 방법과 코드는 비슷하지만, 모든 종류의 스크롤 이벤트에 대해서 비교연산을 하지 않고 내가 원하는 위젯의 스크롤 이벤트만 추적할 수 있어 3번 방법을 택했다. 그리고 문제가 잘 해결되었다.


문제를 해결하는데 생각보다 시간이 많이 들었고 검색했을 때 적절한 해결 방법을 많이 찾아볼 수 없어 해당 글을 작성하게 되었다. (필요한 사람들에게 도움이 되었으면 하는 마음이다)

혹시 궁금하신 점이나 틀린 부분이 있다면 말씀해주시면 정말 감사하겠습니다!


참고

https://api.flutter.dev/flutter/widgets/NotificationListener-class.html
https://api.flutter.dev/flutter/widgets/ScrollController-class.html

profile
내가 만들어낸 것들로 세계에 많은 가치를 창출해내고 싶어요.

0개의 댓글