Bottom navigation is a common layout for apps. In fact, most of my own apps use bottom navigation. There are official components available for Android and iOS. The Flutter also has a BottomNavigationBar, which is marked here when used.
General usage
Common implementation:
BottomNavigationBar botttomNavBar = BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.code), title: Text('code')),
BottomNavigationBarItem(icon: Icon(Icons.add), title: Text('add')),
BottomNavigationBarItem(icon: Icon(Icons.print), title: Text('print'))
],
currentIndex: _currentIndex,
type: BottomNavigationBarType.fixed,
onTap: (int index) {
setState(() { _currentIndex = index; }); });Copy the code
Q: Looks simple, but is there so much analysis?
A: Emmmm, the advantage of this implementation is the design standard specification, and the official components are simple, stable and reliable. But the premise is that the designer accepts this setting (even if the icon and text are fixed, there will be zooming in and out animation when selecting the icon and text). At least, for mainstream Chinese apps, navigation item is fixed without animation, and official components do not provide this option.
A little problem
If the bottom Navigationtile is selected and the currentIndex is not passed to the BottomNavigationBar, it should be checked by the bottomnavigationtile
Widget _buildBottomNavigationBar() {
return BottomNavigationBar(
type: BottomNavigationBarType.fixed,
items: [
_buildItem(icon: Icons.code, tabItem: TabItem.code),
_buildItem(icon: Icons.add, tabItem: TabItem.add),
_buildItem(icon: Icons.print, tabItem: TabItem.print),
],
onTap: _onSelectTab,
);
}
// Build BottomNavigationBarItem with the custom icon and tabItem
BottomNavigationBarItem _buildItem({IconData icon, TabItem tabItem}) {
String text = tabItemName(tabItem);
return BottomNavigationBarItem(
icon: Icon(
icon,
color: _colorTabMatching(item: tabItem),
),
title: Text(
text,
style: TextStyle(
color: _colorTabMatching(item: tabItem),
),
),
);
}
// Toggle the color of item, select primaryColor, all others are grey
Color _colorTabMatching({TabItem item}) {
return currentItem == item ? Theme.of(context).primaryColor : Colors.grey;
}
Copy the code
Q: What was the effect?
A: Well, that’s good. Wait… Oh, it’s a little bigger. There is no reason, ah, things must be strange demon, need to find the answer from the source code. Home is obviously bigger than Mail, right?
The source code to read
The main code is in bottom_navigation_bar.dart, which is the definition of item
bottom_navigation_bar_item.dart
This is a custom Button that you can place on top of the BottomNavigationBar, which implements Material(Android) and Cupertino(iOS) styles.
bottom_navigation_bar.dart
Scaffold is the scaffolding for the Root Widget-MaterialApp. It encapsulates AppBar, Drawer, SnackBar, BottomNavigationBar and so on that Material Design App will use. The BottomNavigationBarType has fixed and shifting styles, which can be distinguished only if there are more than three. Generally, we use fixed type to experience consistency.
The BottomNavigationBar is a StatefulWidget that you can analyze such a component by (1) looking at the state it holds, (2) looking at its lifecycle implementation, and (3) looking at its build method.
- Holding state
List<AnimationController> _controllers = <AnimationController>[];
List<CurvedAnimation> _animations;
// A queue of color splashes currently being animated.
final Queue<_Circle> _circles = Queue<_Circle>();
// Last splash circle's color, and the final color of the control after
// animation is complete.
Color _backgroundColor;
Copy the code
The first three properties are all related to animation, and the fourth is setting the background.
Why does the BottomNavigationBar have no variable to mark which item is currently selected?
A: One of the rules of functional programming is to keep functions as pure as possible. CurrentIndex is a property that relies on being passed in from the outside and retriggers Render every time it changes. If you maintain it yourself, you also need to provide a callback method for external calls that returns the latest currentIndex value.
- Lifecycle approach
ResetState initializes the above state attributes
@override
void initState(a) {
super.initState();
_resetState();
}
// Recycle the resource
@override
void dispose(a) {
for (AnimationController controller in _controllers)
controller.dispose();
for (_Circle circle in _circles)
circle.dispose();
super.dispose();
}
// The Flutter system calls back to this method when properties change. Reinitialize directly when the number of items changes; When index changes, animate accordingly.
@override
void didUpdateWidget(BottomNavigationBar oldWidget) {
super.didUpdateWidget(oldWidget);
// No animated segue if the length of the items list changes.
if(widget.items.length ! = oldWidget.items.length) { _resetState();return;
}
if(widget.currentIndex ! = oldWidget.currentIndex) {switch (widget.type) {
case BottomNavigationBarType.fixed:
break;
case BottomNavigationBarType.shifting:
_pushCircle(widget.currentIndex);
break;
}
_controllers[oldWidget.currentIndex].reverse();
_controllers[widget.currentIndex].forward();
}
if(_backgroundColor ! = widget.items[widget.currentIndex].backgroundColor) _backgroundColor = widget.items[widget.currentIndex].backgroundColor; }//
@override
Widget build(BuildContext context) {}
Copy the code
Note that initState has a subtle operation: _controllers[widget.currentIndex]. Value = 1.0;
- Analysis build method
@override
Widget build(BuildContext context) {
/ / check the debug
assert(debugCheckHasDirectionality(context));
assert(debugCheckHasMaterialLocalizations(context));
// Labels apply up to _bottomMargin padding. Remainder is media padding.
final double additionalBottomPadding = math.max(MediaQuery.of(context).padding.bottom - _kBottomMargin, 0.0);
// Set the background color according to BottomNavigationBarType
Color backgroundColor;
switch (widget.type) {
case BottomNavigationBarType.fixed:
break;
case BottomNavigationBarType.shifting:
backgroundColor = _backgroundColor;
break;
}
return Semantics( // Semantics is used to implement accessibility
container: true,
explicitChildNodes: true,
child: Stack(
children: <Widget>[
Positioned.fill(
child: Material( // Casts shadow.
elevation: 8.0,
color: backgroundColor,
),
),
ConstrainedBox(
constraints: BoxConstraints(minHeight: kBottomNavigationBarHeight + additionalBottomPadding),
child: Stack(
children: <Widget>[
Positioned.fill( // Click on the circular type of ripple animation
child: CustomPaint(
painter: _RadialPainter(
circles: _circles.toList(),
textDirection: Directionality.of(context),
),
),
),
Material( // Splashes.
type: MaterialType.transparency,
child: Padding(
padding: EdgeInsets.only(bottom: additionalBottomPadding),
child: MediaQuery.removePadding(
context: context,
removeBottom: true.// tiles is just the bottom navigationtile, where you put the BottomNavigationBarItem
child: _createContainer(_createTiles()),
)))]))]));
}}
Copy the code
- _BottomNavigationTile see
Widget _buildIcon() {
...
/ / build Iocn
}
Widget _buildFixedLabel() {
....
// The matrix is used to animate the text, smoother
// The font size should grow here when active, but because of the way
// font rendering works, it doesn't grow smoothly if we just animate
// the font size, so we use a transform instead.
child: Transform(
transform: Matrix4.diagonal3(
Vector3.all(
Tween<double>(
begin: _kInactiveFontSize / _kActiveFontSize,
end: 1.0,
).evaluate(animation),
),
),
alignment: Alignment.bottomCenter,
child: item.title,
),
),
),
);
}
Widget _buildShiftingLabel() {
return Align(
.....
// The shift label is a fade animation, and only the currently selected label is displayed
child: FadeTransition(
alwaysIncludeSemantics: true,
opacity: animation,
child: DefaultTextStyle.merge(
style: const TextStyle(
fontSize: _kActiveFontSize,
color: Colors.white,
),
child: item.title,
),
),
),
);
}
@override
Widget build(BuildContext context) {
int size;
Widget label;
// Generate different labels
switch (type) {
case BottomNavigationBarType.fixed:
size = 1;
label = _buildFixedLabel();
break;
case BottomNavigationBarType.shifting:
size = (flex * 1000.0).round();
label = _buildShiftingLabel();
break;
}
return Expanded(
....
children: <Widget>[
_buildIcon(),
label,
],
),
),
Semantics(
label: indexLabel,
}
Copy the code
To improve the implementation
_controllers[Widget.currentIndex]. Value = 1.0 in initState of bottomNavigationBarState CurrentIndex defaults to 0, so the first icon is a little bit bigger. This problem also has the technique that compares chicken thief can deal with (magic change source code what ~), but so we all feel wrong. My colleague frowned and made a bold decision, instead of using the system component BottomNavigationBar, encapsulate it by himself:
// SafeArea is compatible with iPhone X, android and iOS shadows are different, so distinguish between them.
Widget _buildBottomNavigationBar() {
return SafeArea(
child: SizedBox(
height: 50.0,
child: Card(
color: Platform.isIOS ? Colors.transparent : Colors.white,
elevation: Platform.isIOS ? 0.0 : 8.0.// iPhone has no shadow
shape: RoundedRectangleBorder(),
margin: EdgeInsets.all(0.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Divider(),
Expanded(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
_buildBottomItem(
image: HImages.home, text: 'home', index: 0),
_buildBottomItem(
image: HImages.stats, text: 'data', index: 1),
_buildBottomItem(
image: HImages.mine, text: 'I', index: 3))))))); }// Wrap BottomItem with the color set to primaryColor and grey unchecked. Click on the ripple effect InkResponse
Widget _buildBottomItem({String image, String text, int index}) {
Color color =
currentIndex == index ? Theme.of(context).primaryColor : Colors.grey;
return Expanded(
child: InkResponse(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Image.asset(image, color: color, width: 22.0, height: 22.0),
Text(text, style: TextStyle(color: color, fontSize: 10.0))
]),
onTap: () => setState(() => currentIndex = index)));
}
Copy the code
Is this the final version? *
Naive, even iPhone X is considered, but the details of gradient color, platform feature support is not yet…At the beginning of next year, the Journey to the West co-production between China and the United States will be officially started. I will continue to play the Monkey King Sun Wukong. I will try to create a positive image with the artistic image of the Monkey King, so as to blossom both literary styles and promote Chinese cultureI hope you will pay more attention to it.
Some of the harvest
- Component animation implementation can refer to BottomNavigationBar, specification,
- Text animations can be implemented using Matrix4 and Vector3, which are more advanced (this is used in TabBar),
- Consider making an issue with the official.
In this paper, the source address: https://github.com/hyjfine/flutter-play
(after)
@ Zi Luyu, the copyright of this article belongs to Zaihui RESEARCH and development team, welcome to reprint, please reserve the source.