nav_bar.dart 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. library google_nav_bar;
  2. import 'package:flutter/material.dart';
  3. import 'package:flutter/services.dart';
  4. class GNav extends StatefulWidget {
  5. const GNav({
  6. Key? key,
  7. this.tabs,
  8. this.selectedIndex = 0,
  9. this.onTabChange,
  10. this.gap,
  11. this.padding,
  12. this.activeColor,
  13. this.color,
  14. this.rippleColor,
  15. this.hoverColor,
  16. this.backgroundColor,
  17. this.tabBackgroundColor,
  18. this.tabBorderRadius,
  19. this.iconSize,
  20. this.textStyle,
  21. this.curve,
  22. this.tabMargin,
  23. this.debug,
  24. this.duration,
  25. this.tabBorder,
  26. this.tabActiveBorder,
  27. this.tabShadow,
  28. this.haptic,
  29. this.tabBackgroundGradient,
  30. this.mainAxisAlignment = MainAxisAlignment.spaceBetween,
  31. }) : super(key: key);
  32. final List<GButton>? tabs;
  33. final int selectedIndex;
  34. final Function? onTabChange;
  35. final double? gap;
  36. final double? tabBorderRadius;
  37. final double? iconSize;
  38. final Color? activeColor;
  39. final Color? backgroundColor;
  40. final Color? tabBackgroundColor;
  41. final Color? color;
  42. final Color? rippleColor;
  43. final Color? hoverColor;
  44. final EdgeInsetsGeometry? padding;
  45. final EdgeInsetsGeometry? tabMargin;
  46. final TextStyle? textStyle;
  47. final Duration? duration;
  48. final Curve? curve;
  49. final bool? debug;
  50. final bool? haptic;
  51. final Border? tabBorder;
  52. final Border? tabActiveBorder;
  53. final List<BoxShadow>? tabShadow;
  54. final Gradient? tabBackgroundGradient;
  55. final MainAxisAlignment mainAxisAlignment;
  56. @override
  57. State<GNav> createState() => _GNavState();
  58. }
  59. class _GNavState extends State<GNav> {
  60. int? selectedIndex;
  61. bool clickable = true;
  62. @override
  63. void initState() {
  64. super.initState();
  65. }
  66. @override
  67. Widget build(BuildContext context) {
  68. debugPrint(
  69. '${(_GNavState).toString()} - build with index ${widget.selectedIndex}',
  70. );
  71. selectedIndex = widget.selectedIndex;
  72. return Container(
  73. color: widget.backgroundColor ?? Colors.transparent,
  74. child: Row(
  75. mainAxisAlignment: widget.mainAxisAlignment,
  76. children: widget.tabs!
  77. .map(
  78. (t) => GButton(
  79. key: t.key,
  80. border: t.border ?? widget.tabBorder,
  81. activeBorder: t.activeBorder ?? widget.tabActiveBorder,
  82. borderRadius:
  83. t.borderRadius as bool? ?? widget.tabBorderRadius != null
  84. ? BorderRadius.all(
  85. Radius.circular(widget.tabBorderRadius!),
  86. )
  87. : const BorderRadius.all(Radius.circular(100.0)),
  88. debug: widget.debug ?? false,
  89. margin: t.margin ?? widget.tabMargin,
  90. active: selectedIndex == widget.tabs!.indexOf(t),
  91. gap: t.gap ?? widget.gap,
  92. iconActiveColor: t.iconActiveColor ?? widget.activeColor,
  93. iconColor: t.iconColor ?? widget.color,
  94. iconSize: t.iconSize ?? widget.iconSize,
  95. textColor: t.textColor ?? widget.activeColor,
  96. rippleColor:
  97. t.rippleColor ?? widget.rippleColor ?? Colors.transparent,
  98. hoverColor:
  99. t.hoverColor ?? widget.hoverColor ?? Colors.transparent,
  100. padding: t.padding ?? widget.padding,
  101. icon: t.icon,
  102. haptic: widget.haptic ?? true,
  103. leading: t.leading,
  104. curve: widget.curve ?? Curves.easeInCubic,
  105. backgroundGradient:
  106. t.backgroundGradient ?? widget.tabBackgroundGradient,
  107. backgroundColor: t.backgroundColor ??
  108. widget.tabBackgroundColor ??
  109. Colors.transparent,
  110. duration: widget.duration ?? const Duration(milliseconds: 500),
  111. onPressed: () {
  112. widget.onTabChange!(widget.tabs!.indexOf(t));
  113. },
  114. ),
  115. )
  116. .toList(),
  117. ),
  118. );
  119. }
  120. }
  121. class GButton extends StatefulWidget {
  122. final bool? active;
  123. final bool? debug;
  124. final bool? haptic;
  125. final double? gap;
  126. final Color? iconColor;
  127. final Color? rippleColor;
  128. final Color? hoverColor;
  129. final Color? iconActiveColor;
  130. final Color? textColor;
  131. final EdgeInsetsGeometry? padding;
  132. final EdgeInsetsGeometry? margin;
  133. final TextStyle? textStyle;
  134. final double? iconSize;
  135. final Function? onPressed;
  136. final String text;
  137. final IconData? icon;
  138. final Color? backgroundColor;
  139. final Duration? duration;
  140. final Curve? curve;
  141. final Gradient? backgroundGradient;
  142. final Widget? leading;
  143. final BorderRadius? borderRadius;
  144. final Border? border;
  145. final Border? activeBorder;
  146. final List<BoxShadow>? shadow;
  147. final String? semanticLabel;
  148. const GButton({
  149. Key? key,
  150. this.active,
  151. this.haptic,
  152. this.backgroundColor,
  153. this.icon,
  154. this.iconColor,
  155. this.rippleColor,
  156. this.hoverColor,
  157. this.iconActiveColor,
  158. this.text = '',
  159. this.textColor,
  160. this.padding,
  161. this.margin,
  162. this.duration,
  163. this.debug,
  164. this.gap,
  165. this.curve,
  166. this.textStyle,
  167. this.iconSize,
  168. this.leading,
  169. this.onPressed,
  170. this.backgroundGradient,
  171. this.borderRadius,
  172. this.border,
  173. this.activeBorder,
  174. this.shadow,
  175. this.semanticLabel,
  176. }) : super(key: key);
  177. @override
  178. State<GButton> createState() => _GButtonState();
  179. }
  180. class _GButtonState extends State<GButton> {
  181. @override
  182. Widget build(BuildContext context) {
  183. return Semantics(
  184. label: widget.semanticLabel ?? widget.text,
  185. child: Button(
  186. borderRadius: widget.borderRadius,
  187. border: widget.border,
  188. activeBorder: widget.activeBorder,
  189. shadow: widget.shadow,
  190. debug: widget.debug,
  191. duration: widget.duration,
  192. iconSize: widget.iconSize,
  193. active: widget.active,
  194. onPressed: () {
  195. if (widget.haptic!) HapticFeedback.selectionClick();
  196. widget.onPressed!();
  197. },
  198. padding: widget.padding,
  199. margin: widget.margin,
  200. gap: widget.gap,
  201. color: widget.backgroundColor,
  202. rippleColor: widget.rippleColor,
  203. hoverColor: widget.hoverColor,
  204. gradient: widget.backgroundGradient,
  205. curve: widget.curve,
  206. leading: widget.leading,
  207. iconActiveColor: widget.iconActiveColor,
  208. iconColor: widget.iconColor,
  209. icon: widget.icon,
  210. ),
  211. );
  212. }
  213. }
  214. class Button extends StatefulWidget {
  215. const Button({
  216. Key? key,
  217. this.icon,
  218. this.iconSize,
  219. this.leading,
  220. this.iconActiveColor,
  221. this.iconColor,
  222. this.text,
  223. this.gap = 0,
  224. this.color,
  225. this.rippleColor,
  226. this.hoverColor,
  227. this.onPressed,
  228. this.duration,
  229. this.curve,
  230. this.padding = const EdgeInsets.all(25),
  231. this.margin = const EdgeInsets.all(0),
  232. this.active = false,
  233. this.debug,
  234. this.gradient,
  235. this.borderRadius = const BorderRadius.all(Radius.circular(100.0)),
  236. this.border,
  237. this.activeBorder,
  238. this.shadow,
  239. }) : super(key: key);
  240. final IconData? icon;
  241. final double? iconSize;
  242. final Text? text;
  243. final Widget? leading;
  244. final Color? iconActiveColor;
  245. final Color? iconColor;
  246. final Color? color;
  247. final Color? rippleColor;
  248. final Color? hoverColor;
  249. final double? gap;
  250. final bool? active;
  251. final bool? debug;
  252. final VoidCallback? onPressed;
  253. final EdgeInsetsGeometry? padding;
  254. final EdgeInsetsGeometry? margin;
  255. final Duration? duration;
  256. final Curve? curve;
  257. final Gradient? gradient;
  258. final BorderRadius? borderRadius;
  259. final Border? border;
  260. final Border? activeBorder;
  261. final List<BoxShadow>? shadow;
  262. @override
  263. State<Button> createState() => _ButtonState();
  264. }
  265. class _ButtonState extends State<Button> with TickerProviderStateMixin {
  266. bool? _expanded;
  267. late AnimationController expandController;
  268. Animation<double>? animation;
  269. @override
  270. void initState() {
  271. super.initState();
  272. _expanded = widget.active;
  273. expandController =
  274. AnimationController(vsync: this, duration: widget.duration)
  275. ..addListener(() => setState(() {}));
  276. }
  277. @override
  278. void dispose() {
  279. expandController.dispose();
  280. super.dispose();
  281. }
  282. @override
  283. Widget build(BuildContext context) {
  284. _expanded = !widget.active!;
  285. if (_expanded!) {
  286. expandController.reverse();
  287. } else {
  288. expandController.forward();
  289. }
  290. final Widget icon = widget.leading ??
  291. Icon(
  292. widget.icon,
  293. color: _expanded! ? widget.iconColor : widget.iconActiveColor,
  294. size: widget.iconSize,
  295. );
  296. return Material(
  297. type: MaterialType.transparency,
  298. child: InkWell(
  299. highlightColor: widget.hoverColor,
  300. splashColor: widget.rippleColor,
  301. borderRadius: BorderRadius.circular(100),
  302. onTap: () {
  303. widget.onPressed!();
  304. },
  305. child: Container(
  306. padding: widget.margin,
  307. child: AnimatedContainer(
  308. curve: Curves.easeOut,
  309. padding: widget.padding,
  310. duration: widget.duration!,
  311. decoration: BoxDecoration(
  312. boxShadow: widget.shadow,
  313. border: widget.active!
  314. ? (widget.activeBorder ?? widget.border)
  315. : widget.border,
  316. gradient: widget.gradient,
  317. color: _expanded!
  318. ? widget.color!.withOpacity(0)
  319. : widget.debug!
  320. ? Colors.red
  321. : widget.gradient != null
  322. ? Colors.white
  323. : widget.color,
  324. borderRadius: widget.borderRadius,
  325. ),
  326. child: FittedBox(
  327. fit: BoxFit.fitHeight,
  328. child: Stack(
  329. children: [
  330. Align(alignment: Alignment.centerLeft, child: icon),
  331. ],
  332. ),
  333. ),
  334. ),
  335. ),
  336. ),
  337. );
  338. }
  339. }