내가 현재 개발하고 있는 앱은 아래와 같은 디자인을 가지고 있기 때문에, iOS 에서 Scroll bounce가 나타날 때 위와 아래의 배경색이 달라야 했다.
Scroll bounce 란?
위나 아래 끝으로 스크롤했을 때 튕기는듯한 느낌을 주는 효과를 Scroll bounce 라고 한다.
이 부분을 해결하는데 시간이 생각보다 오래 걸려서, 어떤 방법을 시도했고 결국 어떻게 해결했는지를 기록하기 위해 글을 썼다.
physics: ClampingScrollPhysics()
를 적용해 스크롤 막아버리기 (채택 X)물리적으로 스크롤을 막아버려 배경 색깔을 확인할 수 없게 만드는 방법이다. 적용할 수는 있지만, 내가 느끼기에도 너무 동작이 어색하다고 생각해 해당 방법은 적용하지 않기로 했다.
찾아본 iOS 대부분의 앱이 Scroll bounce를 지원했고, 위에서 아래로 드래그했을 때 '새로고침' 등의 기능을 특색있게 사용자에게 제공했다.
특이하게도 요기요 앱은 홈 탭에만 Scroll bounce 를 막아두었는데, 왜 막아두었는지 의도는 잘 모르겠어서 물을 수 있다면 의도에 대해 묻고 싶었다.
가장 생각하기 쉬운 방법이었지만 실제로 적용해보고 해당 방법을 적용하는 것을 포기했다.
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
),
);
위와 같이 적용했을 때, 결국 스크롤이 중앙을 넘으면 다른 배경색이 나오게 되어 굉장히 어색한 모습을 보여준다. (아래 동영상 참고)
잘 동작한 코드 중 하나이다.
현재 스크롤의 위치(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를 이용한 스크롤 위치 측정, 배경색 변경 방법을 채택해 사용했다.
실제 코드
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,
위 코드가 내가 채택한 방법의 코드이다. 간단히 설명하자면,
flutter_hooks
패키지를 사용하고 있기 때문에 사전 정의된 useScrollController
훅을 이용해 ScrollController
를 생성해주었다.useEffect
훅 내에 handleScroll
함수를 만들어 리스너에 추가해주었다. 이제, 스크롤의 위치가 변경될 때마다 배경색이 변경될 것이다.SingleChildScrollView
에 controller를 붙여주었다. 이제, 해당 위젯의 스크롤 위치가 추적된다.2번 방법과 코드는 비슷하지만, 모든 종류의 스크롤 이벤트에 대해서 비교연산을 하지 않고 내가 원하는 위젯의 스크롤 이벤트만 추적할 수 있어 3번 방법을 택했다. 그리고 문제가 잘 해결되었다.
문제를 해결하는데 생각보다 시간이 많이 들었고 검색했을 때 적절한 해결 방법을 많이 찾아볼 수 없어 해당 글을 작성하게 되었다. (필요한 사람들에게 도움이 되었으면 하는 마음이다)
혹시 궁금하신 점이나 틀린 부분이 있다면 말씀해주시면 정말 감사하겠습니다!
https://api.flutter.dev/flutter/widgets/NotificationListener-class.html
https://api.flutter.dev/flutter/widgets/ScrollController-class.html