blob: 3046c74b08d9d8bb2b0e0e80e0d79c23d13f9e7b [file] [log] [blame]
// 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';
import '../../gallery/demo.dart';
class NavigationIconView {
NavigationIconView({
Widget icon,
Widget activeIcon,
String title,
Color color,
TickerProvider vsync,
}) : _icon = icon,
_color = color,
_title = title,
item = BottomNavigationBarItem(
icon: icon,
activeIcon: activeIcon,
title: Text(title),
backgroundColor: color,
),
controller = AnimationController(
duration: kThemeAnimationDuration,
vsync: vsync,
) {
_animation = controller.drive(CurveTween(
curve: const Interval(0.5, 1.0, curve: Curves.fastOutSlowIn),
));
}
final Widget _icon;
final Color _color;
final String _title;
final BottomNavigationBarItem item;
final AnimationController controller;
Animation<double> _animation;
FadeTransition transition(BottomNavigationBarType type, BuildContext context) {
Color iconColor;
if (type == BottomNavigationBarType.shifting) {
iconColor = _color;
} else {
final ThemeData themeData = Theme.of(context);
iconColor = themeData.brightness == Brightness.light
? themeData.primaryColor
: themeData.accentColor;
}
return FadeTransition(
opacity: _animation,
child: SlideTransition(
position: _animation.drive(
Tween<Offset>(
begin: const Offset(0.0, 0.02), // Slightly down.
end: Offset.zero,
),
),
child: IconTheme(
data: IconThemeData(
color: iconColor,
size: 120.0,
),
child: Semantics(
label: 'Placeholder for $_title tab',
child: _icon,
),
),
),
);
}
}
class CustomIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
final IconThemeData iconTheme = IconTheme.of(context);
return Container(
margin: const EdgeInsets.all(4.0),
width: iconTheme.size - 8.0,
height: iconTheme.size - 8.0,
color: iconTheme.color,
);
}
}
class CustomInactiveIcon extends StatelessWidget {
@override
Widget build(BuildContext context) {
final IconThemeData iconTheme = IconTheme.of(context);
return Container(
margin: const EdgeInsets.all(4.0),
width: iconTheme.size - 8.0,
height: iconTheme.size - 8.0,
decoration: BoxDecoration(
border: Border.all(color: iconTheme.color, width: 2.0),
)
);
}
}
class BottomNavigationDemo extends StatefulWidget {
static const String routeName = '/material/bottom_navigation';
@override
_BottomNavigationDemoState createState() => _BottomNavigationDemoState();
}
class _BottomNavigationDemoState extends State<BottomNavigationDemo>
with TickerProviderStateMixin {
int _currentIndex = 0;
BottomNavigationBarType _type = BottomNavigationBarType.shifting;
List<NavigationIconView> _navigationViews;
@override
void initState() {
super.initState();
_navigationViews = <NavigationIconView>[
NavigationIconView(
icon: const Icon(Icons.access_alarm),
title: 'Alarm',
color: Colors.deepPurple,
vsync: this,
),
NavigationIconView(
activeIcon: CustomIcon(),
icon: CustomInactiveIcon(),
title: 'Box',
color: Colors.deepOrange,
vsync: this,
),
NavigationIconView(
activeIcon: const Icon(Icons.cloud),
icon: const Icon(Icons.cloud_queue),
title: 'Cloud',
color: Colors.teal,
vsync: this,
),
NavigationIconView(
activeIcon: const Icon(Icons.favorite),
icon: const Icon(Icons.favorite_border),
title: 'Favorites',
color: Colors.indigo,
vsync: this,
),
NavigationIconView(
icon: const Icon(Icons.event_available),
title: 'Event',
color: Colors.pink,
vsync: this,
)
];
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.
});
}
Widget _buildTransitionsStack() {
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) {
final Animation<double> aAnimation = a.opacity;
final Animation<double> bAnimation = b.opacity;
final double aValue = aAnimation.value;
final double bValue = bAnimation.value;
return aValue.compareTo(bValue);
});
return Stack(children: transitions);
}
@override
Widget build(BuildContext context) {
final BottomNavigationBar botNavBar = BottomNavigationBar(
items: _navigationViews
.map<BottomNavigationBarItem>((NavigationIconView navigationView) => navigationView.item)
.toList(),
currentIndex: _currentIndex,
type: _type,
onTap: (int index) {
setState(() {
_navigationViews[_currentIndex].controller.reverse();
_currentIndex = index;
_navigationViews[_currentIndex].controller.forward();
});
},
);
return Scaffold(
appBar: AppBar(
title: const Text('Bottom navigation'),
actions: <Widget>[
MaterialDemoDocumentationButton(BottomNavigationDemo.routeName),
PopupMenuButton<BottomNavigationBarType>(
onSelected: (BottomNavigationBarType value) {
setState(() {
_type = value;
});
},
itemBuilder: (BuildContext context) => <PopupMenuItem<BottomNavigationBarType>>[
const PopupMenuItem<BottomNavigationBarType>(
value: BottomNavigationBarType.fixed,
child: Text('Fixed'),
),
const PopupMenuItem<BottomNavigationBarType>(
value: BottomNavigationBarType.shifting,
child: Text('Shifting'),
)
],
)
],
),
body: Center(
child: _buildTransitionsStack()
),
bottomNavigationBar: botNavBar,
);
}
}