nav_bar.dart 10 KB

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