├── CHANGELOG.md ├── .gitignore ├── LICENSE ├── breathing_flutter.iml ├── pubspec.yaml ├── README.md ├── pubspec.lock └── lib └── breathing_flutter.dart /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.0.1] - TODO: Add release date. 2 | 3 | * TODO: Describe initial release. 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | ios/.generated/ 9 | ios/Flutter/Generated.xcconfig 10 | ios/Runner/GeneratedPluginRegistrant.* 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 Pawan Kumar 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /breathing_flutter.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: breathing_flutter 2 | description: A breathing library in Flutter. 3 | version: 0.0.1 4 | author: Pawan Kumar 5 | homepage: https://github.com/iampawan/BreathingFlutter 6 | 7 | environment: 8 | sdk: ">=2.0.0-dev.28.0 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | 18 | # For information on the generic Dart part of this file, see the 19 | # following page: https://www.dartlang.org/tools/pub/pubspec 20 | 21 | # The following section is specific to Flutter. 22 | flutter: 23 | 24 | # To add assets to your package, add an assets section, like this: 25 | # assets: 26 | # - images/a_dot_burr.jpeg 27 | # - images/a_dot_ham.jpeg 28 | # 29 | # For details regarding assets in packages, see 30 | # https://flutter.io/assets-and-images/#from-packages 31 | # 32 | # An image asset can refer to one or more resolution-specific "variants", see 33 | # https://flutter.io/assets-and-images/#resolution-aware. 34 | 35 | # To add custom fonts to your package, add a fonts section here, 36 | # in this "flutter" section. Each entry in this list should have a 37 | # "family" key with the font family name, and a "fonts" key with a 38 | # list giving the asset and other descriptors for the font. For 39 | # example: 40 | # fonts: 41 | # - family: Schyler 42 | # fonts: 43 | # - asset: fonts/Schyler-Regular.ttf 44 | # - asset: fonts/Schyler-Italic.ttf 45 | # style: italic 46 | # - family: Trajan Pro 47 | # fonts: 48 | # - asset: fonts/TrajanPro.ttf 49 | # - asset: fonts/TrajanPro_Bold.ttf 50 | # weight: 700 51 | # 52 | # For details regarding fonts in packages, see 53 | # https://flutter.io/custom-fonts/#from-packages 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BreathingFlutter 2 | 3 | [![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg)](https://saythanks.io/to/iampawan) [![Twitter](https://img.shields.io/twitter/url/https/github.com/iampawan/BreathingFlutter.svg?style=social)](https://twitter.com/intent/tweet?text=Wow:&url=https%3A%2F%2Fgithub.com%2Fiampawan%2FBreathingFlutterr) 4 | 5 | A breathing appbar and widget library in Flutter. 6 | 7 | The source code is **100% Dart**, and everything resides in the [/lib](https://github.com/iampawan/BreathingFlutter/tree/master/lib) folder. 8 | 9 | 10 | ### Show some :heart: and star the repo to support the project 11 | 12 | [![GitHub stars](https://img.shields.io/github/stars/iampawan/BreathingFlutter.svg?style=social&label=Star)](https://github.com/iampawan/BreathingFlutter) [![GitHub forks](https://img.shields.io/github/forks/iampawan/BreathingFlutter.svg?style=social&label=Fork)](https://github.com/iampawan/BreathingFlutter/fork) [![GitHub watchers](https://img.shields.io/github/watchers/iampawan/BreathingFlutter.svg?style=social&label=Watch)](https://github.com/iampawan/BreathingFlutter) [![GitHub followers](https://img.shields.io/github/followers/iampawan.svg?style=social&label=Follow)](https://github.com/iampawan/BreathingFlutter) 13 | [![Twitter Follow](https://img.shields.io/twitter/follow/imthepk.svg?style=social)](https://twitter.com/imthepk) 14 | 15 | [![Open Source Love](https://badges.frapsoft.com/os/v1/open-source.svg?v=102)](https://opensource.org/licenses/Apache-2.0) 16 | 17 | ## YouTube Channel 18 | 19 | [MTechViral](https://www.youtube.com/c/MTechViral) 20 | 21 | ## Facebook Group 22 | 23 | [Let's Flutter](https://www.facebook.com/groups/425920117856409/) 24 | 25 | 26 | 27 | ## 💻 Installation 28 | In the `dependencies:` section of your `pubspec.yaml`, add the following line: 29 | 30 | ```yaml 31 | breathing_flutter: 32 | ``` 33 | 34 | ## ❔ Usage 35 | 36 | ### Breathing AppBar 37 | 38 | ```dart 39 | import 'package:breathing_flutter/breathing_flutter.dart'; 40 | 41 | class HomePage extends StatelessWidget { 42 | final GlobalKey _breathingAppBarKey = 43 | GlobalKey(); 44 | @override 45 | Widget build(BuildContext context) { 46 | return Scaffold( 47 | appBar: BreathingAppBar( 48 | key: _breathingAppBarKey, 49 | title: Text("Breathing App Bar"), 50 | breathColors: [Colors.black, Colors.red, Colors.blue], 51 | ), 52 | ); 53 | } 54 | } 55 | 56 | ``` 57 | 58 | ### Breathing Widget 59 | 60 | ```dart 61 | import 'package:breathing_flutter/breathing_flutter.dart'; 62 | 63 | class HomePage extends StatelessWidget { 64 | final GlobalKey _breathingWidgetKey = 65 | GlobalKey(); 66 | @override 67 | Widget build(BuildContext context) { 68 | return Scaffold( 69 | body: Center( 70 | child: BreathingWidget( 71 | key: _breathingWidgetKey, 72 | breathColors: [ 73 | Colors.black, 74 | Colors.deepOrange, 75 | Colors.green, 76 | Colors.indigo 77 | ], 78 | child: Container( 79 | width: 100.0, 80 | height: 100.0, 81 | child: Center( 82 | child: Text( 83 | "Breathing Widget", 84 | textAlign: TextAlign.center, 85 | style: TextStyle(color: Colors.white), 86 | ), 87 | ), 88 | )), 89 | )); 90 | } 91 | } 92 | ``` 93 | 94 | ## Stop Breathing 95 | This is how you can stop breathing for an appbar and the widget. Also, 96 | Specify Color in the parameter to fix the appbar or widget color. 97 | 98 | BreathingAppBar 99 | ``` 100 | _breathingAppBarKey.currentState.stopBreath(Colors.teal); 101 | 102 | ``` 103 | BreathingWigdet 104 | 105 | ``` 106 | _breathingWidgetKey.currentState.stopBreath(Colors.amber); 107 | 108 | 109 | ``` 110 | 111 | ## Start Breathing 112 | BreathingAppBar 113 | ``` 114 | _breathingAppBarKey.currentState.startBreath(); 115 | 116 | ``` 117 | BreathingWigdet 118 | 119 | ``` 120 | _breathingWidgetKey.currentState.startBreath(); 121 | 122 | 123 | ``` 124 | 125 | 126 | ## 💰 Donations 127 | 128 | This project needs you! If you would like to support this project's further development, the creator of this project or the continuous maintenance of this project, feel free to donate. Your donation is highly appreciated (and I love food, coffee and beer). Thank you! 129 | 130 | **PayPal** 131 | 132 | * **[Donate $5](https://www.paypal.me/imthepk/5)**: Thank's for creating this project, here's a tea (or some juice) for you! 133 | * **[Donate $10](https://www.paypal.me/imthepk/10)**: Wow, I am stunned. Let me take you to the movies! 134 | * **[Donate $15](https://www.paypal.me/imthepk/15)**: I really appreciate your work, let's grab some lunch! 135 | * **[Donate $25](https://www.paypal.me/imthepk/25)**: That's some awesome stuff you did right there, dinner is on me! 136 | * **[Donate $50](https://www.paypal.me/imthepk/50)**: I really really want to support this project, great job! 137 | * **[Donate $100](https://www.paypal.me/imthepk/100)**: You are the man! This project saved me hours (if not days) of struggle and hard work, simply awesome! 138 | * **[Donate $2799](https://www.paypal.me/imthepk/2799)**: Go buddy, buy Macbook Pro for yourself! 139 | 140 | Of course, you can also choose what you want to donate, all donations are awesome! 141 | 142 | ## 👨 Developed By 143 | 144 | ``` 145 | Pawan Kumar 146 | ``` 147 | GDE (Google Developer Expert) for Flutter. Passionate #Flutter, #Android Developer. #Entrepreneur #YouTuber 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | # 👍 How to Contribute 156 | 1. Fork it 157 | 2. Create your feature branch (git checkout -b my-new-feature) 158 | 3. Commit your changes (git commit -am 'Add some feature') 159 | 4. Push to the branch (git push origin my-new-feature) 160 | 5. Create new Pull Request 161 | 162 | # 📃 License 163 | 164 | Copyright (c) 2018 Pawan Kumar 165 | 166 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 167 | 168 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 169 | 170 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 171 | 172 | ## Getting Started 173 | 174 | For help getting started with Flutter, view our online [documentation](https://flutter.io/). 175 | 176 | For help on editing package code, view the [documentation](https://flutter.io/developing-packages/). -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://www.dartlang.org/tools/pub/glossary#lockfile 3 | packages: 4 | analyzer: 5 | dependency: transitive 6 | description: 7 | name: analyzer 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "0.31.2-alpha.2" 11 | args: 12 | dependency: transitive 13 | description: 14 | name: args 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.4.3" 18 | async: 19 | dependency: transitive 20 | description: 21 | name: async 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.0.7" 25 | boolean_selector: 26 | dependency: transitive 27 | description: 28 | name: boolean_selector 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.0.3" 32 | charcode: 33 | dependency: transitive 34 | description: 35 | name: charcode 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.1" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.14.6" 46 | convert: 47 | dependency: transitive 48 | description: 49 | name: convert 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "2.0.1" 53 | crypto: 54 | dependency: transitive 55 | description: 56 | name: crypto 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "2.0.5" 60 | csslib: 61 | dependency: transitive 62 | description: 63 | name: csslib 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "0.14.4" 67 | flutter: 68 | dependency: "direct main" 69 | description: flutter 70 | source: sdk 71 | version: "0.0.0" 72 | flutter_test: 73 | dependency: "direct dev" 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | front_end: 78 | dependency: transitive 79 | description: 80 | name: front_end 81 | url: "https://pub.dartlang.org" 82 | source: hosted 83 | version: "0.1.0-alpha.12" 84 | glob: 85 | dependency: transitive 86 | description: 87 | name: glob 88 | url: "https://pub.dartlang.org" 89 | source: hosted 90 | version: "1.1.5" 91 | html: 92 | dependency: transitive 93 | description: 94 | name: html 95 | url: "https://pub.dartlang.org" 96 | source: hosted 97 | version: "0.13.3+1" 98 | http: 99 | dependency: transitive 100 | description: 101 | name: http 102 | url: "https://pub.dartlang.org" 103 | source: hosted 104 | version: "0.11.3+16" 105 | http_multi_server: 106 | dependency: transitive 107 | description: 108 | name: http_multi_server 109 | url: "https://pub.dartlang.org" 110 | source: hosted 111 | version: "2.0.5" 112 | http_parser: 113 | dependency: transitive 114 | description: 115 | name: http_parser 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "3.1.2" 119 | io: 120 | dependency: transitive 121 | description: 122 | name: io 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "0.3.2+1" 126 | js: 127 | dependency: transitive 128 | description: 129 | name: js 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "0.6.1" 133 | json_rpc_2: 134 | dependency: transitive 135 | description: 136 | name: json_rpc_2 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "2.0.8" 140 | kernel: 141 | dependency: transitive 142 | description: 143 | name: kernel 144 | url: "https://pub.dartlang.org" 145 | source: hosted 146 | version: "0.3.0-alpha.12" 147 | logging: 148 | dependency: transitive 149 | description: 150 | name: logging 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "0.11.3+1" 154 | matcher: 155 | dependency: transitive 156 | description: 157 | name: matcher 158 | url: "https://pub.dartlang.org" 159 | source: hosted 160 | version: "0.12.2" 161 | meta: 162 | dependency: transitive 163 | description: 164 | name: meta 165 | url: "https://pub.dartlang.org" 166 | source: hosted 167 | version: "1.1.5" 168 | mime: 169 | dependency: transitive 170 | description: 171 | name: mime 172 | url: "https://pub.dartlang.org" 173 | source: hosted 174 | version: "0.9.6+1" 175 | multi_server_socket: 176 | dependency: transitive 177 | description: 178 | name: multi_server_socket 179 | url: "https://pub.dartlang.org" 180 | source: hosted 181 | version: "1.0.1" 182 | node_preamble: 183 | dependency: transitive 184 | description: 185 | name: node_preamble 186 | url: "https://pub.dartlang.org" 187 | source: hosted 188 | version: "1.4.2" 189 | package_config: 190 | dependency: transitive 191 | description: 192 | name: package_config 193 | url: "https://pub.dartlang.org" 194 | source: hosted 195 | version: "1.0.3" 196 | package_resolver: 197 | dependency: transitive 198 | description: 199 | name: package_resolver 200 | url: "https://pub.dartlang.org" 201 | source: hosted 202 | version: "1.0.3" 203 | path: 204 | dependency: transitive 205 | description: 206 | name: path 207 | url: "https://pub.dartlang.org" 208 | source: hosted 209 | version: "1.6.1" 210 | plugin: 211 | dependency: transitive 212 | description: 213 | name: plugin 214 | url: "https://pub.dartlang.org" 215 | source: hosted 216 | version: "0.2.0+2" 217 | pool: 218 | dependency: transitive 219 | description: 220 | name: pool 221 | url: "https://pub.dartlang.org" 222 | source: hosted 223 | version: "1.3.5" 224 | pub_semver: 225 | dependency: transitive 226 | description: 227 | name: pub_semver 228 | url: "https://pub.dartlang.org" 229 | source: hosted 230 | version: "1.4.1" 231 | quiver: 232 | dependency: transitive 233 | description: 234 | name: quiver 235 | url: "https://pub.dartlang.org" 236 | source: hosted 237 | version: "0.29.0+1" 238 | shelf: 239 | dependency: transitive 240 | description: 241 | name: shelf 242 | url: "https://pub.dartlang.org" 243 | source: hosted 244 | version: "0.7.3+1" 245 | shelf_packages_handler: 246 | dependency: transitive 247 | description: 248 | name: shelf_packages_handler 249 | url: "https://pub.dartlang.org" 250 | source: hosted 251 | version: "1.0.3" 252 | shelf_static: 253 | dependency: transitive 254 | description: 255 | name: shelf_static 256 | url: "https://pub.dartlang.org" 257 | source: hosted 258 | version: "0.2.7+1" 259 | shelf_web_socket: 260 | dependency: transitive 261 | description: 262 | name: shelf_web_socket 263 | url: "https://pub.dartlang.org" 264 | source: hosted 265 | version: "0.2.2+2" 266 | sky_engine: 267 | dependency: transitive 268 | description: flutter 269 | source: sdk 270 | version: "0.0.99" 271 | source_map_stack_trace: 272 | dependency: transitive 273 | description: 274 | name: source_map_stack_trace 275 | url: "https://pub.dartlang.org" 276 | source: hosted 277 | version: "1.1.4" 278 | source_maps: 279 | dependency: transitive 280 | description: 281 | name: source_maps 282 | url: "https://pub.dartlang.org" 283 | source: hosted 284 | version: "0.10.5" 285 | source_span: 286 | dependency: transitive 287 | description: 288 | name: source_span 289 | url: "https://pub.dartlang.org" 290 | source: hosted 291 | version: "1.4.0" 292 | stack_trace: 293 | dependency: transitive 294 | description: 295 | name: stack_trace 296 | url: "https://pub.dartlang.org" 297 | source: hosted 298 | version: "1.9.2" 299 | stream_channel: 300 | dependency: transitive 301 | description: 302 | name: stream_channel 303 | url: "https://pub.dartlang.org" 304 | source: hosted 305 | version: "1.6.7+1" 306 | string_scanner: 307 | dependency: transitive 308 | description: 309 | name: string_scanner 310 | url: "https://pub.dartlang.org" 311 | source: hosted 312 | version: "1.0.2" 313 | term_glyph: 314 | dependency: transitive 315 | description: 316 | name: term_glyph 317 | url: "https://pub.dartlang.org" 318 | source: hosted 319 | version: "1.0.0" 320 | test: 321 | dependency: transitive 322 | description: 323 | name: test 324 | url: "https://pub.dartlang.org" 325 | source: hosted 326 | version: "0.12.41" 327 | typed_data: 328 | dependency: transitive 329 | description: 330 | name: typed_data 331 | url: "https://pub.dartlang.org" 332 | source: hosted 333 | version: "1.1.5" 334 | utf: 335 | dependency: transitive 336 | description: 337 | name: utf 338 | url: "https://pub.dartlang.org" 339 | source: hosted 340 | version: "0.9.0+4" 341 | vector_math: 342 | dependency: transitive 343 | description: 344 | name: vector_math 345 | url: "https://pub.dartlang.org" 346 | source: hosted 347 | version: "2.0.6" 348 | vm_service_client: 349 | dependency: transitive 350 | description: 351 | name: vm_service_client 352 | url: "https://pub.dartlang.org" 353 | source: hosted 354 | version: "0.2.4+3" 355 | watcher: 356 | dependency: transitive 357 | description: 358 | name: watcher 359 | url: "https://pub.dartlang.org" 360 | source: hosted 361 | version: "0.9.7+8" 362 | web_socket_channel: 363 | dependency: transitive 364 | description: 365 | name: web_socket_channel 366 | url: "https://pub.dartlang.org" 367 | source: hosted 368 | version: "1.0.8" 369 | yaml: 370 | dependency: transitive 371 | description: 372 | name: yaml 373 | url: "https://pub.dartlang.org" 374 | source: hosted 375 | version: "2.1.14" 376 | sdks: 377 | dart: ">=2.0.0-dev.62.0 <=2.0.0-dev.63.0.flutter-4c9689c1d2" 378 | -------------------------------------------------------------------------------- /lib/breathing_flutter.dart: -------------------------------------------------------------------------------- 1 | library breathing_flutter; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter/services.dart'; 6 | 7 | class BreathingWidget extends StatefulWidget { 8 | final Widget child; 9 | 10 | final List breathColors; 11 | 12 | const BreathingWidget( 13 | {Key key, @required this.breathColors, @required this.child}) 14 | : super(key: key); 15 | 16 | @override 17 | BreathingWidgetState createState() { 18 | return new BreathingWidgetState(); 19 | } 20 | } 21 | 22 | class BreathingWidgetState extends State 23 | with TickerProviderStateMixin { 24 | AnimationController breathColorController; 25 | Animation breathColorAnimation; 26 | Animation breathOpacityAnimation; 27 | List breathColors; 28 | 29 | @override 30 | void initState() { 31 | super.initState(); 32 | breathColors = widget.breathColors; 33 | breathColorController = 34 | AnimationController(vsync: this, duration: Duration(milliseconds: 7000)) 35 | ..repeat(); 36 | 37 | setColorAnimation(); 38 | setOpacityAnimation(0.3); 39 | } 40 | 41 | @override 42 | void dispose() { 43 | breathColorController?.dispose(); 44 | super.dispose(); 45 | } 46 | 47 | void setOpacityAnimation(double begin) { 48 | breathOpacityAnimation = Tween( 49 | begin: begin, 50 | end: 1.0, 51 | ).animate(CurvedAnimation( 52 | parent: breathColorController, 53 | curve: Curves.linear, 54 | )); 55 | } 56 | 57 | void setColorAnimation() { 58 | breathColorAnimation = IntTween( 59 | begin: 0, 60 | end: breathColors.length - 1, 61 | ).animate(breathColorController); 62 | } 63 | 64 | stopBreath(var fixedColor) { 65 | setState(() { 66 | breathColors = [fixedColor]; 67 | setColorAnimation(); 68 | setOpacityAnimation(1.0); 69 | }); 70 | breathColorController.stop(); 71 | } 72 | 73 | void startBreath() { 74 | setState(() { 75 | breathColors = widget.breathColors; 76 | setColorAnimation(); 77 | setOpacityAnimation(0.3); 78 | }); 79 | breathColorController.repeat(); 80 | } 81 | 82 | @override 83 | Widget build(BuildContext context) { 84 | return AnimatedBuilder( 85 | animation: breathColorAnimation, 86 | builder: (context, child) => FadeTransition( 87 | opacity: breathOpacityAnimation, 88 | child: DecoratedBox( 89 | decoration: BoxDecoration( 90 | color: breathColors[breathColorAnimation.value], 91 | ), 92 | child: widget.child, 93 | ), 94 | )); 95 | } 96 | } 97 | 98 | const double _kLeadingWidth = 99 | kToolbarHeight; // So the leading button is square. 100 | 101 | class BreathingAppBar extends StatefulWidget implements PreferredSizeWidget { 102 | BreathingAppBar({ 103 | Key key, 104 | this.leading, 105 | this.automaticallyImplyLeading = true, 106 | this.title, 107 | this.actions, 108 | this.flexibleSpace, 109 | this.bottom, 110 | this.elevation = 4.0, 111 | this.brightness, 112 | this.iconTheme, 113 | this.textTheme, 114 | this.primary = true, 115 | this.centerTitle, 116 | this.titleSpacing = NavigationToolbar.kMiddleSpacing, 117 | this.toolbarOpacity = 1.0, 118 | this.bottomOpacity = 1.0, 119 | @required this.breathColors, 120 | }) : assert(automaticallyImplyLeading != null), 121 | assert(elevation != null), 122 | assert(primary != null), 123 | assert(titleSpacing != null), 124 | assert(toolbarOpacity != null), 125 | assert(bottomOpacity != null), 126 | preferredSize = new Size.fromHeight( 127 | kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)), 128 | super(key: key); 129 | 130 | final List breathColors; 131 | final Widget leading; 132 | 133 | final bool automaticallyImplyLeading; 134 | 135 | final Widget title; 136 | 137 | final List actions; 138 | 139 | /// This widget is stacked behind the toolbar and the tabbar. It's height will 140 | /// be the same as the app bar's overall height. 141 | /// 142 | /// A flexible space isn't actually flexible unless the [AppBar]'s container 143 | /// changes the [AppBar]'s size. A [SliverAppBar] in a [CustomScrollView] 144 | /// changes the [AppBar]'s height when scrolled. 145 | /// 146 | /// Typically a [FlexibleSpaceBar]. See [FlexibleSpaceBar] for details. 147 | final Widget flexibleSpace; 148 | 149 | /// This widget appears across the bottom of the app bar. 150 | /// 151 | /// Typically a [TabBar]. Only widgets that implement [PreferredSizeWidget] can 152 | /// be used at the bottom of an app bar. 153 | /// 154 | /// See also: 155 | /// 156 | /// * [PreferredSize], which can be used to give an arbitrary widget a preferred size. 157 | final PreferredSizeWidget bottom; 158 | 159 | /// The z-coordinate at which to place this app bar. This controls the size of 160 | /// the shadow below the app bar. 161 | /// 162 | /// Defaults to 4, the appropriate elevation for app bars. 163 | final double elevation; 164 | 165 | /// The color to use for the app bar's material. Typically this should be set 166 | /// along with [brightness], [iconTheme], [textTheme]. 167 | /// 168 | 169 | /// The brightness of the app bar's material. Typically this is set along 170 | /// with [backgroundColor], [iconTheme], [textTheme]. 171 | /// 172 | /// Defaults to [ThemeData.primaryColorBrightness]. 173 | final Brightness brightness; 174 | 175 | /// The color, opacity, and size to use for app bar icons. Typically this 176 | /// is set along with [backgroundColor], [brightness], [textTheme]. 177 | /// 178 | /// Defaults to [ThemeData.primaryIconTheme]. 179 | final IconThemeData iconTheme; 180 | 181 | /// The typographic styles to use for text in the app bar. Typically this is 182 | /// set along with [brightness] [backgroundColor], [iconTheme]. 183 | /// 184 | /// Defaults to [ThemeData.primaryTextTheme]. 185 | final TextTheme textTheme; 186 | 187 | /// Whether this app bar is being displayed at the top of the screen. 188 | /// 189 | /// If true, the appbar's toolbar elements and [bottom] widget will be 190 | /// padded on top by the height of the system status bar. The layout 191 | /// of the [flexibleSpace] is not affected by the [primary] property. 192 | final bool primary; 193 | 194 | /// Whether the title should be centered. 195 | /// 196 | /// Defaults to being adapted to the current [TargetPlatform]. 197 | final bool centerTitle; 198 | 199 | /// The spacing around [title] content on the horizontal axis. This spacing is 200 | /// applied even if there is no [leading] content or [actions]. If you want 201 | /// [title] to take all the space available, set this value to 0.0. 202 | /// 203 | /// Defaults to [NavigationToolbar.kMiddleSpacing]. 204 | final double titleSpacing; 205 | 206 | /// How opaque the toolbar part of the app bar is. 207 | /// 208 | /// A value of 1.0 is fully opaque, and a value of 0.0 is fully transparent. 209 | /// 210 | /// Typically, this value is not changed from its default value (1.0). It is 211 | /// used by [SliverAppBar] to animate the opacity of the toolbar when the app 212 | /// bar is scrolled. 213 | final double toolbarOpacity; 214 | 215 | /// How opaque the bottom part of the app bar is. 216 | /// 217 | /// A value of 1.0 is fully opaque, and a value of 0.0 is fully transparent. 218 | /// 219 | /// Typically, this value is not changed from its default value (1.0). It is 220 | /// used by [SliverAppBar] to animate the opacity of the toolbar when the app 221 | /// bar is scrolled. 222 | final double bottomOpacity; 223 | 224 | /// A size whose height is the sum of [kToolbarHeight] and the [bottom] widget's 225 | /// preferred height. 226 | /// 227 | /// [Scaffold] uses this this size to set its app bar's height. 228 | @override 229 | final Size preferredSize; 230 | 231 | bool _getEffectiveCenterTitle(ThemeData themeData) { 232 | if (centerTitle != null) return centerTitle; 233 | assert(themeData.platform != null); 234 | switch (themeData.platform) { 235 | case TargetPlatform.android: 236 | case TargetPlatform.fuchsia: 237 | return false; 238 | case TargetPlatform.iOS: 239 | return actions == null || actions.length < 2; 240 | } 241 | return null; 242 | } 243 | 244 | @override 245 | BreathingAppBarState createState() => new BreathingAppBarState(); 246 | } 247 | 248 | class BreathingAppBarState extends State 249 | with TickerProviderStateMixin { 250 | List breathColors; 251 | 252 | void _handleDrawerButton() { 253 | Scaffold.of(context).openDrawer(); 254 | } 255 | 256 | void _handleDrawerButtonEnd() { 257 | Scaffold.of(context).openEndDrawer(); 258 | } 259 | 260 | AnimationController breathColorController; 261 | Animation breathColorAnimation; 262 | Animation breathOpacityAnimation; 263 | 264 | @override 265 | void initState() { 266 | super.initState(); 267 | breathColors = widget.breathColors; 268 | breathColorController = 269 | AnimationController(vsync: this, duration: Duration(milliseconds: 7000)) 270 | ..repeat(); 271 | 272 | setColorAnimation(); 273 | setOpacityAnimation(0.3); 274 | } 275 | 276 | @override 277 | void dispose() { 278 | breathColorController?.dispose(); 279 | super.dispose(); 280 | } 281 | 282 | void setOpacityAnimation(double begin) { 283 | breathOpacityAnimation = Tween( 284 | begin: begin, 285 | end: 1.0, 286 | ).animate(CurvedAnimation( 287 | parent: breathColorController, 288 | curve: Curves.linear, 289 | )); 290 | } 291 | 292 | void setColorAnimation() { 293 | breathColorAnimation = IntTween( 294 | begin: 0, 295 | end: breathColors.length - 1, 296 | ).animate(breathColorController); 297 | } 298 | 299 | stopBreath(var fixedColor) { 300 | setState(() { 301 | breathColors = [fixedColor]; 302 | setColorAnimation(); 303 | setOpacityAnimation(1.0); 304 | }); 305 | breathColorController.stop(); 306 | } 307 | 308 | void startBreath() { 309 | setState(() { 310 | breathColors = widget.breathColors; 311 | setColorAnimation(); 312 | setOpacityAnimation(0.3); 313 | }); 314 | breathColorController.repeat(); 315 | } 316 | 317 | @override 318 | Widget build(BuildContext context) { 319 | assert(!widget.primary || debugCheckHasMediaQuery(context)); 320 | final ThemeData themeData = Theme.of(context); 321 | final ScaffoldState scaffold = Scaffold.of(context, nullOk: true); 322 | final ModalRoute parentRoute = ModalRoute.of(context); 323 | 324 | final bool hasDrawer = scaffold?.hasDrawer ?? false; 325 | final bool hasEndDrawer = scaffold?.hasEndDrawer ?? false; 326 | final bool canPop = parentRoute?.canPop ?? false; 327 | final bool useCloseButton = 328 | parentRoute is PageRoute && parentRoute.fullscreenDialog; 329 | 330 | IconThemeData appBarIconTheme = 331 | widget.iconTheme ?? themeData.primaryIconTheme; 332 | TextStyle centerStyle = 333 | widget.textTheme?.title ?? themeData.primaryTextTheme.title; 334 | TextStyle sideStyle = 335 | widget.textTheme?.body1 ?? themeData.primaryTextTheme.body1; 336 | 337 | if (widget.toolbarOpacity != 1.0) { 338 | final double opacity = 339 | const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn) 340 | .transform(widget.toolbarOpacity); 341 | if (centerStyle?.color != null) 342 | centerStyle = 343 | centerStyle.copyWith(color: centerStyle.color.withOpacity(opacity)); 344 | if (sideStyle?.color != null) 345 | sideStyle = 346 | sideStyle.copyWith(color: sideStyle.color.withOpacity(opacity)); 347 | appBarIconTheme = appBarIconTheme.copyWith( 348 | opacity: opacity * (appBarIconTheme.opacity ?? 1.0)); 349 | } 350 | 351 | Widget leading = widget.leading; 352 | if (leading == null && widget.automaticallyImplyLeading) { 353 | if (hasDrawer) { 354 | leading = new IconButton( 355 | icon: const Icon(Icons.menu), 356 | onPressed: _handleDrawerButton, 357 | tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip, 358 | ); 359 | } else { 360 | if (canPop) 361 | leading = useCloseButton ? const CloseButton() : const BackButton(); 362 | } 363 | } 364 | if (leading != null) { 365 | leading = new ConstrainedBox( 366 | constraints: const BoxConstraints.tightFor(width: _kLeadingWidth), 367 | child: leading, 368 | ); 369 | } 370 | 371 | Widget title = widget.title; 372 | if (title != null) { 373 | bool namesRoute; 374 | switch (defaultTargetPlatform) { 375 | case TargetPlatform.android: 376 | case TargetPlatform.fuchsia: 377 | namesRoute = true; 378 | break; 379 | case TargetPlatform.iOS: 380 | break; 381 | } 382 | title = new DefaultTextStyle( 383 | style: centerStyle, 384 | softWrap: false, 385 | overflow: TextOverflow.ellipsis, 386 | child: new Semantics( 387 | namesRoute: namesRoute, 388 | child: title, 389 | header: true, 390 | ), 391 | ); 392 | } 393 | 394 | Widget actions; 395 | if (widget.actions != null && widget.actions.isNotEmpty) { 396 | actions = new Row( 397 | mainAxisSize: MainAxisSize.min, 398 | crossAxisAlignment: CrossAxisAlignment.stretch, 399 | children: widget.actions, 400 | ); 401 | } else if (hasEndDrawer) { 402 | actions = new IconButton( 403 | icon: const Icon(Icons.menu), 404 | onPressed: _handleDrawerButtonEnd, 405 | tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip, 406 | ); 407 | } 408 | 409 | final Widget toolbar = new NavigationToolbar( 410 | leading: leading, 411 | middle: title, 412 | trailing: actions, 413 | centerMiddle: widget._getEffectiveCenterTitle(themeData), 414 | middleSpacing: widget.titleSpacing, 415 | ); 416 | 417 | // If the toolbar is allocated less than kToolbarHeight make it 418 | // appear to scroll upwards within its shrinking container. 419 | Widget appBar = new ClipRect( 420 | child: new CustomSingleChildLayout( 421 | delegate: const _ToolbarContainerLayout(), 422 | child: IconTheme.merge( 423 | data: appBarIconTheme, 424 | child: new DefaultTextStyle( 425 | style: sideStyle, 426 | child: toolbar, 427 | ), 428 | ), 429 | ), 430 | ); 431 | if (widget.bottom != null) { 432 | appBar = new Column( 433 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 434 | children: [ 435 | new Flexible( 436 | child: new ConstrainedBox( 437 | constraints: const BoxConstraints(maxHeight: kToolbarHeight), 438 | child: appBar, 439 | ), 440 | ), 441 | widget.bottomOpacity == 1.0 442 | ? widget.bottom 443 | : new Opacity( 444 | opacity: 445 | const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn) 446 | .transform(widget.bottomOpacity), 447 | child: widget.bottom, 448 | ), 449 | ], 450 | ); 451 | } 452 | 453 | // The padding applies to the toolbar and tabbar, not the flexible space. 454 | if (widget.primary) { 455 | appBar = new SafeArea( 456 | top: true, 457 | child: appBar, 458 | ); 459 | } 460 | 461 | appBar = new Align( 462 | alignment: Alignment.topCenter, 463 | child: appBar, 464 | ); 465 | 466 | if (widget.flexibleSpace != null) { 467 | appBar = new Stack( 468 | fit: StackFit.passthrough, 469 | children: [ 470 | widget.flexibleSpace, 471 | appBar, 472 | ], 473 | ); 474 | } 475 | final Brightness brightness = 476 | widget.brightness ?? themeData.primaryColorBrightness; 477 | final SystemUiOverlayStyle overlayStyle = brightness == Brightness.dark 478 | ? SystemUiOverlayStyle.light 479 | : SystemUiOverlayStyle.dark; 480 | 481 | return new Semantics( 482 | container: true, 483 | explicitChildNodes: true, 484 | child: new AnnotatedRegion( 485 | value: overlayStyle, 486 | child: AnimatedBuilder( 487 | animation: breathOpacityAnimation, 488 | builder: (context, child) => FadeTransition( 489 | opacity: breathOpacityAnimation, 490 | child: new Material( 491 | color: breathColors[breathColorAnimation.value], 492 | elevation: widget.elevation, 493 | child: appBar, 494 | ), 495 | )), 496 | ), 497 | ); 498 | } 499 | } 500 | 501 | class _ToolbarContainerLayout extends SingleChildLayoutDelegate { 502 | const _ToolbarContainerLayout(); 503 | 504 | @override 505 | BoxConstraints getConstraintsForChild(BoxConstraints constraints) { 506 | return constraints.tighten(height: kToolbarHeight); 507 | } 508 | 509 | @override 510 | Size getSize(BoxConstraints constraints) { 511 | return new Size(constraints.maxWidth, kToolbarHeight); 512 | } 513 | 514 | @override 515 | Offset getPositionForChild(Size size, Size childSize) { 516 | return new Offset(0.0, size.height - childSize.height); 517 | } 518 | 519 | @override 520 | bool shouldRelayout(_ToolbarContainerLayout oldDelegate) => false; 521 | } 522 | --------------------------------------------------------------------------------