From a1afcfd6dd0584e0d944178c9615d77c565208ad Mon Sep 17 00:00:00 2001 From: Daniyil Yakovlev Date: Mon, 3 Jun 2019 17:58:23 +0300 Subject: [PATCH] [update] Huge lib update --- example/lib/home_screen.dart | 266 ++++++++++++++++++++++++++++++ example/lib/main.dart | 116 +------------ lib/badges.dart | 7 +- lib/src/badge.dart | 156 ++++++++++++++++++ lib/src/badge_animation_type.dart | 1 + lib/src/badge_icon_button.dart | 127 -------------- lib/src/badge_position.dart | 25 ++- lib/src/badge_positioned.dart | 27 +++ lib/src/badge_positions.dart | 27 --- lib/src/badge_shape.dart | 2 +- 10 files changed, 482 insertions(+), 272 deletions(-) create mode 100644 example/lib/home_screen.dart create mode 100644 lib/src/badge.dart create mode 100644 lib/src/badge_animation_type.dart delete mode 100644 lib/src/badge_icon_button.dart create mode 100644 lib/src/badge_positioned.dart delete mode 100644 lib/src/badge_positions.dart diff --git a/example/lib/home_screen.dart b/example/lib/home_screen.dart new file mode 100644 index 0000000..cfa04f4 --- /dev/null +++ b/example/lib/home_screen.dart @@ -0,0 +1,266 @@ +import 'package:flutter/material.dart'; +import 'package:badges/badges.dart'; + +class HomeScreen extends StatefulWidget { + @override + _HomeScreenState createState() => _HomeScreenState(); +} + +class _HomeScreenState extends State { + int _counter = 0; + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: 2, + child: Scaffold( + bottomNavigationBar: _bottomNavigationBar(), + appBar: AppBar( + leading: Badge( + position: BadgePosition.topRight(top: 10, right: 10), + badgeContent: null, + toAnimate: false, + child: IconButton( + icon: Icon(Icons.menu), + onPressed: () {}, + ), + ), + title: Text('Badge Demo', style: TextStyle(color: Colors.black)), + backgroundColor: Colors.white, + bottom: _tabBar(), + actions: [ + Badge( + position: BadgePosition.topRight(top: 0, right: 3), + animationDuration: Duration(milliseconds: 300), + animationType: BadgeAnimationType.fade, + badgeContent: Text( + _counter.toString(), + style: TextStyle(color: Colors.white), + ), + child: + IconButton(icon: Icon(Icons.shopping_cart), onPressed: () {}), + ), + ], + ), + body: Column( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + RaisedButton.icon( + onPressed: () { + setState(() { + _counter++; + }); + }, + icon: Icon(Icons.add), + label: Text('Add to cart')), + RaisedButton.icon( + onPressed: () { + if (_counter > 0) { + setState(() { + _counter--; + }); + } + }, + icon: Icon(Icons.remove), + label: Text('Remove from cart')), + ], + ), + ), + _textBadge(), + _raisedButtonBadge(), + _chipWithZeroPadding(), + _badgeWithZeroPadding(), + _listView(), + ], + ), + ), + ); + } + + Widget _chipWithZeroPadding() { + return Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + Text('Chip with zero padding:'), + Chip( + label: Text('Hello'), + padding: EdgeInsets.all(0), + ), + ]); + } + + Widget _badgeWithZeroPadding() { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text('Badges:'), + for (int i = 0; i < 5; i++) + _getExampleBadge(padding: (i * 2).toDouble()) + ], + ); + } + + Widget _getExampleBadge({double padding}) { + return Padding( + padding: const EdgeInsets.all(4), + child: Badge( + badgeColor: Colors.lightBlueAccent, + toAnimate: false, + borderRadius: 20, + padding: EdgeInsets.all(padding ?? 4), + shape: BadgeShape.square, + badgeContent: Text( + 'Hello', + style: TextStyle(color: Colors.white), + ), + ), + ); + } + + Widget _bottomNavigationBar() { + return BottomNavigationBar( + items: [ + BottomNavigationBarItem( + title: Text('Events'), + icon: Icon(Icons.event), + ), + BottomNavigationBarItem( + title: Text('Messages'), + icon: Icon(Icons.message), + ), + BottomNavigationBarItem( + title: Text('Settings'), + icon: Badge( + shape: BadgeShape.circle, + borderRadius: 100, + toAnimate: false, + child: Icon(Icons.settings), + badgeContent: Container( + height: 5, + width: 5, + decoration: + BoxDecoration(shape: BoxShape.circle, color: Colors.white), + ), + ), + ), + ], + ); + } + + Widget _tabBar() { + return TabBar(tabs: [ + Tab( + icon: Badge( + toAnimate: false, + badgeColor: Colors.blue, + badgeContent: Text( + '3', + style: TextStyle(color: Colors.white), + ), + child: Icon(Icons.account_balance_wallet, color: Colors.grey), + ), + ), + Tab( + icon: Badge( + shape: BadgeShape.square, + borderRadius: 5, + toAnimate: false, + position: BadgePosition.topRight(top: -12, right: -20), + padding: EdgeInsets.all(2), + badgeContent: Text( + 'NEW', + style: TextStyle( + color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold), + ), + child: Text( + 'MUSIC', + style: TextStyle(color: Colors.grey[600]), + ), + ), + ), + ]); + } + + Widget _raisedButtonBadge() { + return Badge( + padding: EdgeInsets.all(8), + toAnimate: false, + badgeColor: Colors.deepPurple, + badgeContent: Text( + '!', + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + ), + child: RaisedButton( + onPressed: () {}, + child: Text('Raised Button'), + ), + ); + } + + Widget _textBadge() { + return Padding( + padding: const EdgeInsets.all(20), + child: Badge( + toAnimate: false, + padding: EdgeInsets.all(6), + badgeContent: Text( + '!', + style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold), + ), + child: Text('This is a text'), + position: BadgePosition.topLeft(top: -15)), + ); + } + + Widget _listView() { + Widget _listTile(String title, String value) { + return ListTile( + dense: true, + title: Text(title, style: TextStyle(fontSize: 16)), + trailing: SizedBox( + width: 100, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Badge( + toAnimate: false, + elevation: 0, + shape: BadgeShape.circle, + padding: EdgeInsets.all(7), + badgeContent: Text( + value, + style: TextStyle(color: Colors.white), + ), + ), + Padding( + padding: const EdgeInsets.only(left: 20), + child: Icon( + Icons.arrow_forward_ios, + size: 14, + ), + ), + ], + ), + ), + ); + } + + return Expanded( + child: ListView.separated( + itemCount: 3, + separatorBuilder: (BuildContext context, int index) => Divider(), + itemBuilder: (BuildContext context, int index) { + if (index == 0) { + return _listTile('Messages', '2'); + } else if (index == 1) { + return _listTile('Friends', '7'); + } else if (index == 2) { + return _listTile('Events', '!'); + } + }, + ), + ); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index 16d2604..9e8a611 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,5 +1,5 @@ +import 'package:example/home_screen.dart'; import 'package:flutter/material.dart'; -import 'package:badges/badges.dart'; void main() => runApp(MyApp()); @@ -7,8 +7,9 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( + debugShowCheckedModeBanner: false, theme: _buildTheme(), - home: MyHomePage(), + home: HomeScreen(), ); } } @@ -18,114 +19,3 @@ ThemeData _buildTheme() { return base.copyWith( primaryIconTheme: base.iconTheme.copyWith(color: Colors.black)); } - -class MyHomePage extends StatefulWidget { - @override - _MyHomePageState createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - Color _color = Colors.red; - Icon _icon = Icon(Icons.shopping_cart); - - void _incrementCounter() { - setState(() { - _counter++; - }); - } - - void _changeBadgeColor() { - setState(() { - if (_color == Colors.red) - _color = Colors.blue; - else - _color = Colors.red; - }); - } - - void _changeIcon() { - setState(() { - if (_icon.icon == Icons.shopping_cart) - _icon = Icon(Icons.email, color: Colors.deepOrange); - else - _icon = Icon(Icons.shopping_cart); - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text('Badge Demo', style: TextStyle(color: Colors.black)), - backgroundColor: Colors.white, - actions: [ - BadgeIconButton( - itemCount: _counter, - badgeColor: _color, - icon: _icon, - padding: EdgeInsets.all(2.0), - shape: BadgeShape.card, - animationDuration: Duration(seconds: 2), - textStyle: TextStyle( - fontSize: 10.0, - color: Colors.white, - fontWeight: FontWeight.bold), - position: BadgePosition.bottomRight, - onPressed: () {}), - ], - ), - body: Column( - children: [ - incrementButton, - changeColorButton, - changeIconButton, - ], - )); - } - - Widget get changeColorButton { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - RaisedButton( - onPressed: _changeBadgeColor, - child: Text('Change badge color'), - ) - ], - ), - ); - } - - Widget get changeIconButton { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - RaisedButton( - onPressed: _changeIcon, - child: Text('Change icon'), - ) - ], - ), - ); - } - - Widget get incrementButton { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - RaisedButton( - child: Text('Increment'), - onPressed: _incrementCounter, - ), - ], - ), - ); - } -} diff --git a/lib/badges.dart b/lib/badges.dart index f87f48a..48e4340 100644 --- a/lib/badges.dart +++ b/lib/badges.dart @@ -1,4 +1,5 @@ -export 'src/badge_icon_button.dart'; export 'src/badge_position.dart'; -export 'src/badge_positions.dart'; -export 'src/badge_shape.dart'; \ No newline at end of file +export 'src/badge_positioned.dart'; +export 'src/badge_shape.dart'; +export 'src/badge_animation_type.dart'; +export 'src/badge.dart'; diff --git a/lib/src/badge.dart b/lib/src/badge.dart new file mode 100644 index 0000000..2928b99 --- /dev/null +++ b/lib/src/badge.dart @@ -0,0 +1,156 @@ +import 'package:badges/src/badge_animation_type.dart'; +import 'package:badges/src/badge_position.dart'; +import 'package:badges/src/badge_positioned.dart'; +import 'package:badges/src/badge_shape.dart'; +import 'package:flutter/material.dart'; + +class Badge extends StatefulWidget { + final VoidCallback onPressed; + final Widget badgeContent; + final Color badgeColor; + final Widget child; + final double elevation; + final bool toAnimate; + final BadgePosition position; + final BadgeShape shape; + final EdgeInsets padding; + final Duration animationDuration; + final double borderRadius; + final BadgeAnimationType animationType; + + Badge({ + Key key, + this.badgeContent, + this.child, + this.onPressed, + this.badgeColor: Colors.red, + this.elevation: 2, + this.toAnimate: true, + this.position, + this.shape: BadgeShape.circle, + this.padding: const EdgeInsets.all(5.0), + this.animationDuration: const Duration(milliseconds: 500), + this.borderRadius, + this.animationType: BadgeAnimationType.slide, + }) : super(key: key); + + @override + BadgeState createState() { + return BadgeState(); + } +} + +class BadgeState extends State with SingleTickerProviderStateMixin { + AnimationController _animationController; + Animation _animation; + + final Tween _positionTween = Tween( + begin: const Offset(-0.5, 0.9), + end: const Offset(0.0, 0.0), + ); + final Tween _scaleTween = Tween(begin: 0.1, end: 1); + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + duration: widget.animationDuration, + vsync: this, + ); + + if (widget.animationType == BadgeAnimationType.slide) { + _animation = CurvedAnimation( + parent: _animationController, curve: Curves.elasticOut); + } else if (widget.animationType == BadgeAnimationType.scale) { + _animation = _scaleTween.animate(_animationController); + } else if (widget.animationType == BadgeAnimationType.fade) { + _animation = + CurvedAnimation(parent: _animationController, curve: Curves.easeIn); + } + + _animationController.forward(); + } + + @override + Widget build(BuildContext context) { + if (widget.child == null) { + return _getBadge(); + } else { + return Stack( + overflow: Overflow.visible, + children: [ + widget.child, + BadgePositioned( + position: widget.position, + child: _getBadge(), + ), + ], + ); + } + } + + Widget _getBadge() { + MaterialType type; + if (widget.shape == BadgeShape.circle) { + type = MaterialType.circle; + } else if (widget.shape == BadgeShape.square) { + type = MaterialType.card; + } else { + print('Unknown material type for badge. Used Card'); + type = MaterialType.card; + } + RoundedRectangleBorder border = type == MaterialType.circle + ? null + : RoundedRectangleBorder( + borderRadius: BorderRadius.circular(widget.borderRadius ?? 0)); + + Widget badgeView() { + return Material( + shape: border, + type: type, + elevation: widget.elevation, + color: widget.badgeColor, + child: Padding( + padding: widget.padding, + child: widget.badgeContent, + ), + ); + } + + if (widget.toAnimate) { + if (widget.animationType == BadgeAnimationType.slide) { + return SlideTransition( + position: _positionTween.animate(_animation), + child: badgeView(), + ); + } else if (widget.animationType == BadgeAnimationType.scale) { + return ScaleTransition( + scale: _animation, + child: badgeView(), + ); + } else if (widget.animationType == BadgeAnimationType.fade) { + return FadeTransition( + opacity: _animation, + child: badgeView(), + ); + } + } else { + return badgeView(); + } + } + + @override + void didUpdateWidget(Badge oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.badgeContent != oldWidget.badgeContent) { + _animationController.reset(); + _animationController.forward(); + } + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } +} diff --git a/lib/src/badge_animation_type.dart b/lib/src/badge_animation_type.dart new file mode 100644 index 0000000..df258e5 --- /dev/null +++ b/lib/src/badge_animation_type.dart @@ -0,0 +1 @@ +enum BadgeAnimationType { slide, scale, fade } diff --git a/lib/src/badge_icon_button.dart b/lib/src/badge_icon_button.dart deleted file mode 100644 index ff4e4b5..0000000 --- a/lib/src/badge_icon_button.dart +++ /dev/null @@ -1,127 +0,0 @@ -library badges; - -import 'package:badges/src/badge_position.dart'; -import 'package:badges/src/badge_positions.dart'; -import 'package:badges/src/badge_shape.dart'; -import 'package:flutter/material.dart'; - -class BadgeIconButton extends StatefulWidget { - final VoidCallback onPressed; - final int itemCount; - final Color badgeColor; - final Icon icon; - final bool hideZeroCount; - final bool toAnimate; - final BadgePosition position; - final BadgeShape shape; - final TextStyle textStyle; - final EdgeInsets padding; - final Duration animationDuration; - - BadgeIconButton( - {Key key, - @required this.itemCount, - @required this.icon, - this.onPressed, - this.hideZeroCount: true, - this.badgeColor: Colors.red, - this.toAnimate: true, - this.position: BadgePosition.topRight, - this.shape: BadgeShape.circle, - this.textStyle: const TextStyle( - fontSize: 13.0, - color: Colors.white, - fontWeight: FontWeight.bold, - ), - this.padding: const EdgeInsets.all(5.0), - this.animationDuration: const Duration(milliseconds: 500)}) - : assert(itemCount >= 0), - assert(badgeColor != null), - super(key: key); - - @override - BadgeIconButtonState createState() { - return BadgeIconButtonState(); - } -} - -class BadgeIconButtonState extends State - with SingleTickerProviderStateMixin { - AnimationController _animationController; - Animation _animation; - - final Tween _badgePositionTween = Tween( - begin: const Offset(-0.5, 0.9), - end: const Offset(0.0, 0.0), - ); - - @override - Widget build(BuildContext context) { - if (widget.hideZeroCount && widget.itemCount == 0) { - return IconButton( - icon: widget.icon, - onPressed: widget.onPressed, - ); - } - - return IconButton( - icon: Stack( - overflow: Overflow.visible, - children: [ - widget.icon, - BadgePositioned( - position: widget.position, - child: widget.toAnimate - ? SlideTransition( - position: _badgePositionTween.animate(_animation), - child: _getBadge()) - : _getBadge(), - ), - ], - ), - onPressed: widget.onPressed); - } - - Widget _getBadge() { - return Material( - type: widget.shape == BadgeShape.circle - ? MaterialType.circle - : MaterialType.card, - elevation: 2.0, - color: widget.badgeColor, - child: Padding( - padding: widget.padding, - child: Text( - widget.itemCount.toString(), - style: widget.textStyle, - ), - )); - } - - @override - void didUpdateWidget(BadgeIconButton oldWidget) { - if (widget.itemCount != oldWidget.itemCount) { - _animationController.reset(); - _animationController.forward(); - } - super.didUpdateWidget(oldWidget); - } - - @override - void dispose() { - _animationController.dispose(); - super.dispose(); - } - - @override - void initState() { - super.initState(); - _animationController = AnimationController( - duration: widget.animationDuration, - vsync: this, - ); - _animation = - CurvedAnimation(parent: _animationController, curve: Curves.elasticOut); - _animationController.forward(); - } -} diff --git a/lib/src/badge_position.dart b/lib/src/badge_position.dart index d691a2d..62e6359 100644 --- a/lib/src/badge_position.dart +++ b/lib/src/badge_position.dart @@ -1 +1,24 @@ -enum BadgePosition { topLeft, topRight, center, bottomRight, bottomLeft } \ No newline at end of file +class BadgePosition { + final double top; + final double right; + final double bottom; + final double left; + + BadgePosition({this.top, this.right, this.bottom, this.left}); + + factory BadgePosition.topLeft({double top, double left}) { + return BadgePosition(top: top ?? -5, left: left ?? -10); + } + + factory BadgePosition.topRight({double top, double right}) { + return BadgePosition(top: top ?? -8, right: right ?? -10); + } + + factory BadgePosition.bottomRight({double bottom, double right}) { + return BadgePosition(bottom: bottom ?? -8, right: right ?? -10); + } + + factory BadgePosition.bottomLeft({double bottom, double left}) { + return BadgePosition(bottom: bottom ?? -8, left: left ?? -10); + } +} diff --git a/lib/src/badge_positioned.dart b/lib/src/badge_positioned.dart new file mode 100644 index 0000000..c3d2322 --- /dev/null +++ b/lib/src/badge_positioned.dart @@ -0,0 +1,27 @@ +import 'package:badges/src/badge_position.dart'; +import 'package:flutter/widgets.dart'; + +class BadgePositioned extends StatelessWidget { + final Widget child; + final BadgePosition position; + + const BadgePositioned({Key key, this.position, this.child}) : super(key: key); + + @override + Widget build(BuildContext context) { + if (position == null) { + final topRight = BadgePosition.topRight(); + return Positioned( + top: topRight.top, + right: topRight.right, + child: child, + ); + } + return Positioned( + top: position.top, + right: position.right, + bottom: position.bottom, + left: position.left, + child: child); + } +} diff --git a/lib/src/badge_positions.dart b/lib/src/badge_positions.dart deleted file mode 100644 index b2e11d5..0000000 --- a/lib/src/badge_positions.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:badges/src/badge_position.dart'; -import 'package:flutter/widgets.dart'; - -class BadgePositioned extends StatelessWidget { - final Widget child; - final BadgePosition position; - - const BadgePositioned({Key key, this.position, this.child}) : super(key: key); - - @override - Widget build(BuildContext context) { - switch (position) { - case BadgePosition.topLeft: - return Positioned(top: -8.0, left: -3.0, child: child); - case BadgePosition.topRight: - return Positioned(top: -8.0, right: -3.0, child: child); - case BadgePosition.center: - return Positioned(child: child); - case BadgePosition.bottomLeft: - return Positioned(bottom: -8.0, left: -3.0, child: child); - case BadgePosition.bottomRight: - return Positioned(bottom: -8.0, right: -3.0, child: child); - default: - return Positioned(top: -8.0, right: -3.0, child: child); - } - } -} diff --git a/lib/src/badge_shape.dart b/lib/src/badge_shape.dart index 801e70c..54df8cb 100644 --- a/lib/src/badge_shape.dart +++ b/lib/src/badge_shape.dart @@ -1 +1 @@ -enum BadgeShape { circle, card } \ No newline at end of file +enum BadgeShape { circle, square }