bottom_navigation_demo.dart 5.98 KB
Newer Older
1 2 3 4 5 6 7 8
// 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';

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

31
  final Widget _icon;
32
  final Color _color;
33
  final String _title;
34
  final BottomNavigationBarItem item;
35 36 37 38 39 40 41 42 43
  final AnimationController controller;
  CurvedAnimation _animation;

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

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

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

class BottomNavigationDemo extends StatefulWidget {
85
  static const String routeName = '/material/bottom_navigation';
86 87 88 89 90

  @override
  _BottomNavigationDemoState createState() => new _BottomNavigationDemoState();
}

91 92
class _BottomNavigationDemoState extends State<BottomNavigationDemo>
    with TickerProviderStateMixin {
93 94 95 96 97 98 99 100 101
  int _currentIndex = 0;
  BottomNavigationBarType _type = BottomNavigationBarType.shifting;
  List<NavigationIconView> _navigationViews;

  @override
  void initState() {
    super.initState();
    _navigationViews = <NavigationIconView>[
      new NavigationIconView(
102
        icon: const Icon(Icons.access_alarm),
103
        title: 'Alarm',
104
        color: Colors.deepPurple,
105
        vsync: this,
106
      ),
107 108
      new NavigationIconView(
        icon: new CustomIcon(),
109
        title: 'Box',
110
        color: Colors.deepOrange,
111 112
        vsync: this,
      ),
113
      new NavigationIconView(
114
        icon: const Icon(Icons.cloud),
115
        title: 'Cloud',
116
        color: Colors.teal,
117
        vsync: this,
118 119
      ),
      new NavigationIconView(
120
        icon: const Icon(Icons.favorite),
121
        title: 'Favorites',
122
        color: Colors.indigo,
123
        vsync: this,
124 125
      ),
      new NavigationIconView(
126
        icon: const Icon(Icons.event_available),
127
        title: 'Event',
128
        color: Colors.pink,
129
        vsync: this,
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
      )
    ];

    for (NavigationIconView view in _navigationViews)
      view.controller.addListener(_rebuild);

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

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

  void _rebuild() {
    setState(() {
      // Rebuild in order to animate views.
    });
  }

152
  Widget _buildTransitionsStack() {
153 154 155 156 157 158 159
    final List<FadeTransition> transitions = <FadeTransition>[];

    for (NavigationIconView view in _navigationViews)
      transitions.add(view.transition(_type, context));

    // We want to have the newly animating (fading in) views on top.
    transitions.sort((FadeTransition a, FadeTransition b) {
160 161
      final Animation<double> aAnimation = a.opacity;
      final Animation<double> bAnimation = b.opacity;
162 163
      final double aValue = aAnimation.value;
      final double bValue = bAnimation.value;
164 165 166
      return aValue.compareTo(bValue);
    });

167
    return new Stack(children: transitions);
168 169 170 171
  }

  @override
  Widget build(BuildContext context) {
172
    final BottomNavigationBar botNavBar = new BottomNavigationBar(
173 174
      items: _navigationViews
          .map((NavigationIconView navigationView) => navigationView.item)
175
          .toList(),
176 177 178 179 180 181 182 183
      currentIndex: _currentIndex,
      type: _type,
      onTap: (int index) {
        setState(() {
          _navigationViews[_currentIndex].controller.reverse();
          _currentIndex = index;
          _navigationViews[_currentIndex].controller.forward();
        });
184
      },
185 186 187 188
    );

    return new Scaffold(
      appBar: new AppBar(
189
        title: const Text('Bottom navigation'),
190 191 192 193 194 195 196 197
        actions: <Widget>[
          new PopupMenuButton<BottomNavigationBarType>(
            onSelected: (BottomNavigationBarType value) {
              setState(() {
                _type = value;
              });
            },
            itemBuilder: (BuildContext context) => <PopupMenuItem<BottomNavigationBarType>>[
198
              const PopupMenuItem<BottomNavigationBarType>(
199
                value: BottomNavigationBarType.fixed,
200
                child: const Text('Fixed'),
201
              ),
202
              const PopupMenuItem<BottomNavigationBarType>(
203
                value: BottomNavigationBarType.shifting,
204
                child: const Text('Shifting'),
205
              )
206
            ],
207
          )
208
        ],
209
      ),
210 211 212
      body: new Center(
        child: _buildTransitionsStack()
      ),
213
      bottomNavigationBar: botNavBar,
214 215 216
    );
  }
}