트러블 등장

개인 프로젝트 풀밭을 개발하며 CampusView의 UI와 기능을 개발하였는데 문제가 생겼다. 자세한 개발 내용은 위의 링크를 누르면 볼 수 있다.

CampusView는 DynamicTabBar on Scroll으로 ScrollTabBar를 서로 연동하여 Tab을 눌렀을 때 자동으로 스크롤이 되거나 혹은 스크롤을 하였을때 Tab이 자동으로 변경되는 기능이 있다.

이 때 발생한 문제는 Tab을 눌러 스크롤이 자동으로 설정된 값을 스크롤 될 때 다른 Tab을 누르게 되면 scrollToIndex함수가 중복 실행이 되어 코드가 꼬이는 문제가 발생하였다. 한 번 어떤 상황인지 화면을 봐보자.

예시 화면

스크롤 이동이 끝난 후 Tab을 눌러 잘 작동 되는 모습

스크롤 이동이 끝난 후 Tab을 눌러 잘 작동 되는 모습

스크롤 이동이 끝나기 전 Tab을 눌러 오류가 있는 화면

스크롤 이동이 끝나기 전 Tab을 눌러 오류가 있는 화면

문제 해결 과정

1. 스크롤을 하는 도중에는 Tab을 못 누르게 하자.

TabBar 코드를 보면 onTap시 해당 Tab의 index를 scrollToIndex함수에 파라미터 값으로 넣고 실행이 된다.

그러면 scrollToIndex가 실행한다면 bool isRunning을 true로 하고 실행이 끝나면 false로 바꾸자.

scrollToIndex함수는 milliseconds: 600 duration이니 onTapDynamicTabBar 함수를 만들어 지연을 시키자!

// TabBar 코드
TabBar(
        tabs: [
          1번 텍스트 탭,
          2번 텍스트 탭,
          3번 텍스트 탭,
        ],
        // Tap시 실행되는 함수.
        onTap: (int index) => scrollToIndex(index),
      ),
 /// Animate To Tab
  void animateToTab() {
    late RenderBox box;

    for (var i = 0; i < campusCategories.length; i++) {
      box = campusCategories[i].currentContext!.findRenderObject() as RenderBox;
      Offset position = box.localToGlobal(Offset.zero);

      if (scrollController.offset >= position.dy) {
        DefaultTabController.of(tabContext!).animateTo(
          i,
          duration: const Duration(milliseconds: 100),
        );
      }
    }
  }

  /// Scroll to Index
  void scrollToIndex(int index) async {
    scrollController.removeListener(animateToTab);
    final categories = campusCategories[index].currentContext!;
    await Scrollable.ensureVisible(
      categories,
      duration: const Duration(milliseconds: 600),
    );
    scrollController.addListener(animateToTab);
  }

1.1 onTapDynamicTabBar 함수 생성

isRunning 변수값을 스크롤이 이동되는 시간만큼 지연시켜 변경해주어 에러를 방지한다. 입력 후 0.6초를 기다리고 0.6초동안 스크롤이 실행되다보니 화면 이동이 느려 0.1초로 변경하였다.

// TabBar 코드
TabBar(
        tabs: [
          1번 텍스트 탭,
          2번 텍스트 탭,
          3번 텍스트 탭,
        ],
        // Tap시 실행되는 함수.
        onTap: (int index) => onTapDynamicTabBar(index), // 함수 변경
      ),
  void onTapDynamicTabBar(int index) {
    setState(() {
      isRunning = true; // 실행 중 상태로 설정
    });

    // 비동기적으로 잠시 대기 후 scrollToIndex 호출
    Future.delayed(const Duration(milliseconds: 100), () { // 0.1초로 변경
      if (isRunning) {
        scrollToIndex(index); // index로 스크롤
        setState(() {
          isRunning = false; // 실행 종료 상태로 설정
        });
      }
    });
  }

1.2 **AbsorbPointer 클래스** 생성

isRunning이 true일때 화면 입력을 감지하지만 무시할 수 있는 **AbsorbPointer** 사용하여 실행중에 화면입력을 방지한다. 사실 지연을 시켜 입력을 무시할 필요는 없지만 혹시 모를 에러를 대비하여 AbsorbPointer를 사용하였다.