写点什么

【Flutter 专题】29 图解自定义底部状态栏 ACEBottomNavigationBar (一)

发布于: 刚刚
【Flutter 专题】29 图解自定义底部状态栏 ACEBottomNavigationBar (一)

      小菜刚接触 Flutter 时接触到底部状态栏 BottomNavigationBar 方便快捷,但随着使用过程发现依然有一些限制,包括图片选择/样式凸出/固定 NavigationItem 位等。小菜不才,准备照葫芦画瓢,自定义一个底部状态栏,并尝试封装成一个 Pub 插件。


      小菜首先了解了一下 BottomNavigationBar,主要由整体填充布局与子 NavigationItem,小菜也是这样设计的,但 BottomNavigationBar 设计的配置部分主要是在 BottomNavigationBar 中完成的,而 BottomNavigationBarItem 可以看作只是一个单纯的实体类,小菜认为这样设计的好处就是统一管理,减少冗余配置等;而小菜为了配置项更多更灵活选择在 NavigationItem 中进行配置判断,这样实现的缺点就是冗余项较多,小菜也会不断学习完善。

设计尝试

一:类型确定

      小菜尝试用枚举类型确定不同的样式,明确且方便,延展性也较好;


enum ACEBottomNavigationBarType {  normal,  // 普通类型,选中变色,样式不变  zoom,    // 图片或icon变大,此时隐藏文字,支持变色  zoomout, // 图片或icon变大,并凸出显示,文字显示,支持变色  zoomoutonlypic,  // 图片或icon变大,并凸出显示,文字隐藏}
复制代码



二:NavigationItem 搭建

      对于 NavigationItem 因为计划有凸出效果展示,整体用了 Stack 来搭建,配合 AnimatedAlign 等具体的组件来共同搭建,因为 Item 中各种状态均可根据用户定义的样式进行传参,故所有字段前均需 @required


class NavigationItem extends StatelessWidget {  final UniqueKey uniqueKey;  final textStr;  final textUnSelectedColor;  final textSelectedColor;  final icon;  final iconUnSelectedColor;  final iconSelectedColor;  final image;  final imageSelected;  final selected;  final ACEBottomNavigationBarType type;  final Function(UniqueKey uniqueKey) callbackFunction;
NavigationItem( {@required this.uniqueKey, @required this.selected, @required this.textStr, @required this.textSelectedColor, @required this.textUnSelectedColor, @required this.icon, @required this.iconSelectedColor, @required this.iconUnSelectedColor, @required this.image, @required this.imageSelected, @required this.callbackFunction, @required this.type});
@override Widget build(BuildContext context) { return Expanded( child: Stack(children: <Widget>[ Container( alignment: Alignment.bottomCenter, child: Opacity( opacity: textOption(), child: Padding( padding: const EdgeInsets.all(6.0), child: Text(textStr, overflow: TextOverflow.ellipsis, maxLines: 1, style: TextStyle( fontWeight: FontWeight.w600, color: selected ? textSelectedColor : textUnSelectedColor))))), Container( child: AnimatedAlign( duration: Duration(milliseconds: 0), alignment: picZoomAlignment(), child: childWid())) ])); }
double picSize() { var size; if (type == ACEBottomNavigationBarType.normal) { size = 30.0; } else { size = selected ? 50.0 : 30.0; } return size; }
double textOption() { var option; if (type == ACEBottomNavigationBarType.zoom || type == ACEBottomNavigationBarType.zoomoutonlypic) { option = selected ? 0.0 : 1.0; } else if (type == ACEBottomNavigationBarType.zoomout) { option = 1.0; } else { option = 1.0; } return option; }
EdgeInsetsGeometry imagePadding() { EdgeInsetsGeometry edge; if (type == ACEBottomNavigationBarType.zoom) { edge = selected ? EdgeInsets.only(top: 6.0, bottom: 6.0) : EdgeInsets.only(bottom: 20.0); } else if (type == ACEBottomNavigationBarType.zoomout || type == ACEBottomNavigationBarType.zoomoutonlypic) { edge = selected ? EdgeInsets.only(bottom: 0.0) : EdgeInsets.only(bottom: 20.0); } else if (type == ACEBottomNavigationBarType.normal) { edge = EdgeInsets.only(bottom: 20.0); } else { edge = EdgeInsets.only(bottom: 0.0); } return edge; }
Widget childWid() { Widget widget; if (image != null) { widget = GestureDetector( child: Padding( padding: imagePadding(), child: Image( image: (selected && imageSelected != null) ? imageSelected : image, width: picSize(), height: picSize())), onTap: () { callbackFunction(uniqueKey); }); } else { widget = IconButton( highlightColor: Colors.transparent, splashColor: Colors.transparent, padding: EdgeInsets.only(bottom: 24.0), alignment: Alignment(0, 0), icon: Icon(icon, size: picSize(), color: selected ? iconSelectedColor : iconUnSelectedColor), onPressed: () { callbackFunction(uniqueKey); }); } return widget; }}
复制代码



三:ACEBottomNavigationBar 框架搭建

      小菜自定义 ACEBottomNavigationBar 用来装载 Item 框架,若不设置单独 Item 时使用 ACEBottomNavigationBar 配置项,为公共效果,若两者同时设置,优先使用 NavigationItem 效果。


      为了实现切换时可以对应相应的 Tab 页,需要设置 item key


class ACEBottomNavigationBar extends StatefulWidget {  final Key key;  final List<NavigationItemBean> items;  final initSelectedIndex;  final bgColor;  final bgImage;  final Function(int position) onTabChangedListener;  final textStr;  final textUnSelectedColor;  final textSelectedColor;  final icon;  final iconUnSelectedColor;  final iconSelectedColor;  final image;  final imageSelected;  final ACEBottomNavigationBarType type;
ACEBottomNavigationBar( {@required this.items, @required this.onTabChangedListener, ACEBottomNavigationBarType type, this.key, this.initSelectedIndex = 0, this.textStr, this.textSelectedColor, this.textUnSelectedColor, this.icon, this.iconSelectedColor, this.iconUnSelectedColor, this.image, this.imageSelected, this.bgColor, this.bgImage}) : assert(onTabChangedListener != null), assert(items != null), assert(items.length >= 1 && items.length <= 5), type = type;
@override _ACEBottomNavigationBar createState() => _ACEBottomNavigationBar();}
class _ACEBottomNavigationBar extends State<ACEBottomNavigationBar> with TickerProviderStateMixin, RouteAware { var curSelectedIndex = 0; var textSelectedColor; var textUnSelectedColor; var iconSelectedColor; var iconUnSelectedColor;
@override void initState() { super.initState(); _setSelected(widget.items[widget.initSelectedIndex].key); }
_setSelected(UniqueKey key) { if (mounted) { setState(() { curSelectedIndex = widget.items.indexWhere((tabData) => tabData.key == key); }); } }
@override void didChangeDependencies() { super.didChangeDependencies();
textUnSelectedColor = (widget.textUnSelectedColor == null) ? (Theme.of(context).brightness == Brightness.dark) ? Colors.white : Colors.black54 : widget.textUnSelectedColor; textSelectedColor = (widget.textSelectedColor == null) ? (Theme.of(context).brightness == Brightness.dark) ? Colors.white : Colors.black87 : widget.textSelectedColor; iconUnSelectedColor = (widget.iconUnSelectedColor == null) ? (Theme.of(context).brightness == Brightness.dark) ? Colors.white : Colors.black54 : widget.iconUnSelectedColor; iconSelectedColor = (widget.iconSelectedColor == null) ? (Theme.of(context).brightness == Brightness.dark) ? Colors.white : Colors.black87 : widget.iconSelectedColor; }
@override Widget build(BuildContext context) { return Stack(alignment: Alignment.bottomCenter, children: <Widget>[ Container( height: 60.0, decoration: navigationBarBg(), child: Row( mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.center, children: widget.items .map((item) => NavigationItem( uniqueKey: item.key, selected: item.key == widget.items[curSelectedIndex].key, icon: item.icon, textStr: item.textStr, textSelectedColor: (item.textSelectedColor == null) ? this.textSelectedColor : item.textSelectedColor, textUnSelectedColor: (item.textUnSelectedColor == null) ? this.textUnSelectedColor : item.textUnSelectedColor, iconSelectedColor: (item.iconSelectedColor == null) ? this.iconSelectedColor : item.iconSelectedColor, iconUnSelectedColor: (item.iconUnSelectedColor == null) ? this.iconUnSelectedColor : item.iconUnSelectedColor, type: widget.type != null ? widget.type : ACEBottomNavigationBarType.normal, image: item.image, imageSelected: item.imageSelected, callbackFunction: (uniqueKey) { int selected = widget.items .indexWhere((tabData) => tabData.key == uniqueKey); widget.onTabChangedListener(selected); _setSelected(uniqueKey); })) .toList())) ]); }
BoxDecoration navigationBarBg() { return widget.bgImage != null ? BoxDecoration(boxShadow: [ BoxShadow( color: Colors.black12, offset: Offset(0, -1), blurRadius: 8) ], image: DecorationImage(fit: BoxFit.cover, image: widget.bgImage)) : BoxDecoration( color: widget.bgColor != null ? widget.bgColor : Colors.white, boxShadow: [ BoxShadow( color: Colors.black12, offset: Offset(0, -1), blurRadius: 8) ]); }}
复制代码



注意事项

  1. ACEBottomNavigationBarType 为状态栏样式,默认为 nomal 类型,支持文字和图片/icon 颜色切换;

  2. 小菜尝试时对图片设置成图片和 icon 两种,icon 类型支持颜色绘制,而图片支持选中和未选中两张图切换;同时如果设置图片和 icon 两种,优先使用图片样式;同时用户对于两张图样式时可以只设置一张未选中状态图;同时支持图片和 icon 两种方式共存;

  3. 小菜设计 NavigationItem 中传递 image 图片,是为了支持本地图/网络图/内存图等多种图片格式;

  4. ACEBottomNavigationBar 中可以设置背景图或背景色,优先使用背景图效果,且背景图支持本地图或网络图。




      小菜尝试过程中还有很多欠缺,下一步计划添加固定凸出 Item 位样式,并尝试发不成 Pub 插件,有不对的地方敬请指点!


      小菜对细节地方介绍较少,希望各位朋友优先尝试效果。


来源:阿策小和尚

发布于: 刚刚阅读数: 2
用户头像

还未添加个人签名 2021.05.13 加入

Android / Flutter 小菜鸟~

评论

发布
暂无评论
【Flutter 专题】29 图解自定义底部状态栏 ACEBottomNavigationBar (一)