diff --git a/lib/tab_item.dart b/lib/tab_item.dart index b4a0a9b21..0ce14f3b6 100644 --- a/lib/tab_item.dart +++ b/lib/tab_item.dart @@ -2,13 +2,14 @@ import 'package:flutter/material.dart'; class TabItem { final T icon; - final String? title; + final String? title, activeTitle; final Widget? count; final String? key; const TabItem({ required this.icon, this.title, + this.activeTitle, this.count, this.key, }) : assert(icon is IconData || icon is Widget, 'TabItem only support IconData and Widget'); diff --git a/lib/widgets/build_icon.dart b/lib/widgets/build_icon.dart index 69af4491c..9dbce201f 100644 --- a/lib/widgets/build_icon.dart +++ b/lib/widgets/build_icon.dart @@ -19,7 +19,7 @@ class BuildIcon extends StatelessWidget { @override Widget build(BuildContext context) { - Widget icon = Icon( + Widget icon = (item.icon is Widget) ? item.icon : Icon( item.icon, size: iconSize, color: iconColor, diff --git a/lib/widgets/inspired/inspired.dart b/lib/widgets/inspired/inspired.dart index 690bb5c4f..84e5ad520 100644 --- a/lib/widgets/inspired/inspired.dart +++ b/lib/widgets/inspired/inspired.dart @@ -14,17 +14,17 @@ * limitations under the License. */ +import 'dart:math' as math; import 'package:awesome_bottom_bar/count_style.dart'; import 'package:awesome_bottom_bar/tab_item.dart'; import 'package:awesome_bottom_bar/widgets/build_icon.dart'; import 'package:awesome_bottom_bar/widgets/hexagon/hexagon.dart'; +import 'package:flutter/material.dart'; import '../../chip_style.dart'; -import 'package:flutter/material.dart'; -import 'dart:math' as math; -import 'stack.dart' as extend; import 'painter.dart'; +import 'stack.dart' as extend; import 'transition_container.dart'; /// Default size of the curve line. @@ -63,6 +63,7 @@ class Inspired extends StatefulWidget { final Duration? duration; final String animateStyle; final List> items; + const Inspired({ Key? key, required this.background, @@ -114,12 +115,16 @@ class _InspiredState extends State with TickerProviderStateMixin { @override void initState() { count = widget.items.length; - if (widget.cornerRadius != null && widget.cornerRadius! > 0 && !widget.fixed) { + if (widget.cornerRadius != null && + widget.cornerRadius! > 0 && + !widget.fixed) { throw FlutterError.fromParts([ ErrorSummary('ConvexAppBar is configured with cornerRadius'), - ErrorDescription('Currently the corner only work for fixed style, if you are using ' + ErrorDescription( + 'Currently the corner only work for fixed style, if you are using ' 'other styles, the convex shape can be broken on the first and last tab item '), - ErrorHint('You should use TabStyle.fixed or TabStyle.fixedCircle to make the' + ErrorHint( + 'You should use TabStyle.fixed or TabStyle.fixedCircle to make the' ' background display with topLeft/topRight corner'), ]); } @@ -132,8 +137,9 @@ class _InspiredState extends State with TickerProviderStateMixin { _updateAnimation( from: from ?? _currentIndex, to: index, - duration: - widget.animated == true ? const Duration(milliseconds: _transitionDuration) : const Duration(microseconds: 0), + duration: widget.animated == true + ? const Duration(milliseconds: _transitionDuration) + : const Duration(microseconds: 0), ); // ignore: unawaited_futures _animationController?.forward(); @@ -152,7 +158,9 @@ class _InspiredState extends State with TickerProviderStateMixin { if (from != null && (from == to) && _animation != null) { return _animation!; } - from ??= widget.fixed ? widget.fixedIndex : _controller?.index ?? widget.initialActive ?? 0; + from ??= widget.fixed + ? widget.fixedIndex + : _controller?.index ?? widget.initialActive ?? 0; to ??= from; final lower = (2 * from! + 1) / (2 * count); final upper = (2 * to! + 1) / (2 * count); @@ -203,13 +211,19 @@ class _InspiredState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { // take care of iPhoneX' safe area at bottom edge - double additionalBottomPadding = math.max(MediaQuery.of(context).padding.bottom, 0.0); + double additionalBottomPadding = + math.max(MediaQuery.of(context).padding.bottom, 0.0); - int convexIndex = widget.fixed ? (widget.fixedIndex ?? (count ~/ 2)) : _currentIndex ?? 0; + int convexIndex = + widget.fixed ? (widget.fixedIndex ?? (count ~/ 2)) : _currentIndex ?? 0; bool active = widget.fixed ? convexIndex == _currentIndex : true; - double height = widget.height + additionalBottomPadding + widget.pad! + widget.padTop! + widget.padbottom!; + double height = widget.height + + additionalBottomPadding + + widget.pad! + + widget.padTop! + + widget.padbottom!; double width = MediaQuery.of(context).size.width; Animation percent = widget.fixed @@ -230,7 +244,8 @@ class _InspiredState extends State with TickerProviderStateMixin { bool convexBridge = widget.chipStyle?.convexBridge ?? false; - NotchSmoothness notchSmoothness = widget.chipStyle?.notchSmoothness ?? NotchSmoothness.defaultEdge; + NotchSmoothness notchSmoothness = + widget.chipStyle?.notchSmoothness ?? NotchSmoothness.defaultEdge; var offset = FractionalOffset(count > 1 ? dx : 0.0, 0); @@ -247,7 +262,8 @@ class _InspiredState extends State with TickerProviderStateMixin { width: widget.curveSize ?? converSize, height: 78, color: widget.background, - shadowColor: widget.shadowColor ?? const Color.fromRGBO(0, 0, 0, 0.06), + shadowColor: + widget.shadowColor ?? const Color.fromRGBO(0, 0, 0, 0.06), sigma: widget.elevation ?? 2, leftPercent: percent, textDirection: textDirection, @@ -257,31 +273,46 @@ class _InspiredState extends State with TickerProviderStateMixin { convexBridge: convexBridge, leftCornerRadius: widget.fixed && widget.fixedIndex == 0 ? 0 - : (widget.initialActive == 0 && !widget.fixed ? 0 : widget.radius!), + : (widget.initialActive == 0 && !widget.fixed + ? 0 + : widget.radius!), rightCornerRadius: widget.fixed && widget.fixedIndex == count - 1 ? 0 - : (widget.initialActive == count - 1 && !widget.fixed ? 0 : widget.radius!), + : (widget.initialActive == count - 1 && !widget.fixed + ? 0 + : widget.radius!), ), ), ), _barContent(height, additionalBottomPadding, convexIndex), - Positioned.fill( - top: (widget.top! - widget.pad! - widget.padTop! - widget.padbottom!), - bottom: additionalBottomPadding, - child: FractionallySizedBox( - widthFactor: factor, - alignment: offset, - child: GestureDetector( - key: ValueKey(widget.items[convexIndex].key ?? ''), - onTap: () => _onTabClick(convexIndex), - child: buildItem(context, item: widget.items[convexIndex], index: convexIndex, active: active), + if (isInItemRange(convexIndex)) + Positioned.fill( + top: (widget.top! - + widget.pad! - + widget.padTop! - + widget.padbottom!), + bottom: additionalBottomPadding, + child: FractionallySizedBox( + widthFactor: factor, + alignment: offset, + child: GestureDetector( + key: ValueKey(widget.items[convexIndex].key ?? ''), + onTap: () => _onTabClick(convexIndex), + child: buildItem(context, + item: widget.items[convexIndex], + index: convexIndex, + active: active), + ), ), ), - ), ], ); } + bool isInItemRange(int index) { + return index >= 0 && index < widget.items.length; + } + Widget _barContent(double height, double paddingBottom, int curveTabIndex) { var children = []; for (var i = 0; i < count; i++) { @@ -292,12 +323,14 @@ class _InspiredState extends State with TickerProviderStateMixin { } var active = _currentIndex == i; - children.add(Expanded( - child: GestureDetector( + children.add( + Expanded( + child: GestureDetector( key: ValueKey(value), - behavior: HitTestBehavior.opaque, + behavior: HitTestBehavior.opaque, onTap: () => _onTabClick(i), - child: buildItem(context, item: widget.items[i], index: i, active: active), + child: buildItem(context, + item: widget.items[i], index: i, active: active), ), ), ); @@ -313,7 +346,8 @@ class _InspiredState extends State with TickerProviderStateMixin { ); } - Widget buildItem(BuildContext context, {required TabItem item, required int index, bool active = false}) { + Widget buildItem(BuildContext context, + {required TabItem item, required int index, bool active = false}) { Color itemColor() { if (widget.fixed) { return active ? widget.chipStyle!.background! : widget.color; @@ -322,26 +356,52 @@ class _InspiredState extends State with TickerProviderStateMixin { } if (widget.fixed ? widget.fixedIndex == index : active) { - if (widget.animated) { - if (widget.animateStyle == 'flip') { - return TransitionContainer.flip( - data: index, - duration: widget.duration ?? const Duration(milliseconds: 350), - height: 80, - curve: widget.curve, - bottomChild: buildContentItem(item, itemColor(), widget.iconSize, widget.sizeInside!), - ); - } else { - return TransitionContainer.scale( - data: index, - duration: widget.duration ?? const Duration(milliseconds: 350), - curve: widget.curve, - child: buildContentItem(item, itemColor(), widget.iconSize, widget.sizeInside!), - ); - } + final content = Center( + child: Stack( + fit: StackFit.passthrough, + alignment: Alignment.center, + children: [ + buildContentItem( + item, itemColor(), widget.iconSize, widget.sizeInside!), + if (item.activeTitle is String && item.activeTitle != '') + Transform.translate( + offset: const Offset(0, 40), + child: Text( + item.activeTitle!, + style: Theme.of(context) + .textTheme + .labelSmall + ?.merge(widget.titleStyle) + .copyWith(color: widget.color), + textAlign: TextAlign.center, + ), + ), + ], + ), + ); + + if (!widget.animated) { + return content; + } + + if (widget.animateStyle == 'flip') { + return TransitionContainer.flip( + data: index, + duration: widget.duration ?? const Duration(milliseconds: 350), + height: 80, + curve: widget.curve, + bottomChild: content, + ); + } else { + return TransitionContainer.scale( + data: index, + duration: widget.duration ?? const Duration(milliseconds: 350), + curve: widget.curve, + child: content, + ); } - return buildContentItem(item, itemColor(), widget.iconSize, widget.sizeInside!); } + return Container( padding: EdgeInsets.only(bottom: widget.padbottom!, top: widget.padTop!), child: Column( @@ -358,7 +418,11 @@ class _InspiredState extends State with TickerProviderStateMixin { SizedBox(height: widget.pad), Text( item.title!, - style: Theme.of(context).textTheme.labelSmall?.merge(widget.titleStyle).copyWith(color: itemColor()), + style: Theme.of(context) + .textTheme + .labelSmall + ?.merge(widget.titleStyle) + .copyWith(color: itemColor()), textAlign: TextAlign.center, ) ], @@ -367,7 +431,8 @@ class _InspiredState extends State with TickerProviderStateMixin { ); } - Widget buildContentItem(TabItem item, Color itemColor, double iconSize, double sizeInside) { + Widget buildContentItem( + TabItem item, Color itemColor, double iconSize, double sizeInside) { return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, @@ -376,7 +441,8 @@ class _InspiredState extends State with TickerProviderStateMixin { Container( width: sizeInside, height: sizeInside, - decoration: BoxDecoration(color: widget.chipStyle?.background!, shape: BoxShape.circle), + decoration: BoxDecoration( + color: widget.chipStyle?.background!, shape: BoxShape.circle), alignment: Alignment.center, child: BuildIcon( item: item,