[Flutter] 스크롤에 따라 AppBar 색상 변경하기


이슈

Flutter를 통해 UI를 개발하던 도중 화면 진입 시 최초에는 AppBar가 투명색이었다가, 스크롤을 내릴수록 AppBar의 색상이 나타나게 하고 싶었다. 다만 AppBar가 투명색일 때도 그 위의 action 버튼들은 보였으면 좋겠다. 어떻게 구현할 수 있을까?

해결

핵심

NotificaionListener를 통해 스크롤을 인식한 후 AnimationController를 사용해 색상 변경

코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
class ExampleScreen extends StatefulWidget {
  const ExampleScreen({Key? key}) : super(key: key);

  @override
  _ExampleScreenState createState() => _ExampleScreenState();
}

class _ExampleScreenState extends State<ExampleScreen>
  with TickerProviderStateMixin {

  final Color _beginColor = Colors.transparent;
  final Color _endColor = Colors.white;

  final Color _beginIconColor = Colors.white;
  final Color _endIconColor = Colors.black;

  final double _animationSpeed = 130;

  late AnimationController _colorAnimationController;
  late Animation _colorTween, _iconColorTween;

  @override
  void initState() {
    super.initState();
    _colorAnimationController =
        AnimationController(vsync: this, duration: Duration(seconds: 0));
    _colorTween = ColorTween(begin: _beginColor, end: _endColor)
        .animate(_colorAnimationController);
    _iconColorTween = ColorTween(begin: _beginIconColor, end: _endIconColor)
        .animate(_colorAnimationController);
  }

  bool _scrollListener(ScrollNotification scrollInfo) {
    if (scrollInfo.metrics.axis == Axis.vertical) {
      _colorAnimationController.animateTo(scrollInfo.metrics.pixels / _animationSpeed);
      return true;
    }
    return false;
  }

  @override
  Widget build(BuildContext context) {
    final double statusBarHeight = MediaQuery.of(context).padding.top;

    return Scaffold(
      body: NotificationListener<ScrollNotification>(
      onNotification: _scrollListener,
      child: Container(
        height: double.infinity,
        child: Stack(children: [
          Container(), // 표시할 화면
          Container(
            height: statusBarHeight + kToolbarHeight, // 상단바 + AppBar 높이
            child: AnimatedBuilder(
              animation: _colorAnimationController,
              builder: (context, build) => AppBar(
                elevation: 0,
                backgroundColor: _colorTween.value,
                leading: IconButton( // 좌측 액션 버튼
                    onPressed: () {},
                    icon: Icon(Icons.arrow_back),
                    color: _iconColorTween.value),
                actions: [
                  IconButton( // 우측 액션 버튼
                      onPressed: () {},
                      icon: Icon(Icons.more_vert,
                          color: _iconColorTween.value))
                ],
              ),
            ),
          )
        ]),
      ),
    ));
  }
}

결과

gif


고찰

애니메이션

Flutter가 타 프레임워크에 비해 갖는 가장 큰 강력함 중 하나는 바로 높은 성능의 애니메이션을 쉽게 구현할 수 있다는 것이다. 물론 이러한 애니메이션을 라이브러리 없이 구현하려면 Animation, AnimationController, AnimatedBuilder 등등 애니메이션에 관한 flutter 내장 기본 클래스들에 대한 높은 이해도가 필요하다. 하지만 한 번 익혀두면 정말 그 어떠한 애니메이션도 구현할 수 있게 된다. 그리고 AnimationControllervsync 속성을 사용하기 위해선 클래스가 TickerProviderStateMixin 상속을 받아야한다.

스크롤

스크롤 인식은 NotificationListener의 인자로 ScrollNotification을 통해 구현된 scrollListener를 전달하여 수행한다. 이때 스크롤 길이에 따라 어느정도 속도로 AppBar의 색상이 변할지 정해주어야 하는데, 위 코드에서 animationSpeed라는 double타입 상수가 그 역할을 한다. 사용자가 스크롤한 길이를 본 상수로 나눠 적절한 값을 취하는 방식으로 애니메이션 속도를 조정할 수 있다.

상단바 및 AppBar 높이

AppBarAnimatedBuilderbuilder 로 넣기 위해 결론적으로 Container 안에 AppBar가 들어가게 되는데, 이때 Container의 높이를 정확히 차지해야할 만큼 지정해주어야 한다. 흔히 우리가 말하는 AppBar는 사실 최상단의 상단바까지 포함한다. AppBar와 상단바의 높이는 다음과 같이 코드 내에서 구할 수 있다.

  • 상단바 높이
    1
    double statusBarHeight = MediaQuery.of(context).padding.top;
    
  • AppBar 높이
    1
    double appBarHeight = kToolbarHeight;
    

기기 상단바 높이의 경우, MediaQuery를 통해 toppadding값을 통해 구할 수 있다. 반면 AppBar와 같은 경우 이미 flutter 내부에 kToolbarHeight라는 상수로 정해져있다! 따라서 위의 전체 코드와 같이 바로 kToolbarHeight로 사용할 수 있다.

0%