bottom_navigation_demo.dart 6.42 KB
Newer Older
1 2 3 4 5 6
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/material.dart';

7 8
import '../../gallery/demo.dart';

9 10
class NavigationIconView {
  NavigationIconView({
11
    Widget icon,
12
    Widget activeIcon,
13
    String title,
14 15
    Color color,
    TickerProvider vsync,
16 17
  }) : _icon = icon,
       _color = color,
18
       _title = title,
19
       item = BottomNavigationBarItem(
20
         icon: icon,
21
         activeIcon: activeIcon,
22
         title: Text(title),
23
         backgroundColor: color,
24
       ),
25
       controller = AnimationController(
26 27
         duration: kThemeAnimationDuration,
         vsync: vsync,
28
       ) {
29
    _animation = controller.drive(CurveTween(
30
      curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
31
    ));
32
  }
33

34
  final Widget _icon;
35
  final Color _color;
36
  final String _title;
37
  final BottomNavigationBarItem item;
38
  final AnimationController controller;
39
  Animation<double> _animation;
40 41 42 43 44 45 46

  FadeTransition transition(BottomNavigationBarType type, BuildContext context) {
    Color iconColor;
    if (type == BottomNavigationBarType.shifting) {
      iconColor = _color;
    } else {
      final ThemeData themeData = Theme.of(context);
47 48 49
      iconColor = themeData.brightness == Brightness.light
          ? themeData.primaryColor
          : themeData.accentColor;
50 51
    }

52
    return FadeTransition(
53
      opacity: _animation,
54
      child: SlideTransition(
55 56 57 58 59 60
        position: _animation.drive(
          Tween<Offset>(
            begin: const Offset(0.0, 0.02), // Slightly down.
            end: Offset.zero,
          ),
        ),
61 62
        child: IconTheme(
          data: IconThemeData(
63 64 65
            color: iconColor,
            size: 120.0,
          ),
66
          child: Semantics(
67 68 69
            label: 'Placeholder for $_title tab',
            child: _icon,
          ),
70 71 72 73 74 75 76 77 78
        ),
      ),
    );
  }
}

class CustomIcon extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
79
    final IconThemeData iconTheme = IconTheme.of(context);
80
    return Container(
81 82 83
      margin: const EdgeInsets.all(4.0),
      width: iconTheme.size - 8.0,
      height: iconTheme.size - 8.0,
84
      color: iconTheme.color,
85 86 87 88
    );
  }
}

89 90 91 92
class CustomInactiveIcon extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final IconThemeData iconTheme = IconTheme.of(context);
93
    return Container(
94 95 96
      margin: const EdgeInsets.all(4.0),
      width: iconTheme.size - 8.0,
      height: iconTheme.size - 8.0,
97 98
      decoration: BoxDecoration(
        border: Border.all(color: iconTheme.color, width: 2.0),
99
      ),
100 101 102 103
    );
  }
}

104
class BottomNavigationDemo extends StatefulWidget {
105
  static const String routeName = '/material/bottom_navigation';
106 107

  @override
108
  _BottomNavigationDemoState createState() => _BottomNavigationDemoState();
109 110
}

111 112
class _BottomNavigationDemoState extends State<BottomNavigationDemo>
    with TickerProviderStateMixin {
113 114 115 116 117 118 119 120
  int _currentIndex = 0;
  BottomNavigationBarType _type = BottomNavigationBarType.shifting;
  List<NavigationIconView> _navigationViews;

  @override
  void initState() {
    super.initState();
    _navigationViews = <NavigationIconView>[
121
      NavigationIconView(
122
        icon: const Icon(Icons.access_alarm),
123
        title: 'Alarm',
124
        color: Colors.deepPurple,
125
        vsync: this,
126
      ),
127 128 129
      NavigationIconView(
        activeIcon: CustomIcon(),
        icon: CustomInactiveIcon(),
130
        title: 'Box',
131
        color: Colors.deepOrange,
132 133
        vsync: this,
      ),
134
      NavigationIconView(
135 136
        activeIcon: const Icon(Icons.cloud),
        icon: const Icon(Icons.cloud_queue),
137
        title: 'Cloud',
138
        color: Colors.teal,
139
        vsync: this,
140
      ),
141
      NavigationIconView(
142 143
        activeIcon: const Icon(Icons.favorite),
        icon: const Icon(Icons.favorite_border),
144
        title: 'Favorites',
145
        color: Colors.indigo,
146
        vsync: this,
147
      ),
148
      NavigationIconView(
149
        icon: const Icon(Icons.event_available),
150
        title: 'Event',
151
        color: Colors.pink,
152
        vsync: this,
153
      ),
154 155 156 157 158 159 160 161 162 163 164 165
    ];

    _navigationViews[_currentIndex].controller.value = 1.0;
  }

  @override
  void dispose() {
    for (NavigationIconView view in _navigationViews)
      view.controller.dispose();
    super.dispose();
  }

166
  Widget _buildTransitionsStack() {
167 168 169
    final List<FadeTransition> transitions = <FadeTransition>[
      for (NavigationIconView view in _navigationViews) view.transition(_type, context),
    ];
170 171 172

    // We want to have the newly animating (fading in) views on top.
    transitions.sort((FadeTransition a, FadeTransition b) {
173 174
      final Animation<double> aAnimation = a.opacity;
      final Animation<double> bAnimation = b.opacity;
175 176
      final double aValue = aAnimation.value;
      final double bValue = bAnimation.value;
177 178 179
      return aValue.compareTo(bValue);
    });

180
    return Stack(children: transitions);
181 182 183 184
  }

  @override
  Widget build(BuildContext context) {
185
    final BottomNavigationBar botNavBar = BottomNavigationBar(
186
      items: _navigationViews
187
          .map<BottomNavigationBarItem>((NavigationIconView navigationView) => navigationView.item)
188
          .toList(),
189 190 191 192 193 194 195 196
      currentIndex: _currentIndex,
      type: _type,
      onTap: (int index) {
        setState(() {
          _navigationViews[_currentIndex].controller.reverse();
          _currentIndex = index;
          _navigationViews[_currentIndex].controller.forward();
        });
197
      },
198 199
    );

200 201
    return Scaffold(
      appBar: AppBar(
202
        title: const Text('Bottom navigation'),
203
        actions: <Widget>[
204
          MaterialDemoDocumentationButton(BottomNavigationDemo.routeName),
205
          PopupMenuButton<BottomNavigationBarType>(
206 207 208 209 210 211
            onSelected: (BottomNavigationBarType value) {
              setState(() {
                _type = value;
              });
            },
            itemBuilder: (BuildContext context) => <PopupMenuItem<BottomNavigationBarType>>[
212
              const PopupMenuItem<BottomNavigationBarType>(
213
                value: BottomNavigationBarType.fixed,
214
                child: Text('Fixed'),
215
              ),
216
              const PopupMenuItem<BottomNavigationBarType>(
217
                value: BottomNavigationBarType.shifting,
218
                child: Text('Shifting'),
219
              ),
220
            ],
221
          ),
222
        ],
223
      ),
224
      body: Center(
225
        child: _buildTransitionsStack(),
226
      ),
227
      bottomNavigationBar: botNavBar,
228 229 230
    );
  }
}