Sunday, August 10, 2025

쉬운 바텀바 스크롤 연동Flutter 스크롤에 따라 사라지는 BottomNavigationBar, 완벽 구현 A to Z

최신 모바일 앱의 사용자 경험(UX) 트렌드 중 하나는 단연 '콘텐츠 중심 디자인'입니다. 사용자가 화면의 콘텐츠에 최대한 집중할 수 있도록 불필요한 UI 요소를 동적으로 숨기는 기법은 이제 선택이 아닌 필수가 되었습니다. 특히 인스타그램, 페이스북, 최신 웹 브라우저 등에서 흔히 볼 수 있는, 아래로 스크롤할 때는 하단 탭 바(BottomNavigationBar)가 사라지고, 위로 스크롤할 때 다시 나타나는 기능은 화면 공간을 극대화하여 사용자에게 쾌적한 경험을 선사합니다.

Flutter로 앱을 개발하면서 이러한 동적 UI를 어떻게 구현할 수 있을지 고민해보셨을 겁니다. 단순히 '보였다/안 보였다'의 이분법적인 접근을 넘어, 부드러운 애니메이션과 함께 사용자의 스크롤 의도를 정확히 파악하여 반응하는 완성도 높은 BottomNavigationBar를 만드는 것이 중요합니다. 이 글에서는 Flutter의 ScrollController, NotificationListener, 그리고 AnimationController를 조합하여, 어떤 복잡한 스크롤 뷰에서도 완벽하게 작동하는 '스크롤 연동 하단 바'를 구현하는 모든 과정을 A부터 Z까지 상세하게 다룰 것입니다. 단순히 코드를 복사 붙여넣기 하는 것을 넘어, 그 원리를 이해하고 다양한 예외 상황에 대처하는 방법까지 마스터하게 될 것입니다.

1. 기본 원리 이해: 어떻게 동작하는가?

구현에 앞서, 우리가 만들 기능의 핵심 원리를 이해하는 것이 중요합니다. 목표는 간단합니다. 사용자의 스크롤 방향을 감지하고, 그 방향에 따라 BottomNavigationBar의 위치를 화면 밖으로 밀어내거나 다시 안으로 가져오는 것입니다.

  1. 스크롤 방향 감지: 사용자가 손가락으로 화면을 위로 미는지(콘텐츠를 아래로 내리는 중), 아래로 당기는지(콘텐츠를 위로 올리는 중)를 알아내야 합니다.
  2. UI 위치 변경: 감지된 방향에 따라 BottomNavigationBar를 Y축으로 이동시킵니다. 아래로 스크롤할 때는 바의 높이만큼 아래로 내려 화면 밖으로 숨기고, 위로 스크롤할 때는 다시 원래 위치(Y=0)로 복귀시킵니다.
  3. 부드러운 전환 효과: 위치가 순간적으로 바뀌면 사용자는 부자연스러움을 느낍니다. 따라서 애니메이션을 적용하여 바가 부드럽게 슬라이드되도록 만들어야 합니다.

이 세 가지 원리를 구현하기 위해 Flutter는 다음과 같은 강력한 도구들을 제공합니다.

  • ScrollController 또는 NotificationListener: 스크롤 가능한 위젯(ListView, GridView, CustomScrollView 등)의 스크롤 이벤트를 감지하는 역할을 합니다. 특히 ScrollController는 스크롤 위치를 직접 제어할 수 있고, NotificationListener는 위젯 트리 상위에서 자식 스크롤 위젯의 다양한 알림(Notification)을 받을 수 있습니다. 우리는 이 둘을 모두 살펴보고, 더 유연한 방식인 NotificationListener를 중심으로 구현할 것입니다.
  • userScrollDirection: ScrollPosition 객체에 포함된 속성으로, 사용자의 현재 스크롤 방향을 ScrollDirection.forward(위로 스크롤), ScrollDirection.reverse(아래로 스croll), ScrollDirection.idle(정지) 세 가지 상태로 알려줍니다.
  • AnimationControllerTransform.translate: AnimationController는 애니메이션의 진행 상태(0.0 ~ 1.0)를 특정 시간 동안 관리해주는 컨트롤러입니다. 이 값을 이용하여 Transform.translate 위젯의 offset을 조절하면, 어떤 위젯이든 원하는 축으로 부드럽게 이동시킬 수 있습니다.

이제 이 도구들을 사용하여 실제 코드를 작성해 보겠습니다.

2. 단계별 구현: 스크롤 감지부터 애니메이션까지

가장 기본적인 형태부터 시작하여 점차 기능을 고도화하는 방식으로 진행하겠습니다. 먼저, 스크롤 가능한 화면과 BottomNavigationBar를 갖춘 기본 앱 구조를 만듭니다.

2.1. 프로젝트 기본 구조 설정

먼저, 상태를 관리해야 하므로 StatefulWidget으로 기본 페이지를 구성합니다. 이 페이지는 긴 목록을 가진 ListViewBottomNavigationBar를 포함합니다.


import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Scroll Aware Bottom Bar',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _selectedIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Scroll Aware Bottom Bar'),
      ),
      body: ListView.builder(
        itemCount: 100, // 스크롤이 가능하도록 아이템 개수를 충분히 줍니다.
        itemBuilder: (context, index) {
          return ListTile(
            title: Text('Item $index'),
          );
        },
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: const <BottomNavigationBarItem>[
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: 'Search',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'Profile',
          ),
        ],
        currentIndex: _selectedIndex,
        onTap: (index) {
          setState(() {
            _selectedIndex = index;
          });
        },
      ),
    );
  }
}

위 코드는 아직 아무런 기능이 없는, 평범한 Flutter 앱의 모습입니다. 이제 여기에 스크롤 감지 로직을 추가해 보겠습니다.

2.2. 스크롤 감지하기: NotificationListener 활용

ScrollControllerListView에 직접 연결하고 리스너를 추가하는 방법도 있지만, NotificationListener를 사용하면 위젯 트리를 더 깔끔하게 유지할 수 있습니다. ListViewNotificationListener<UserScrollNotification> 위젯으로 감싸주기만 하면 됩니다. UserScrollNotification은 사용자의 직접적인 스크롤 액션에 의해서만 발생하는 알림이라, 코드에 의한 스크롤과 구분되어 더 정확한 제어가 가능합니다.

먼저, BottomNavigationBar의 가시성(visibility)을 제어할 상태 변수 _isVisible을 추가합니다.


// _HomePageState 클래스 내부에 추가
bool _isVisible = true;

다음으로, ListViewNotificationListener로 감싸고 onNotification 콜백을 구현합니다. 이 콜백 함수는 스크롤 이벤트가 발생할 때마다 호출됩니다.


// build 메소드 내부
// ...
body: NotificationListener<UserScrollNotification>(
  onNotification: (notification) {
    // 사용자가 아래로 스크롤 할 때 (리스트의 끝 방향)
    if (notification.direction == ScrollDirection.reverse) {
      if (_isVisible) {
        setState(() {
          _isVisible = false;
        });
      }
    }
    // 사용자가 위로 스크롤 할 때 (리스트의 시작 방향)
    else if (notification.direction == ScrollDirection.forward) {
      if (!_isVisible) {
        setState(() {
          _isVisible = true;
        });
      }
    }
    // true를 반환하면 상위로 알림이 전파되는 것을 막습니다.
    return true; 
  },
  child: ListView.builder(
    itemCount: 100,
    itemBuilder: (context, index) {
      return ListTile(
        title: Text('Item $index'),
      );
    },
  ),
),
// ...

이제 스크롤 방향에 따라 _isVisible 상태가 변경됩니다. 하지만 아직 UI에 아무런 변화가 없습니다. 이제 이 상태값을 이용하여 BottomNavigationBar를 실제로 움직여 보겠습니다.

2.3. 애니메이션으로 부드럽게 움직이기

_isVisible 상태가 바뀔 때마다 BottomNavigationBar가 부드럽게 나타나고 사라지게 하려면 애니메이션이 필요합니다. AnimationControllerAnimatedContainer 또는 Transform.translate를 사용할 수 있습니다. 여기서는 더 정교한 제어가 가능한 AnimationControllerTransform.translateAnimatedBuilder와 함께 사용하는 방법을 소개합니다. 이 조합은 성능적으로도 매우 효율적입니다.

2.3.1. AnimationController 초기화

_HomePageStateAnimationController를 추가하고 initState에서 초기화합니다. vsync를 사용해야 하므로 _HomePageStateTickerProviderStateMixin을 추가해야 합니다.


// 클래스 선언부 수정
class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
  // ... 기존 변수들

  late AnimationController _animationController;
  late Animation<Offset> _offsetAnimation;
  final double _bottomNavBarHeight = 85.0; // BottomNavBar의 높이, kBottomNavigationBarHeight + 여유분

  @override
  void initState() {
    super.initState();
    // 애니메이션 컨트롤러 초기화
    _animationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 300), // 애니메이션 속도
    );

    // 오프셋 애니메이션 초기화
    // 시작(begin): Offset(0, 1) -> 화면 하단 밖
    // 끝(end): Offset.zero -> 화면 안 원래 위치
    _offsetAnimation = Tween<Offset>(
      begin: Offset.zero,
      end: Offset(0, 1), // Y축으로 자신의 높이만큼 아래로 이동
    ).animate(CurvedAnimation(
      parent: _animationController,
      curve: Curves.easeOut,
    ));
    
    // 처음에는 바가 보이도록 애니메이션을 완료(보이는 상태)로 설정
    _animationController.forward(); // 이 부분을 수정하여 시작 상태를 제어할 수 있습니다.
  }

  @override
  void dispose() {
    _animationController.dispose();
    super.dispose();
  }

  // ...
}

_animationController는 애니메이션의 '엔진'과 같습니다. duration을 설정하고 vsync를 연결하여 화면 주사율에 맞춰 부드러운 애니메이션을 생성합니다. _offsetAnimation은 컨트롤러의 값(0.0~1.0)을 실제 UI가 사용할 Offset 값으로 변환해주는 역할을 합니다. Offset(0, 1)은 Y축으로 위젯 자신의 높이의 1배만큼 아래로 이동하라는 의미입니다. (SlideTransition이 내부적으로 이렇게 계산합니다.)

2.3.2. 스크롤에 따른 애니메이션 트리거

이제 NotificationListener에서 setState를 호출하는 대신, _animationController를 제어합니다.


// onNotification 콜백 수정
onNotification: (notification) {
  if (notification.direction == ScrollDirection.reverse) {
    // 아래로 스크롤 -> 숨기기
    if (_animationController.isCompleted) { // 이미 보이는 상태일 때만 실행
        _animationController.reverse(); // 0.0으로 (숨겨지는 방향으로)
    }
  } else if (notification.direction == ScrollDirection.forward) {
    // 위로 스크롤 -> 보이기
    if (_animationController.isDismissed) { // 이미 숨겨진 상태일 때만 실행
        _animationController.forward(); // 1.0으로 (보이는 방향으로)
    }
  }
  return true;
},

코드를 약간 수정했습니다. _animationController.forward()는 애니메이션을 '끝' 상태(보이는 상태)로, reverse()는 '시작' 상태(숨겨진 상태)로 만듭니다. isCompletedisDismissed로 현재 애니메이션 상태를 확인하여 불필요한 호출을 막습니다.

2.3.3. SlideTransition으로 UI에 애니메이션 적용

마지막으로, BottomNavigationBarSlideTransition 위젯으로 감싸서 애니메이션을 실제로 UI에 반영합니다.


// build 메소드의 bottomNavigationBar 부분 수정
// ...
bottomNavigationBar: SlideTransition(
  position: _offsetAnimation,
  child: BottomNavigationBar(
    // ... 기존 BottomNavigationBar 코드
  ),
),

잠깐, 코드를 약간 수정해야겠습니다. Tween의 begin/end를 Offset(0, 0)(보임)과 Offset(0, 1)(숨김)으로 설정하고, 컨트롤러의 forward/reverse를 그에 맞게 다시 조정하는 것이 더 직관적입니다. 수정된 전체 코드를 보겠습니다.

3. 완성된 전체 코드 및 상세 설명

지금까지의 개념들을 종합하여 완성된, 즉시 실행 가능한 코드는 다음과 같습니다. 직관성을 위해 애니메이션 방향을 다시 정리했습니다.


import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Scroll Aware Bottom Bar',
      theme: ThemeData(
        primarySwatch: Colors.indigo,
        scaffoldBackgroundColor: Colors.grey[200],
      ),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> with TickerProviderStateMixin {
  int _selectedIndex = 0;

  // 애니메이션 관련
  late final AnimationController _hideBottomBarAnimationController;

  // 가시성 상태를 직접 관리하는 변수
  bool _isBottomBarVisible = true;

  @override
  void initState() {
    super.initState();
    _hideBottomBarAnimationController = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 200),
      // 초기값: 1.0 (완전히 보이는 상태)
      value: 1.0, 
    );
  }

  @override
  void dispose() {
    _hideBottomBarAnimationController.dispose();
    super.dispose();
  }

  // 스크롤 알림 처리 함수
  bool _handleScrollNotification(ScrollNotification notification) {
    // 사용자의 스크롤 액션일 때만 처리
    if (notification is UserScrollNotification) {
      final UserScrollNotification userScroll = notification;
      switch (userScroll.direction) {
        case ScrollDirection.forward:
          // 위로 스크롤: 바를 보여줌
          if (!_isBottomBarVisible) {
            setState(() {
              _isBottomBarVisible = true;
              _hideBottomBarAnimationController.forward();
            });
          }
          break;
        case ScrollDirection.reverse:
          // 아래로 스크롤: 바를 숨김
          if (_isBottomBarVisible) {
            setState(() {
              _isBottomBarVisible = false;
              _hideBottomBarAnimationController.reverse();
            });
          }
          break;
        case ScrollDirection.idle:
          // 스크롤 멈춤: 아무것도 안 함
          break;
      }
    }
    return false; // 다른 리스너도 알림을 받을 수 있도록 false 반환
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Perfect Scroll-Aware Bar'),
      ),
      body: NotificationListener<ScrollNotification>(
        onNotification: _handleScrollNotification,
        child: ListView.builder(
          // 스크롤 위치를 추후에 참조하기 위해 컨트롤러를 연결해둘 수 있습니다. (예외처리용)
          // controller: _scrollController, 
          itemCount: 100,
          itemBuilder: (context, index) {
            return Card(
              margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
              child: ListTile(
                leading: CircleAvatar(child: Text('$index')),
                title: Text('List Item $index'),
                subtitle: const Text('Scroll up and down to see the magic!'),
              ),
            );
          },
        ),
      ),
      // SizeTransition을 사용하여 높이를 조절하는 애니메이션을 구현
      bottomNavigationBar: SizeTransition(
        sizeFactor: _hideBottomBarAnimationController,
        axisAlignment: -1.0, // 바가 사라질 때 아래로 정렬되도록 함
        child: BottomNavigationBar(
          items: const <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              icon: Icon(Icons.home),
              label: 'Home',
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.search),
              label: 'Search',
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.person),
              label: 'Profile',
            ),
          ],
          currentIndex: _selectedIndex,
          onTap: (index) {
            setState(() {
              _selectedIndex = index;
            });
          },
          selectedItemColor: Colors.indigo,
          unselectedItemColor: Colors.grey,
        ),
      ),
    );
  }
}

이전 예제에서 SlideTransition을 사용했지만, SizeTransition을 사용하는 것이 더 일반적이고 자연스러운 효과를 줍니다. SizeTransition은 자식 위젯의 높이(또는 너비)를 sizeFactor 값(0.0 ~ 1.0)에 따라 조절합니다. 애니메이션 컨트롤러의 값을 직접 sizeFactor에 연결하면, 컨트롤러 값이 1.0일 때 원래 높이를, 0.0일 때 높이 0을 갖게 되어 자연스럽게 사라지는 효과를 냅니다. axisAlignment: -1.0은 높이가 줄어들 때 위젯이 아래쪽 경계를 기준으로 축소되도록 하여, 마치 아래로 사라지는 것처럼 보이게 하는 중요한 속성입니다.

4. 심화 과정: 예외 처리 및 고급 기법

기본적인 기능은 완성되었습니다. 하지만 실제 프로덕션 환경에서는 다양한 예외 상황이 발생할 수 있습니다. 완성도를 높이기 위한 몇 가지 고급 기법들을 알아봅시다.

4.1. 스크롤 끝(Edge)에 도달했을 때 처리

사용자가 스크롤을 아주 빠르게 '휙' 던졌다가(Fling) 리스트의 맨 위나 맨 아래에 도달했을 때, 마지막 스크롤 방향이 reverse였다면 바가 숨겨진 채로 멈출 수 있습니다. 일반적으로 리스트의 끝에 도달했을 때는 탐색 바가 항상 보이는 것이 사용자 경험에 좋습니다.

이 문제를 해결하려면 ScrollController를 함께 사용해야 합니다. 컨트롤러를 ListView에 연결하고, 스크롤 알림 콜백에서 현재 스크롤 위치를 확인합니다.


// _HomePageState에 ScrollController 추가
final ScrollController _scrollController = ScrollController();

// initState에 리스너 추가 (또는 NotificationListener 내에서 확인)
@override
void initState() {
  super.initState();
  // ... 기존 코드
  _scrollController.addListener(_scrollListener);
}

void _scrollListener() {
    // 스크롤이 맨 위에 도달했을 때
    if (_scrollController.position.atEdge && _scrollController.position.pixels == 0) {
        if (!_isBottomBarVisible) {
            setState(() {
                _isBottomBarVisible = true;
                _hideBottomBarAnimationController.forward();
            });
        }
    }
}

// ListView에 controller 연결
// ...
child: ListView.builder(
  controller: _scrollController,
// ...

위 코드는 ScrollController의 리스너를 통해 스크롤 위치를 계속 감시합니다. position.atEdge가 true이고 position.pixels가 0이면 스크롤이 맨 위에 도달했음을 의미합니다. 이때 BottomNavigationBar를 강제로 보이게 합니다. NotificationListenerScrollController.addListener를 조합하여 더 정교한 제어가 가능해집니다.

4.2. 상태 관리 라이브러리(Provider)와 함께 사용하기

앱의 규모가 커지면 UI와 비즈니스 로직을 분리하는 것이 중요합니다. Provider나 Riverpod 같은 상태 관리 라이브러리를 사용하면 코드를 더 깔끔하게 구조화할 수 있습니다. BottomNavigationBar의 가시성 상태를 ChangeNotifier로 분리해 보겠습니다.

4.2.1. BottomBarVisibilityNotifier 생성


import 'package:flutter/material.dart';

class BottomBarVisibilityNotifier with ChangeNotifier {
  bool _isVisible = true;

  bool get isVisible => _isVisible;

  void show() {
    if (!_isVisible) {
      _isVisible = true;
      notifyListeners();
    }
  }

  void hide() {
    if (_isVisible) {
      _isVisible = false;
      notifyListeners();
    }
  }
}

4.2.2. Provider 설정 및 UI 연동

main.dart에서 ChangeNotifierProvider를 설정하고, UI에서는 Consumer 또는 context.watch를 사용하여 상태를 구독합니다.


// main.dart
void main() {
  runApp(
    ChangeNotifierProvider(
      create: (_) => BottomBarVisibilityNotifier(),
      child: const MyApp(),
    ),
  );
}

// HomePage.dart
// _handleScrollNotification 함수 내에서 setState 대신 Notifier 호출
// ...
if (userScroll.direction == ScrollDirection.forward) {
    context.read<BottomBarVisibilityNotifier>().show();
} else if (userScroll.direction == ScrollDirection.reverse) {
    context.read<BottomBarVisibilityNotifier>().hide();
}
// ...

// build 메소드 내에서
@override
Widget build(BuildContext context) {
    final isVisible = context.watch<BottomBarVisibilityNotifier>().isVisible;

    // isVisible 상태가 변경될 때마다 애니메이션 컨트롤러를 직접 제어
    // 이 로직은 build 메소드 내에 위치하면 안되므로, Consumer나 다른 방식을 사용해야 합니다.
    // 더 나은 방법은 Notifier 자체에 AnimationController를 포함시키는 것입니다.

    // ... 올바른 구현을 위해 Notifier가 AnimationController를 갖도록 리팩토링 ...
}

더 발전된 구조는 BottomBarVisibilityNotifierAnimationController를 직접 소유하고 관리하는 것입니다. 이렇게 하면 UI는 단순히 Notifier의 상태만 구독하고, 애니메이션 로직은 Notifier 내부에 완전히 캡슐화되어 재사용성이 극대화됩니다.

4.3. CustomScrollViewSliver 위젯과의 호환성

우리가 사용한 NotificationListener 방식의 가장 큰 장점은 특정 스크롤 위젯에 종속되지 않는다는 것입니다. ListView 대신 CustomScrollViewSliverAppBar, SliverList 등을 사용하는 복잡한 화면에서도 동일한 코드가 문제없이 작동합니다.


// body 부분을 CustomScrollView로 교체해도 동일하게 작동
body: NotificationListener<ScrollNotification>(
  onNotification: _handleScrollNotification,
  child: CustomScrollView(
    slivers: [
      SliverAppBar(
        title: Text('Complex Scroll'),
        floating: true,
        pinned: false,
      ),
      SliverList(
        delegate: SliverChildBuilderDelegate(
          (context, index) => Card(
            // ...
          ),
          childCount: 100,
        ),
      ),
    ],
  ),
),

CustomScrollView가 발생시키는 스크롤 알림 또한 NotificationListener가 감지할 수 있으므로, 어떤 종류의 스크롤 뷰를 사용하든 하단 바 숨김/표시 기능은 일관되게 동작합니다. 이것이 ScrollController에만 의존하는 방식보다 NotificationListener가 더 유연하고 강력한 이유입니다.

결론: 사용자 경험을 한 단계 끌어올리는 디테일

지금까지 Flutter에서 스크롤 방향에 따라 BottomNavigationBar를 동적으로 숨기고 표시하는 방법을 깊이 있게 탐구했습니다. 단순히 기능을 구현하는 것을 넘어, NotificationListener를 활용한 유연한 구조, AnimationControllerSizeTransition을 이용한 부드러운 애니메이션, 그리고 스크롤 끝에 도달하는 예외 상황 처리까지 다루었습니다.

이러한 동적 UI는 단순히 '있으면 좋은' 기능이 아니라, 사용자가 앱의 콘텐츠에 더 깊이 몰입하게 하고, 제한된 모바일 화면을 최대한 효율적으로 사용할 수 있게 해주는 핵심적인 UX 요소입니다. 오늘 배운 기법들을 여러분의 프로젝트에 적용하여, 사용자에게 더욱 쾌적하고 전문적인 인상을 주는 앱을 만들어 보시길 바랍니다.

핵심을 요약하자면 다음과 같습니다.

  • 스크롤 감지: NotificationListener<UserScrollNotification>을 사용하여 사용자의 명시적인 스크롤 의도를 파악합니다.
  • 상태 관리: bool 변수나 ChangeNotifier를 통해 바의 가시성 상태를 관리합니다.
  • 애니메이션: AnimationController를 상태에 따라 제어하고, SizeTransition 또는 SlideTransition을 사용하여 UI를 부드럽게 변경합니다.
  • 예외 처리: ScrollController를 보조적으로 사용하여 스크롤의 끝(edge)에 도달하는 등의 특수 상황에 대응하여 완성도를 높입니다.

이제 여러분은 Flutter에서 어떤 스크롤 뷰와도 완벽하게 연동되는 동적 BottomNavigationBar를 자신 있게 구현할 수 있을 것입니다. 코드를 직접 실행해보고, 애니메이션 속도나 커브를 변경해보며 자신만의 스타일을 찾아보는 것을 추천합니다.


0 개의 댓글:

Post a Comment