diff --git a/docs/components/bottomTabBar/BrnBottomTabBar/BrnBottomTabBar.md b/docs/components/bottomTabBar/BrnBottomTabBar/BrnBottomTabBar.md index 3033aeeb..7225d841 100644 --- a/docs/components/bottomTabBar/BrnBottomTabBar/BrnBottomTabBar.md +++ b/docs/components/bottomTabBar/BrnBottomTabBar/BrnBottomTabBar.md @@ -81,11 +81,15 @@ const BrnBottomTabBarItem({ | icon | Widget | 未选中时的icon | 是 | 无 | | activeIcon | Widget | 选中时的icon | 否 | 无 | | title | Widget? | Tab标题名调 | 否 | 无 | +| selectedTextStyle | TextStyle? | tab 选中文本样式 | 否 | 否 | +| unSelectedTextStyle | TextStyle? | tab 未选中文本样式 | 否 | 否 | | backgroundColor | Color? | 背景色 | 否 | 无 | | badge | Widget? | 未读信息 | 否 | 无 | | badgeNo | String? | 未读信息个数 | 否 | 无 | | maxBadgeNo | int | 未读消息最大个数 | 否 | 99 | + + ## 四、代码演示 ### 效果1 diff --git a/example/lib/sample/components/bottom_tabbar/bottom_tabbar_example.dart b/example/lib/sample/components/bottom_tabbar/bottom_tabbar_example.dart index d916d9a3..764c314d 100644 --- a/example/lib/sample/components/bottom_tabbar/bottom_tabbar_example.dart +++ b/example/lib/sample/components/bottom_tabbar/bottom_tabbar_example.dart @@ -1,5 +1,3 @@ - - import 'package:bruno/bruno.dart'; import 'package:flutter/material.dart'; @@ -25,6 +23,9 @@ class BottomTabbarExampleState extends State /// test3 int _selectedIndexTest3 = 0; + /// test4 + int _selectedIndexTest4 = 0; + /// 未读消息数量 String badgeNo1 = '100'; @@ -76,6 +77,14 @@ class BottomTabbarExampleState extends State }); } + /// test4 + void _onItemSelectedTest4(int index) { + setState(() { + /// 置为当前选中item的索引值 + _selectedIndexTest4 = index; + }); + } + @override Widget build(BuildContext context) { return MaterialApp( @@ -98,32 +107,47 @@ class BottomTabbarExampleState extends State items: [ // 定义每个BottomTabBarItem,子属性请看源码 BrnBottomTabBarItem( - icon: - Image(image: AssetImage("assets/icons/navbar_house.png")), - activeIcon: - Image(image: AssetImage("assets/icons/navbar_house.png")), + icon: Image(image: AssetImage("assets/icons/navbar_house.png")), + activeIcon: Image( + image: AssetImage("assets/icons/navbar_house.png"), + color: Colors.blue, + ), title: Text(titles[0])), BrnBottomTabBarItem( - icon: - Image(image: AssetImage("assets/icons/navbar_house.png")), + icon: Image(image: AssetImage("assets/icons/navbar_house.png")), + activeIcon: Image( + image: AssetImage("assets/icons/navbar_house.png"), + color: Colors.blue, + ), title: Text(titles[1])), BrnBottomTabBarItem( - icon: - Image(image: AssetImage("assets/icons/navbar_house.png")), + icon: Image(image: AssetImage("assets/icons/navbar_house.png")), + activeIcon: Image( + image: AssetImage("assets/icons/navbar_house.png"), + color: Colors.blue, + ), title: Text(titles[2])), BrnBottomTabBarItem( - icon: - Image(image: AssetImage("assets/icons/navbar_house.png")), - activeIcon: - Image(image: AssetImage("assets/icons/navbar_house.png")), + icon: Image(image: AssetImage("assets/icons/navbar_house.png")), + activeIcon: Image( + image: AssetImage("assets/icons/navbar_house.png"), + color: Colors.blue, + ), title: Text(titles[3])), BrnBottomTabBarItem( - icon: - Image(image: AssetImage("assets/icons/navbar_house.png")), - title: Text(titles[4])), + icon: Image(image: AssetImage("assets/icons/navbar_house.png")), + activeIcon: Image( + image: AssetImage("assets/icons/navbar_house.png"), + color: Colors.blue, + ), + title: Text(titles[4]), + ), BrnBottomTabBarItem( - icon: - Image(image: AssetImage("assets/icons/navbar_house.png")), + icon: Image(image: AssetImage("assets/icons/navbar_house.png")), + activeIcon: Image( + image: AssetImage("assets/icons/navbar_house.png"), + color: Colors.blue, + ), title: Text(titles[5])), ], ), @@ -138,66 +162,69 @@ class BottomTabbarExampleState extends State currentIndex: _selectedIndexTest1, onTap: _onItemSelectedTest1, items: [ - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text(titles[0])), + BrnBottomTabBarItem(icon: Icon(icons[0]), title: Text(titles[0])), ], ), Padding( padding: const EdgeInsets.all(12.0), - child: Text('极限条件测试2,有8个item'), + child: Text('有 2 个 item'), ), BrnBottomTabBar( fixedColor: Colors.blue, currentIndex: _selectedIndexTest2, onTap: _onItemSelectedTest2, items: [ - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text(titles[0])), - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text(titles[0])), - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text(titles[0])), - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text(titles[0])), - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text(titles[0])), - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text(titles[0])), - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text(titles[0])), - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text(titles[0])), + BrnBottomTabBarItem(icon: Icon(icons[0]), title: Text(titles[0])), + BrnBottomTabBarItem(icon: Icon(icons[0]), title: Text(titles[0])), ], ), Padding( padding: const EdgeInsets.all(12.0), - child: Text('极限条件测试3,text比较长的情况'), + child: Text('极限条件测试2,有8个item'), ), BrnBottomTabBar( fixedColor: Colors.blue, currentIndex: _selectedIndexTest3, onTap: _onItemSelectedTest3, + items: _getTabBarItems(count: 8), + ), + Padding( + padding: const EdgeInsets.all(12.0), + child: Text('极限条件测试3,text比较长的情况'), + ), + BrnBottomTabBar( + fixedColor: Colors.blue, + currentIndex: _selectedIndexTest4, + onTap: _onItemSelectedTest4, items: [ - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text("1111111111")), - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text("2222222222")), - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text("3333333333")), - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text("4444444444")), - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text("5555555555")), - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text("6666666666")), - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text("7777777777")), - BrnBottomTabBarItem( - icon: Icon(icons[0]), title: Text("8888888888")), + BrnBottomTabBarItem(icon: Icon(icons[0]), title: Text("1111111111")), + BrnBottomTabBarItem(icon: Icon(icons[0]), title: Text("2222222222")), + BrnBottomTabBarItem(icon: Icon(icons[0]), title: Text("3333333333")), + BrnBottomTabBarItem(icon: Icon(icons[0]), title: Text("4444444444")), + BrnBottomTabBarItem(icon: Icon(icons[0]), title: Text("5555555555")), + BrnBottomTabBarItem(icon: Icon(icons[0]), title: Text("6666666666")), + BrnBottomTabBarItem(icon: Icon(icons[0]), title: Text("7777777777")), + BrnBottomTabBarItem(icon: Icon(icons[0]), title: Text("8888888888")), ], ), ], )), ); } + + List _getTabBarItems({int count = 1}) { + return List.generate( + count, + (index) => BrnBottomTabBarItem( + icon: Icon( + icons[0], + color: Colors.grey, + ), + title: Text(titles[0]), + activeIcon: Icon( + icons[0], + color: Colors.blue, + ), + )); + } } diff --git a/example/lib/sample/components/navbar/nav_bar_example_page.dart b/example/lib/sample/components/navbar/nav_bar_example_page.dart index 57a696d8..a1f950fd 100644 --- a/example/lib/sample/components/navbar/nav_bar_example_page.dart +++ b/example/lib/sample/components/navbar/nav_bar_example_page.dart @@ -245,7 +245,11 @@ class _NavBarPageState extends State with TickerProviderStateMixin { key: actionKey, iconPressed: () { BrnPopupListWindow.showPopListWindow(context, actionKey, - offset: 10, data: ["aaaa", "bbbbb"]); + offset: 10, data: ["aaaa", "bbbbb"], onItemClick: (index, item){ + BrnDialogManager.showConfirmDialog(context, cancel: 'cancel', confirm: 'confirm', message: 'message'); + }, onDismiss: (){ + BrnToast.show('onDismiss', context); + }); }, ), ); @@ -510,6 +514,12 @@ class _NavBarPageState extends State with TickerProviderStateMixin { context, keyLeading, data: ["aaaa", "bbbbb"], + onItemClick: (index, data) { + BrnDialogManager.showConfirmDialog(context, cancel: 'cancel', confirm: 'confirm', message: 'message'); + }, + onDismiss: (){ + BrnToast.show('onDismiss', context); + }, ); }, //输入框 文本内容变化的监听 @@ -568,7 +578,12 @@ class _NavBarPageState extends State with TickerProviderStateMixin { leadClickCallback: (controller, update) { //controller 是文本控制器,通过controller 可以拿到输入的内容 以及 对输入的内容更改 //update 是setState方法的方法命,update() 就可以刷新输入框 - BrnPopupListWindow.showPopListWindow(context, keyLeading, data: ["aaaa", "bbbbb"], offset: 10); + BrnPopupListWindow.showPopListWindow( + context, + keyLeading, + data: ["aaaa", "bbbbb"], + offset: 10 + ); }, //输入框 文本内容变化的监听 searchBarInputChangeCallback: (input) { diff --git a/example/web/assets/FontManifest.json b/example/web/assets/FontManifest.json new file mode 100644 index 00000000..ac26bd3f --- /dev/null +++ b/example/web/assets/FontManifest.json @@ -0,0 +1,10 @@ +[ + { + "family": "MaterialIcons", + "fonts": [ + { + "asset": "fonts/MaterialIcons-Regular.otf" + } + ] + } +] \ No newline at end of file diff --git a/example/web/assets/fonts/MaterialIcons-Regular.otf b/example/web/assets/fonts/MaterialIcons-Regular.otf new file mode 100644 index 00000000..3246ad55 Binary files /dev/null and b/example/web/assets/fonts/MaterialIcons-Regular.otf differ diff --git a/example/web/favicon.png b/example/web/favicon.png new file mode 100644 index 00000000..00158cec Binary files /dev/null and b/example/web/favicon.png differ diff --git a/example/web/icons/icon-192.png b/example/web/icons/icon-192.png new file mode 100644 index 00000000..f1d902f4 Binary files /dev/null and b/example/web/icons/icon-192.png differ diff --git a/example/web/icons/icon-512.png b/example/web/icons/icon-512.png new file mode 100644 index 00000000..624294c2 Binary files /dev/null and b/example/web/icons/icon-512.png differ diff --git a/example/web/index.html b/example/web/index.html new file mode 100644 index 00000000..f32bf8da --- /dev/null +++ b/example/web/index.html @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/example/web/main.dart b/example/web/main.dart new file mode 100644 index 00000000..002f07d0 --- /dev/null +++ b/example/web/main.dart @@ -0,0 +1,7 @@ + +// TODO: change `my_app` to refer to your app package name. +import 'package:example/main.dart' as app; + +main() async { + app.main(); +} \ No newline at end of file diff --git a/example/web/manifest.json b/example/web/manifest.json new file mode 100644 index 00000000..e10777ee --- /dev/null +++ b/example/web/manifest.json @@ -0,0 +1,23 @@ +{ + "name": "Bruno Demo", + "short_name": "Bruno", + "start_url": ".", + "display": "Bruno", + "background_color": "#FFFFFFFF", + "theme_color": "#0175C2", + "description": "Bruno 是基于一整套设计体系的 Flutter 组件库。An enterprise-class package of Flutter components for mobile applications.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + } + ] +} diff --git a/lib/src/components/button/collection/brn_button_panel.dart b/lib/src/components/button/collection/brn_button_panel.dart index 5e26e4e2..3650f2d0 100644 --- a/lib/src/components/button/collection/brn_button_panel.dart +++ b/lib/src/components/button/collection/brn_button_panel.dart @@ -202,17 +202,18 @@ class _BrnButtonPanelState extends State { fontSize: 16)); }, popDirection: widget.popDirection, - onItemClick: (index, item) { + onItemClickInterceptor: (index, item) { // 按钮不可用的时候,点击无响应; + if (_secondaryButtonList[index + 2].isEnable) { + return false; + } else { + return true; + } + }, + onItemClick: (index, item) { if (widget.secondaryButtonOnTap != null) { - if (_secondaryButtonList[index + 2].isEnable) { - widget.secondaryButtonOnTap!(index + 2); - return false; - } else { - return true; - } + widget.secondaryButtonOnTap!(index + 2); } - return false; }); }, ); diff --git a/lib/src/components/popup/brn_popup_window.dart b/lib/src/components/popup/brn_popup_window.dart index 3ae42acb..cd0d94bc 100644 --- a/lib/src/components/popup/brn_popup_window.dart +++ b/lib/src/components/popup/brn_popup_window.dart @@ -66,9 +66,6 @@ class BrnPopupWindow extends StatefulWidget { /// 箭头图标水平方向的绝对偏移量,为 null 时则自动计算 final double? arrowOffset; - /// popUpWindow 消失回调,此回调会在 pop 之后执行 - final VoidCallback? onDismiss; - /// popWindow 距离底部的距离小于此值的时候, /// 自动将 popWindow 在 targetView 上面弹出 final double turnOverFromBottom; @@ -91,7 +88,6 @@ class BrnPopupWindow extends StatefulWidget { this.canWrap = false, this.spaceMargin = 20, this.arrowOffset, - this.onDismiss, this.turnOverFromBottom = 50.0}) : super(key: key); @@ -157,7 +153,6 @@ class BrnPopupWindow extends StatefulWidget { canWrap: canWrap, spaceMargin: spaceMargin, arrowOffset: arrowOffset, - onDismiss: dismissCallback, turnOverFromBottom: turnOverFromBottom, ))); } @@ -254,9 +249,6 @@ class _BrnPopupWindowState extends State { behavior: HitTestBehavior.translucent, onTap: () { Navigator.pop(context); - if (widget.onDismiss != null) { - widget.onDismiss!(); - } }, child: Material( color: Colors.transparent, @@ -270,9 +262,6 @@ class _BrnPopupWindowState extends State { ), ), onWillPop: () { - if (widget.onDismiss != null) { - widget.onDismiss!(); - } return Future.value(true); }), ); @@ -469,7 +458,13 @@ class BrnPopupRoute extends PopupRoute { /// popup 中每个 Item 被点击时的回调, /// [index] Item 的索引 /// [item] Item 内容 -typedef BrnPopupListItemClick = Function(int index, String item); +typedef BrnPopupListItemClick = void Function(int index, String item); + +/// popup 中每个 Item 被点击时,是否拦截点击事件 +/// [index] Item 的索引 +/// [item] Item 内容 +/// 返回 true 则拦截点击事件,不再回调 [onItemClick]。 +typedef BrnPopupListItemClickInterceptor = bool Function(int index, String item); /// popup 用于构造自定义的 Item /// [index] Item 的索引 @@ -484,17 +479,20 @@ class BrnPopupListWindow { /// [popDirection] 箭头的方向 /// [itemBuilder] 自定义 item 构造方法 /// [onItemClick] item 点击回调 - static void showButtonPanelPopList(context, GlobalKey popKey, - {List? data, - BrnPopupDirection popDirection = BrnPopupDirection.bottom, - BrnPopupListItemBuilder? itemBuilder, - BrnPopupListItemClick? onItemClick}) { + /// [onItemClickInterceptor] item 点击拦截回调 + /// [onDismiss] popUpWindow消失回调 + static void showButtonPanelPopList( + context, + GlobalKey popKey, { + List? data, + BrnPopupDirection popDirection = BrnPopupDirection.bottom, + BrnPopupListItemBuilder? itemBuilder, + BrnPopupListItemClick? onItemClick, + BrnPopupListItemClickInterceptor? onItemClickInterceptor, + VoidCallback? onDismiss, + }) { TextStyle textStyle = TextStyle( - color: BrnThemeConfigurator.instance - .getConfig() - .commonConfig - .colorTextBase, - fontSize: 16); + color: BrnThemeConfigurator.instance.getConfig().commonConfig.colorTextBase, fontSize: 16); double arrowHeight = 6.0; Color borderColor = Color(0xffCCCCCC); Color backgroundColor = Colors.white; @@ -505,10 +503,8 @@ class BrnPopupListWindow { double maxHeight = 200; double borderRadius = 4; bool hasCloseIcon = true; - assert(popKey.currentContext != null && - popKey.currentContext!.findRenderObject() != null); - if (popKey.currentContext == null || - popKey.currentContext!.findRenderObject() == null) return; + assert(popKey.currentContext != null && popKey.currentContext!.findRenderObject() != null); + if (popKey.currentContext == null || popKey.currentContext!.findRenderObject() == null) return; Navigator.push( context, BrnPopupRoute( @@ -522,18 +518,23 @@ class BrnPopupListWindow { offset: offset, widget: BrunoTools.isEmpty(data) ? Container( - constraints: - BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight), + constraints: BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight), ) : Container( - constraints: - BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight), + constraints: BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight), child: SingleChildScrollView( child: Container( padding: EdgeInsets.only(top: 6, bottom: 6), child: Column( - children: _getItems(context, minWidth, maxWidth, - itemBuilder, textStyle, data!, onItemClick, null), + children: + _getItems(context, minWidth, maxWidth, itemBuilder, textStyle, data!, + (index, item) { + if (onItemClickInterceptor != null) { + bool isIntercept = onItemClickInterceptor(index, item); + if (isIntercept) return; + } + Navigator.pop(context, {'index': index, 'item': item}); + }), ), ), ), @@ -542,7 +543,14 @@ class BrnPopupListWindow { borderRadius: borderRadius, borderColor: borderColor, spaceMargin: spaceMargin, - ))); + ))).then((result) { + if (onItemClick != null && result != null) { + onItemClick(result['index'], result['item']); + } + if (onDismiss != null) { + onDismiss(); + } + }); } /// 显示Popup List Window @@ -551,17 +559,17 @@ class BrnPopupListWindow { /// [popDirection] 箭头的方向 /// [offset] 距离targetView偏移量 /// [onItemClick] item 点击回调 + /// [onItemClickInterceptor] item 点击拦截回调 /// [onDismiss] popUpWindow消失回调 static void showPopListWindow(context, GlobalKey popKey, {List? data, BrnPopupDirection popDirection = BrnPopupDirection.bottom, double offset = 0, BrnPopupListItemClick? onItemClick, + BrnPopupListItemClickInterceptor? onItemClickInterceptor, VoidCallback? onDismiss}) { - assert(popKey.currentContext != null && - popKey.currentContext!.findRenderObject() != null); - if (popKey.currentContext == null || - popKey.currentContext!.findRenderObject() == null) return; + assert(popKey.currentContext != null && popKey.currentContext!.findRenderObject() != null); + if (popKey.currentContext == null || popKey.currentContext!.findRenderObject() == null) return; double arrowHeight = 6.0; double borderRadius = 4; @@ -570,21 +578,16 @@ class BrnPopupListWindow { double maxWidth = 150; double maxHeight = 200; double? arrowOffset; - Color borderColor = - BrnThemeConfigurator.instance.getConfig().commonConfig.dividerColorBase; + Color borderColor = BrnThemeConfigurator.instance.getConfig().commonConfig.dividerColorBase; Color backgroundColor = Colors.white; TextStyle textStyle = TextStyle( - color: BrnThemeConfigurator.instance - .getConfig() - .commonConfig - .colorTextBase, - fontSize: 14); + color: BrnThemeConfigurator.instance.getConfig().commonConfig.colorTextBase, fontSize: 14); bool hasCloseIcon = true; Navigator.push( - context, - BrnPopupRoute( - child: BrnPopupWindow( + context, + BrnPopupRoute( + child: BrnPopupWindow( context, arrowHeight: arrowHeight, popKey: popKey, @@ -595,18 +598,22 @@ class BrnPopupListWindow { offset: offset, widget: BrunoTools.isEmpty(data) ? Container( - constraints: - BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight), + constraints: BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight), ) : Container( - constraints: - BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight), + constraints: BoxConstraints(maxWidth: maxWidth, maxHeight: maxHeight), child: SingleChildScrollView( child: Container( padding: EdgeInsets.only(top: 6, bottom: 6), child: Column( - children: _getItems(context, minWidth, maxWidth, null, - textStyle, data!, onItemClick, onDismiss), + children: _getItems(context, minWidth, maxWidth, null, textStyle, data!, + (index, item) { + if (onItemClickInterceptor != null) { + bool isIntercept = onItemClickInterceptor(index, item); + if (isIntercept) return; + } + Navigator.pop(context, {'index': index, 'item': item}); + }), ), ), ), @@ -615,8 +622,16 @@ class BrnPopupListWindow { borderRadius: borderRadius, borderColor: borderColor, spaceMargin: spaceMargin, - onDismiss: onDismiss, - ))); + ), + ), + ).then((result) { + if (onItemClick != null && result != null) { + onItemClick(result['index'], result['item']); + } + if (onDismiss != null) { + onDismiss(); + } + }); } static List _getItems( @@ -626,8 +641,7 @@ class BrnPopupListWindow { BrnPopupListItemBuilder? itemBuilder, TextStyle textStyle, List data, - BrnPopupListItemClick? onItemClick, - VoidCallback? onDismiss) { + BrnPopupListItemClick onItemClick) { double textMaxWidth = _getMaxWidth(textStyle, data); if (textMaxWidth + 52 < minWidth) { textMaxWidth = minWidth; @@ -639,14 +653,7 @@ class BrnPopupListWindow { return data.map((f) { return GestureDetector( onTap: () { - if (onItemClick != null) { - dynamic isIntercept = onItemClick(data.indexOf(f), f); - if ((isIntercept is bool) && isIntercept) return; - } - Navigator.pop(context); - if (onDismiss != null) { - onDismiss(); - } + onItemClick(data.indexOf(f), f); }, child: Container( width: textMaxWidth, diff --git a/lib/src/components/tabbar/bottom/brn_bottom_tab_bar_item.dart b/lib/src/components/tabbar/bottom/brn_bottom_tab_bar_item.dart index 98d6ebd1..2ce74ff9 100644 --- a/lib/src/components/tabbar/bottom/brn_bottom_tab_bar_item.dart +++ b/lib/src/components/tabbar/bottom/brn_bottom_tab_bar_item.dart @@ -5,23 +5,31 @@ import 'package:flutter/material.dart'; /// 特别注意:Tab的右上角小红点可能不符合UI规范,可以使用BrnBadge小红点组件 class BrnBottomTabBarItem { const BrnBottomTabBarItem({ - required this.icon, this.title, + required this.icon, Widget? activeIcon, + this.selectedTextStyle, + this.unSelectedTextStyle, this.backgroundColor, this.badge, this.badgeNo, this.maxBadgeNo = 99, }) : activeIcon = activeIcon ?? icon; + /// Tab标题名 + final Widget? title; + /// 未选中时的icon final Widget icon; /// 选中时的icon final Widget activeIcon; - /// Tab标题名 - final Widget? title; + /// tab 选中文本样式 + final TextStyle? selectedTextStyle; + + /// tab 未选中文本样式 + final TextStyle? unSelectedTextStyle; /// 背景色 final Color? backgroundColor; diff --git a/lib/src/components/tabbar/bottom/brn_bottom_tab_bar_main.dart b/lib/src/components/tabbar/bottom/brn_bottom_tab_bar_main.dart index 2f4675e2..70776a9e 100644 --- a/lib/src/components/tabbar/bottom/brn_bottom_tab_bar_main.dart +++ b/lib/src/components/tabbar/bottom/brn_bottom_tab_bar_main.dart @@ -3,7 +3,9 @@ import 'dart:collection' show Queue; import 'dart:math' as math; +import 'package:bruno/bruno.dart'; import 'package:bruno/src/components/tabbar/bottom/brn_bottom_tab_bar_item.dart'; +import 'package:bruno/src/theme/configs/brn_all_config.dart'; import 'package:flutter/material.dart'; /// 定义一些UI常量,根据UI稿进行填写 @@ -79,246 +81,9 @@ class BrnBottomTabBar extends StatefulWidget { _BottomTabBarState createState() => _BottomTabBarState(); } -/// 表示底部导航栏中的单个tile,它的目的是进入一个伸缩页面 -class _BottomNavigationTile extends StatelessWidget { - const _BottomNavigationTile( - this.type, - this.item, - this.animation, - this.iconSize, { - this.onTap, - this.colorTween, - this.flex, - this.selected = false, - this.indexLabel, - this.isAnimation = true, - this.isInkResponse = true, - this.badgeColor, - }); - - final BrnBottomTabBarDisplayType type; - final BrnBottomTabBarItem item; - final Animation animation; - final double iconSize; - final VoidCallback? onTap; - final ColorTween? colorTween; - final double? flex; - final bool selected; - final String? indexLabel; - final bool isAnimation; - final bool isInkResponse; - final Color? badgeColor; - - /// 构建icon - Widget _buildIcon() { - double? tweenStart; - Color? iconColor; - switch (type) { - case BrnBottomTabBarDisplayType.fixed: - tweenStart = 8.0; - iconColor = colorTween?.evaluate(animation); - break; - case BrnBottomTabBarDisplayType.shifting: - tweenStart = 16.0; - iconColor = Colors.blue; - break; - } - return Align( - alignment: Alignment.topCenter, - heightFactor: 1.0, - child: Container( - margin: EdgeInsets.only( - top: isAnimation - ? Tween( - begin: tweenStart, - end: _kTopMargin, - ).evaluate(animation) - : _kTopMargin, - ), - child: IconTheme( - data: IconThemeData( - color: iconColor, - size: iconSize, - ), - child: selected ? item.activeIcon : item.icon, - ), - ), - ); - } - - /// 构建固定Label - /// 修改icon与text间距在这里修改 - Widget _buildFixedLabel() { - double scale = isAnimation - ? Tween( - begin: _kInactiveFontSize / _kActiveFontSize, - end: 1.0, - ).evaluate(animation) - : 1.0; - return Align( - alignment: Alignment.bottomCenter, - heightFactor: 1.0, - child: Container( - margin: const EdgeInsets.only( - bottom: _kBottomMargin, top: _kMiddleInterval), - child: DefaultTextStyle.merge( - style: TextStyle( - fontSize: _kActiveFontSize, - color: colorTween?.evaluate(animation), - ), - - /// 使用矩阵变化控制字体大小 - child: Transform( - transform: Matrix4.diagonal3Values( - scale, - scale, - scale, - ), - alignment: Alignment.bottomCenter, - child: item.title, - ), - ), - ), - ); - } - - /// 构建可变Label - Widget _buildShiftingLabel() { - return Align( - alignment: Alignment.bottomCenter, - heightFactor: 1.0, - child: Container( - margin: EdgeInsets.only( - bottom: Tween( - /// 在规范中,他们只是删除了非活动项目的标签,并指定了16dp的底部边距, - /// 我们不想移除标签因为我们想淡入淡出它,所以这修改了底部边距来考虑到这一点。 - begin: 2.0, - end: _kBottomMargin, - ).evaluate(animation), - ), - child: FadeTransition( - alwaysIncludeSemantics: true, - opacity: animation, - child: DefaultTextStyle.merge( - style: const TextStyle( - fontSize: _kActiveFontSize, - color: Colors.blue, - ), - child: item.title!, - ), - ), - ), - ); - } - - /// 构建未读消息弹窗 - Widget? _buildBadge() { - if (item.badge == null && (item.badgeNo == null || item.badgeNo!.isEmpty)) { - return Container(); - } - if (item.badge != null) { - return item.badge; - } - return Container( - width: 24, - padding: EdgeInsets.fromLTRB(0, 2, 0, 2), - alignment: Alignment.center, - decoration: BoxDecoration( - color: badgeColor, - shape: BoxShape.rectangle, - borderRadius: BorderRadius.all(Radius.circular(10))), - child: Text( - /// 设置未读数 > item.maxBadgeNo 则报加+ 默认 99 - _getUnReadText(), - style: TextStyle(fontSize: 10, color: Colors.white), - ), - ); - } - - String _getUnReadText() { - int _badgeNo = 0; - try { - if (item.badgeNo != null) { - _badgeNo = int.parse(item.badgeNo!); - } - } catch (e) { - debugPrint('badgeNo has FormatException'); - } - return '${_badgeNo > item.maxBadgeNo ? '${item.maxBadgeNo}+' : _badgeNo}'; - } - - /// 构建底字体缩放动画 - /// label: 传入的文字组件 - Widget _buildInkWidget(Widget? label) { - if (isInkResponse) { - return InkResponse( - onTap: onTap, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - _buildIcon(), - label!, - ], - ), - ); - } - return GestureDetector( - onTap: onTap, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - mainAxisSize: MainAxisSize.min, - children: [ - _buildIcon(), - label!, - ], - )); - } - - @override - Widget build(BuildContext context) { - /// 为了在动画过程中使用flex容器来增长平铺块,我们 - /// 需要将flex分配中的更改划分为更小的块 - /// 制作流畅的动画。我们通过将flex值相乘来实现这一点 - /// (这是一个整数)乘以一个大数。 - late int size; - Widget? label; - switch (type) { - case BrnBottomTabBarDisplayType.fixed: - size = 1; - label = _buildFixedLabel(); - break; - case BrnBottomTabBarDisplayType.shifting: - size = (flex! * 1000.0).round(); - label = _buildShiftingLabel(); - break; - } - - return Expanded( - flex: size, - child: Semantics( - container: true, - header: true, - selected: selected, - child: Stack( - children: [ - Positioned(right: 4, top: 4, child: _buildBadge()!), - _buildInkWidget(label), - Semantics( - label: indexLabel, - ) - ], - ), - ), - ); - } -} /// 底部导航栏中状态控制类 -class _BottomTabBarState extends State - with TickerProviderStateMixin { +class _BottomTabBarState extends State with TickerProviderStateMixin { List _controllers = []; late List _animations; @@ -582,6 +347,244 @@ class _BottomTabBarState extends State } } + +/// 表示底部导航栏中的单个tile,它的目的是进入一个伸缩页面 +class _BottomNavigationTile extends StatelessWidget { + + const _BottomNavigationTile( + this.type, + this.item, + this.animation, + this.iconSize, { + this.onTap, + this.colorTween, + this.flex, + this.selected = false, + this.indexLabel, + this.isAnimation = true, + this.isInkResponse = true, + this.badgeColor, + }); + + final BrnBottomTabBarDisplayType type; + final BrnBottomTabBarItem item; + final Animation animation; + final double iconSize; + final VoidCallback? onTap; + final ColorTween? colorTween; + final double? flex; + final bool selected; + final String? indexLabel; + final bool isAnimation; + final bool isInkResponse; + final Color? badgeColor; + + @override + Widget build(BuildContext context) { + /// 为了在动画过程中使用flex容器来增长平铺块,我们 + /// 需要将flex分配中的更改划分为更小的块 + /// 制作流畅的动画。我们通过将flex值相乘来实现这一点 + /// (这是一个整数)乘以一个大数。 + late int size; + Widget? label; + switch (type) { + case BrnBottomTabBarDisplayType.fixed: + size = 1; + label = _buildFixedLabel(); + break; + case BrnBottomTabBarDisplayType.shifting: + size = (flex! * 1000.0).round(); + label = _buildShiftingLabel(); + break; + } + + return Expanded( + flex: size, + child: Semantics( + container: true, + header: true, + selected: selected, + child: Stack( + children: [ + Positioned(right: 4, top: 4, child: _buildBadge()!), + _buildInkWidget(label), + Semantics( + label: indexLabel, + ) + ], + ), + ), + ); + } + + + /// 构建icon + Widget _buildIcon() { + double? tweenStart; + Color? iconColor; + switch (type) { + case BrnBottomTabBarDisplayType.fixed: + tweenStart = 8.0; + iconColor = colorTween?.evaluate(animation); + break; + case BrnBottomTabBarDisplayType.shifting: + tweenStart = 16.0; + iconColor = Colors.blue; + break; + } + return Align( + alignment: Alignment.topCenter, + heightFactor: 1.0, + child: Container( + margin: EdgeInsets.only( + top: isAnimation + ? Tween( + begin: tweenStart, + end: _kTopMargin, + ).evaluate(animation) + : _kTopMargin, + ), + child: IconTheme( + data: IconThemeData( + color: iconColor, + size: iconSize, + ), + child: selected ? item.activeIcon : item.icon, + ), + ), + ); + } + + /// 构建固定Label + /// 修改icon与text间距在这里修改 + Widget _buildFixedLabel() { + double scale = isAnimation + ? Tween( + begin: _kInactiveFontSize / _kActiveFontSize, + end: 1.0, + ).evaluate(animation) + : 1.0; + return Align( + alignment: Alignment.bottomCenter, + heightFactor: 1.0, + child: Container( + margin: const EdgeInsets.only( + bottom: _kBottomMargin, top: _kMiddleInterval), + child: DefaultTextStyle.merge( + style: TextStyle( + fontSize: _kActiveFontSize, + color: colorTween?.evaluate(animation), + ), + + /// 使用矩阵变化控制字体大小 + child: Transform( + transform: Matrix4.diagonal3Values( + scale, + scale, + scale, + ), + alignment: Alignment.bottomCenter, + child: item.title, + ), + ), + ), + ); + } + + /// 构建可变Label + Widget _buildShiftingLabel() { + return Align( + alignment: Alignment.bottomCenter, + heightFactor: 1.0, + child: Container( + margin: EdgeInsets.only( + bottom: Tween( + /// 在规范中,他们只是删除了非活动项目的标签,并指定了16dp的底部边距, + /// 我们不想移除标签因为我们想淡入淡出它,所以这修改了底部边距来考虑到这一点。 + begin: 2.0, + end: _kBottomMargin, + ).evaluate(animation), + ), + child: DefaultTextStyle.merge( + style: TextStyle( + fontSize: _kActiveFontSize, + color: selected ? BrnThemeConfigurator.instance.getConfig().commonConfig.brandPrimary + : BrnThemeConfigurator.instance.getConfig().commonConfig.colorTextBase, + ).merge(selected ? item.selectedTextStyle : item.unSelectedTextStyle), + child: item.title!, + ), + ), + ); + } + + /// 构建未读消息弹窗 + Widget? _buildBadge() { + if (item.badge == null && (item.badgeNo == null || item.badgeNo!.isEmpty)) { + return Container(); + } + if (item.badge != null) { + return item.badge; + } + return Container( + width: 24, + padding: EdgeInsets.fromLTRB(0, 2, 0, 2), + alignment: Alignment.center, + decoration: BoxDecoration( + color: badgeColor, + shape: BoxShape.rectangle, + borderRadius: BorderRadius.all(Radius.circular(10))), + child: Text( + /// 设置未读数 > item.maxBadgeNo 则报加+ 默认 99 + _getUnReadText(), + style: TextStyle(fontSize: 10, color: Colors.white), + ), + ); + } + + String _getUnReadText() { + int _badgeNo = 0; + try { + if (item.badgeNo != null) { + _badgeNo = int.parse(item.badgeNo!); + } + } catch (e) { + debugPrint('badgeNo has FormatException'); + } + return '${_badgeNo > item.maxBadgeNo ? '${item.maxBadgeNo}+' : _badgeNo}'; + } + + /// 构建底字体缩放动画 + /// label: 传入的文字组件 + Widget _buildInkWidget(Widget? label) { + if (isInkResponse) { + return InkResponse( + onTap: onTap, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + _buildIcon(), + label!, + ], + ), + ); + } + return GestureDetector( + onTap: onTap, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.min, + children: [ + _buildIcon(), + label!, + ], + )); + } +} + + /// 简介:TabBarItem点击飞溅动画私有类 /// 功能:实现点击飞溅动画 class _Circle { @@ -633,6 +636,7 @@ class _Circle { } } + /// 绘制动画色彩飞溅的圆圈 class _RadialPainter extends CustomPainter { _RadialPainter({