1// Copyright 2016 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'package:flutter/material.dart'; 6 7import '../../gallery/demo.dart'; 8 9class NavigationIconView { 10 NavigationIconView({ 11 Widget icon, 12 Widget activeIcon, 13 String title, 14 Color color, 15 TickerProvider vsync, 16 }) : _icon = icon, 17 _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 duration: kThemeAnimationDuration, 27 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 FadeTransition transition(BottomNavigationBarType type, BuildContext context) { 42 Color iconColor; 43 if (type == BottomNavigationBarType.shifting) { 44 iconColor = _color; 45 } else { 46 final ThemeData themeData = Theme.of(context); 47 iconColor = themeData.brightness == Brightness.light 48 ? themeData.primaryColor 49 : themeData.accentColor; 50 } 51 52 return FadeTransition( 53 opacity: _animation, 54 child: SlideTransition( 55 position: _animation.drive( 56 Tween<Offset>( 57 begin: const Offset(0.0, 0.02), // Slightly down. 58 end: Offset.zero, 59 ), 60 ), 61 child: IconTheme( 62 data: IconThemeData( 63 color: iconColor, 64 size: 120.0, 65 ), 66 child: Semantics( 67 label: 'Placeholder for $_title tab', 68 child: _icon, 69 ), 70 ), 71 ), 72 ); 73 } 74} 75 76class CustomIcon extends StatelessWidget { 77 @override 78 Widget build(BuildContext context) { 79 final IconThemeData iconTheme = IconTheme.of(context); 80 return Container( 81 margin: const EdgeInsets.all(4.0), 82 width: iconTheme.size - 8.0, 83 height: iconTheme.size - 8.0, 84 color: iconTheme.color, 85 ); 86 } 87} 88 89class CustomInactiveIcon extends StatelessWidget { 90 @override 91 Widget build(BuildContext context) { 92 final IconThemeData iconTheme = IconTheme.of(context); 93 return Container( 94 margin: const EdgeInsets.all(4.0), 95 width: iconTheme.size - 8.0, 96 height: iconTheme.size - 8.0, 97 decoration: BoxDecoration( 98 border: Border.all(color: iconTheme.color, width: 2.0), 99 ), 100 ); 101 } 102} 103 104class BottomNavigationDemo extends StatefulWidget { 105 static const String routeName = '/material/bottom_navigation'; 106 107 @override 108 _BottomNavigationDemoState createState() => _BottomNavigationDemoState(); 109} 110 111class _BottomNavigationDemoState extends State<BottomNavigationDemo> 112 with TickerProviderStateMixin { 113 int _currentIndex = 0; 114 BottomNavigationBarType _type = BottomNavigationBarType.shifting; 115 List<NavigationIconView> _navigationViews; 116 117 @override 118 void initState() { 119 super.initState(); 120 _navigationViews = <NavigationIconView>[ 121 NavigationIconView( 122 icon: const Icon(Icons.access_alarm), 123 title: 'Alarm', 124 color: Colors.deepPurple, 125 vsync: this, 126 ), 127 NavigationIconView( 128 activeIcon: CustomIcon(), 129 icon: CustomInactiveIcon(), 130 title: 'Box', 131 color: Colors.deepOrange, 132 vsync: this, 133 ), 134 NavigationIconView( 135 activeIcon: const Icon(Icons.cloud), 136 icon: const Icon(Icons.cloud_queue), 137 title: 'Cloud', 138 color: Colors.teal, 139 vsync: this, 140 ), 141 NavigationIconView( 142 activeIcon: const Icon(Icons.favorite), 143 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 _navigationViews[_currentIndex].controller.value = 1.0; 157 } 158 159 @override 160 void dispose() { 161 for (NavigationIconView view in _navigationViews) 162 view.controller.dispose(); 163 super.dispose(); 164 } 165 166 Widget _buildTransitionsStack() { 167 final List<FadeTransition> transitions = <FadeTransition>[]; 168 169 for (NavigationIconView view in _navigationViews) 170 transitions.add(view.transition(_type, context)); 171 172 // We want to have the newly animating (fading in) views on top. 173 transitions.sort((FadeTransition a, FadeTransition b) { 174 final Animation<double> aAnimation = a.opacity; 175 final Animation<double> bAnimation = b.opacity; 176 final double aValue = aAnimation.value; 177 final double bValue = bAnimation.value; 178 return aValue.compareTo(bValue); 179 }); 180 181 return Stack(children: transitions); 182 } 183 184 @override 185 Widget build(BuildContext context) { 186 final BottomNavigationBar botNavBar = BottomNavigationBar( 187 items: _navigationViews 188 .map<BottomNavigationBarItem>((NavigationIconView navigationView) => navigationView.item) 189 .toList(), 190 currentIndex: _currentIndex, 191 type: _type, 192 onTap: (int index) { 193 setState(() { 194 _navigationViews[_currentIndex].controller.reverse(); 195 _currentIndex = index; 196 _navigationViews[_currentIndex].controller.forward(); 197 }); 198 }, 199 ); 200 201 return Scaffold( 202 appBar: AppBar( 203 title: const Text('Bottom navigation'), 204 actions: <Widget>[ 205 MaterialDemoDocumentationButton(BottomNavigationDemo.routeName), 206 PopupMenuButton<BottomNavigationBarType>( 207 onSelected: (BottomNavigationBarType value) { 208 setState(() { 209 _type = value; 210 }); 211 }, 212 itemBuilder: (BuildContext context) => <PopupMenuItem<BottomNavigationBarType>>[ 213 const PopupMenuItem<BottomNavigationBarType>( 214 value: BottomNavigationBarType.fixed, 215 child: Text('Fixed'), 216 ), 217 const PopupMenuItem<BottomNavigationBarType>( 218 value: BottomNavigationBarType.shifting, 219 child: Text('Shifting'), 220 ), 221 ], 222 ), 223 ], 224 ), 225 body: Center( 226 child: _buildTransitionsStack(), 227 ), 228 bottomNavigationBar: botNavBar, 229 ); 230 } 231} 232