반응형

인스타그램과 같은 UI를 만들기 위해서 ListView.builder를 사용하여 상하 스크롤을 기본적으로 배치하고 각 페이지를 PageView.builder로  좌우 스크롤이 가능하게 구현을 했다.

실제로는 Firebase의 데이터를 불러서 동적으로 구현하지만 예제 소스는 텍스트 값을 리스트에 넣어서 정리했다.

class _HomePageState extends State<HomePage> {
  final ScrollController _scrollController = ScrollController();
  final PageController _pageController = PageController(initialPage: 1);

  List verticalList = ["1", "2"];
  List horizontalList = ["1", "2", "3"];

  @override
  Widget build(BuildContext context) {
    var width = MediaQuery.of(context).size.width;

    return Scaffold(
      appBar: AppBar(
        title: const Text('Photo Manager'),
      ),
      body: SafeArea(
        child: ListView.builder(
          controller: _scrollController,
          scrollDirection: Axis.vertical,
          shrinkWrap: true,
          itemCount: verticalList.length,
          itemBuilder: (context, index) =>
              SizedBox(
                  width: width,
                  height: width * 1.35,
                  child: PageView.builder(
                    controller: _pageController,
                    itemCount: horizontalList.length,
                    itemBuilder: (context, index) =>
                        Text(horizontalList[index])
                ),
              ))
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        child: const Icon(Icons.add),
      ),
    );
  }
}

해당 소스를 실행해보면 화면 상하 2개의 위젯과 각 위젯 안에 3개씩의 위젯으로 구성된 결과가 보여지게 된다.

대충 이런 느낌....

상하좌우 스크롤을 해보면 문제없이 잘 동작한다. 화면상으로만 콘솔을 보지 않는다면 말이다.

콘솔을 확인해보면 아래와 같은 메시지를 볼수가 있다.

위의 메세지는 좌우 스크롤을 끝까지 한상태에서 한번더 스크롤을 해보면 발생한다.

'package:flutter/src/widgets/overscroll_indicator.dart': Failed assertion: line 243 pos 14: 'notification.metrics.axis == widget.axis': is not true.

overscroll_indicator.dart 에서 발생한다고 하니 스크롤이 범위를 벗어났다고 하는거 같긴한데 지금까지 해본결과 좌우던 상하던 오버스크롤이 되도 문제가 없었는데 느닷없는 오류에 많이 당황했다.

 

해당 메시지로 구글링을 해봐도 문제를 해결하기 위한 답변을 찾지 못하고 NotificationListener라는 힌트만 얻게 되었다.

그리고 NotificationListener에 대해서 찾아보기 시작한 결과

위와 같은 중첩구조에서  scroll event가 발생할 경우 상위의 위젯(위에선 ListView.builder)에서는 단일 위젯과 같기에 문제 없으나 내부에 있는 위젯(위에선 PageView.builder)에 발생한 event를 상위 위젯에서도 잡는다는 것이다.

page_view.dart나 list_view.dart 소스를 열어보면 안에 NotificationLIstener를 return 하는것을 찾아볼수 있다.

 

그럼 상위위젯에서 listening 하지 못하게 하면 해결되는거 아닌가? 란 생각과 함께 찾아본결과

해당 이벤트를 발생시키는 위젯을 NotificationLIstener위젯으로 감싸서 직접 컨트롤 할 수 있다는걸 알게 되었다.

class _HomePageState extends State<HomePage> {
  final ScrollController _scrollController = ScrollController();
  final PageController _pageController = PageController(initialPage: 1);

  List verticalList = ["1", "2"];
  List horizontalList = ["1", "2", "3"];

  @override
  Widget build(BuildContext context) {
    var width = MediaQuery.of(context).size.width;

    return Scaffold(
      appBar: AppBar(
        title: const Text('Photo Manager'),
      ),
      body: SafeArea(
        child: ListView.builder(
          controller: _scrollController,
          scrollDirection: Axis.vertical,
          shrinkWrap: true,
          itemCount: verticalList.length,
          itemBuilder: (context, index) =>
              SizedBox(
                  width: width,
                  height: width * 1.35,
                  child: NotificationListener<ScrollNotification> (
                    onNotification: (notification) {
                      return true;
                    },
                    child: PageView.builder(
                        controller: _pageController,
                        itemCount: horizontalList.length,
                        itemBuilder: (context, index) =>
                            Text(horizontalList[index])
                    ),
                  ))
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        child: const Icon(Icons.add),
      ),
    );
  }
}

위처럼 변경하고 실행해보면 더이상 에러가 발생하지 않는다.

NotificationListener로 감싸서  onNotification을 무조건 true로 던졌다. true를 쓴 이유는 notification_listener.dart에 있는 설명에 나와있다.

Return true to cancel the notification bubbling. Return false to allow the notification to continue to be dispatched to further ancestors.

번역해보면

"알림 버블링을 취소하려면 true를 반환합니다. 알림이 추가 조상에게 계속 전달되도록 하려면 false를 반환합니다." 라고 한다.

지금은 상위 위젯에 연계해서 이벤트 처리할 일이 없기에 무조건 true로 처리하지만 내부 위젯과 연계해서 상위 위젯에 어떠한 이벤트를 주고자 한다면 

onNotification: (notification) {
.
.
}

onNotification에 인자로 받은  notification은 실제로 scroll_notification.dart 객체이기 때문에 소스를 열어보면 얻을 수 있는 정보들이 존재함을 확인할 수 있다.

NotificationListener와 관련된 부분은 아직 이해가 부족하고 추측성 결론이 많아 후에 조금 더 공부해 보기로 하고 마무리 한다.

728x90
반응형

+ Recent posts