├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ └── bug_report.md ├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README-zh.md ├── README.md ├── analysis_options.yaml ├── ci ├── ci-analysis_options.yaml ├── deploy ├── doc ├── app-release-arm64.apk ├── appbar-badge.gif ├── appbar-corner-fixed.png ├── appbar-corner.png ├── appbar-demo.gif ├── appbar-fixed-circle.gif ├── appbar-fixed.gif ├── appbar-flip.gif ├── appbar-gradient.gif ├── appbar-image.gif ├── appbar-react-circle.gif ├── appbar-react.gif ├── appbar-single-button.png ├── appbar-single-shape.png ├── appbar-textIn.gif ├── appbar-theming.png ├── appbar-titled.gif ├── badge-demo-preview.gif ├── badge-demo.mp4 ├── donate-kofi1.png ├── donate-wechat.jpeg ├── flutter-favorite.png ├── how-to-block-tab-event.md ├── issue-change-active-tab-index.md ├── issue-crash-on-flutter-dev-channel.md ├── issue-image-for-actionitem.md ├── issue-remove-elevation.md ├── preview.png ├── tab-hook.gif └── tabcontroller_pageview.gif ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── example │ │ │ │ │ └── convexappbar │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── images │ ├── sample-1-2.png │ ├── sample-1.png │ ├── sample-2-2.png │ ├── sample-2.png │ ├── sample-3-2.png │ ├── sample-3.png │ ├── sample-4-2.png │ ├── sample-4.png │ ├── sample-5-2.png │ └── sample-5.png ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h ├── lib │ ├── color_item_view.dart │ ├── components │ │ ├── chip_item.dart │ │ ├── choose_tab_item.dart │ │ ├── colors_item.dart │ │ ├── gradient_item.dart │ │ ├── heading.dart │ │ └── radio_item.dart │ ├── convex_button_demo.dart │ ├── custom_appbar_sample.dart │ ├── data.dart │ ├── default_appbar_demo.dart │ ├── main.dart │ └── model │ │ ├── badge.dart │ │ ├── choice_value.dart │ │ └── named_color.dart ├── pubspec.yaml └── web │ ├── favicon.png │ ├── icons │ ├── Icon-192.png │ └── Icon-512.png │ ├── index.html │ └── manifest.json ├── lib ├── convex_bottom_bar.dart └── src │ ├── bar.dart │ ├── chip_builder.dart │ ├── convex_shape.dart │ ├── fab.dart │ ├── interface.dart │ ├── item.dart │ ├── painter.dart │ ├── reused_gradient.dart │ ├── stack.dart │ └── style │ ├── blend_image_icon.dart │ ├── fixed_circle_tab_style.dart │ ├── fixed_tab_style.dart │ ├── flip_tab_style.dart │ ├── inner_builder.dart │ ├── internal_style_config.dart │ ├── react_circle_tab_style.dart │ ├── react_tab_style.dart │ ├── styles.dart │ ├── textin_tab_style.dart │ ├── titled_tab_style.dart │ ├── transition_container.dart │ └── transition_container_builder.dart ├── pubspec.yaml └── test ├── provider_test.dart ├── utils_test.dart └── widget_test.dart /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: hacktons 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Notice for Stack compile error 11 | ``` 12 | * Replace all Stack.overflow to Stack.clipBehavior. 13 | * The removal of [Stack.overflow][https://api.flutter.dev/flutter/widgets/Stack/overflow.html] is not a good idea, and the Flutter team has noticed that it would take time to remove all the usage without breaking Google. 14 | Since the overflow has been removed in 1.20 and be rolled back now(perhaps 1.22? not for sure). It's hard to say when it will be removed again, so just replace all overflow with clipBehavior. 15 | ``` 16 | --- 17 | 18 | > Please search the [issue list](https://github.com/hacktons/convex_bottom_bar/issues) and [FAQ list](https://github.com/hacktons/convex_bottom_bar#faq) before opening any issues!! 19 | 20 | **Describe the bug** 21 | 22 | A clear and concise description of what the bug is. 23 | 24 | **Environment details** 25 | 26 | Paste the flutter environment detail. 27 | ``` 28 | flutter doctor 29 | flutter --version 30 | ``` 31 | Paste the package version. 32 | ``` 33 | dependencies: 34 | convex_bottom_bar: x.y.z 35 | ``` 36 | 37 | **To Reproduce** 38 | 39 | Steps to reproduce the behavior: 40 | 1. Go to '...' 41 | 2. Click on '....' 42 | 3. Scroll down to '....' 43 | 4. See error 44 | 45 | **Expected behavior** 46 | 47 | A clear and concise description of what you expected to happen. 48 | 49 | **Screenshots** 50 | 51 | If applicable, add screenshots to help explain your problem. 52 | 53 | **Additional context** 54 | 55 | Add any other context about the problem here. 56 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | build/ 31 | pubspec.lock 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Generated.xcconfig 62 | **/ios/Flutter/app.flx 63 | **/ios/Flutter/app.zip 64 | **/ios/Flutter/flutter_assets/ 65 | **/ios/Flutter/flutter_export_environment.sh 66 | **/ios/ServiceDefinitions.json 67 | **/ios/Runner/GeneratedPluginRegistrant.* 68 | **/ios/Podfile.lock 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 76 | # Pana report 77 | pana_visual/ 78 | coverage/ 79 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: cc949a8e8b9cf394b9290a8e80f87af3e207dce5 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [3.2.0] 2 | * Support flutter 3.7 3 | 4 | ## [3.1.0+1] 5 | * Format project with `flutter format .` 6 | 7 | ## [3.1.0] 8 | * Bug fix; 9 | * New API to set shadow color 10 | * Fix lint warning 11 | 12 | ## [3.0.0-nullsafety.1] 13 | * Prepare for Flutter 2. 14 | 15 | ## [2.7.1+2] 16 | * format 17 | 18 | ## [2.7.1+1] 19 | * Bug fix [Manual update tab index workaround seems not working since version 2.7.x](https://github.com/hacktons/convex_bottom_bar/issues/134) 20 | 21 | ## [2.7.1] 22 | * fix runtime exception when there is no controller passed in 23 | 24 | ## [2.7.0+1] 25 | * add new configuration `disableDefaultTabController` 26 | 27 | ## [3.0.0-nullsafety.0] 28 | 29 | * Migrate to [null-safety](https://dart.dev/null-safety/migration-guide). 30 | 31 | ## [2.6.0] 32 | * Fix tab highlight state to satisfy keyboard and router case; [#115](https://github.com/hacktons/convex_bottom_bar/issues/115), [#112](https://github.com/hacktons/convex_bottom_bar/issues/112) 33 | * Breaking changes: disable initial index property when working with TabController to avoid potential index conflict; 34 | 35 | ## [2.5.1+1] 36 | * Bug fix [#111 Tab selection not registered during tab transition](https://github.com/hacktons/convex_bottom_bar/issues/111) 37 | 38 | ## [2.5.1] 39 | 40 | * Replace all Stack.overflow to Stack.clipBehavior. 41 | * The removal of [Stack.overflow][https://api.flutter.dev/flutter/widgets/Stack/overflow.html] is not a good idea, and the Flutter team has noticed that it would take time to remove all the usage without breaking Google. 42 | Since the overflow has been removed in 1.20 and be rolled back now(perhaps 1.22? not for sure). It's hard to say when it will be removed again, so just replace all overflow with clipBehavior. 43 | 44 | ## [2.5.0] 45 | * Support hook api for tab event. [How to block tab event?](https://github.com/hacktons/convex_bottom_bar/blob/master/doc/how-to-block-tab-event.md) 46 | 47 | ## [2.4.2] 48 | * Bug fix [#102 dispose is executed more than once](https://github.com/hacktons/convex_bottom_bar/issues/102) 49 | * Set min version of flutter to 1.12.0 50 | 51 | ## [2.4.1] 52 | * Fix badge alignment issues [#77](https://github.com/hacktons/convex_bottom_bar/issues/77). 53 | 54 | ## [2.4.0] 55 | * Support flutter v1.20, the v2.2.4-flutter-1.20 is now sync with v2.4.0. 56 | * Add new feature. We can now add corner on AppBars's background. 57 | 58 | ## [2.3.0] 59 | * Add convex button widget. 60 | 61 | ## [2.2.5+1] 62 | 63 | * fix format issue. 64 | ``` 65 | lib/src/bar.dart is not formatted according to dartfmt 66 | To format your files run: dartfmt -w . 67 | ``` 68 | 69 | ## [2.2.5] 70 | 71 | * Bug fix [#67](https://github.com/hacktons/convex_bottom_bar/issues/67) 72 | 73 | ## [2.2.4-flutter-1.20] 74 | * support the flutter v1.20 75 | * fix: overflow property of stack deprecated [#60](https://github.com/hacktons/convex_bottom_bar/pull/60) 76 | 77 | ## [2.2.4] 78 | * Polish internal style, avoid reanimate when AppBar rebuild. 79 | 80 | ## [2.2.3] 81 | 82 | * Bug fix [#59](https://github.com/hacktons/convex_bottom_bar/issues/59) 83 | * Bug fix: default index not match with DefaultTabController. 84 | 85 | ## [2.2.2] 86 | * Fix activate index when appbar is not working with controller; 87 | * Dispose AnimationController when is dirty; 88 | 89 | ## [2.2.1] 90 | * Support RTL; 91 | 92 | ## [2.2.0] 93 | 94 | * Add new API to hook internal styles. Checkout `StyleProvider` for details; 95 | * Provide configuration to hide Text widget when label is empty; 96 | 97 | ## [2.1.1] 98 | 99 | * Fix ChipBuilder missing issue; 100 | * Polish ci test; 101 | 102 | ## [2.1.0+1] 103 | 104 | * Document all the public APIs; 105 | 106 | ## [2.1.0] 107 | 108 | * Add controller for Appbar to change tab index programmaticlly; 109 | * Support with framework's DefaultTabController and TabController; 110 | * Enable access to the ConvexAppBarState; 111 | * Fix active tab position when using even menus; 112 | 113 | ## [2.0.3] 114 | 115 | * Fix hitTest for active tab; 116 | 117 | ## [2.0.2] 118 | 119 | * Add new config parameter for initial active index; 120 | 121 | ## [2.0.1] 122 | 123 | * Update usage instructions in README.md; 124 | * Add new test cases to improve the code coverage; 125 | * Bug fix; 126 | 127 | ## [2.0.0] 128 | 129 | * Support badge on tab item; 130 | * Constructor update, rename the builder; 131 | * Bug fix; 132 | 133 | ## [1.4.1] 134 | 135 | * Add titled style; 136 | * Remove some redundant widget layers; 137 | 138 | ## [1.4.0] 139 | 140 | * Add flip style; 141 | * Add textIn style; 142 | 143 | ## [1.3.1+1] 144 | 145 | * Bug fix: activate icon is not working in some style; 146 | * Improvement: enable image with/without color blend; 147 | 148 | ## [1.3.1] 149 | 150 | * Support gradient background; 151 | * Tab item are generic type, both `IconData` and `Widget` can be used; 152 | 153 | ## [1.3.0] 154 | 155 | * Add new tab style; 156 | * Support animated tab transition; 157 | 158 | ## [1.2.0] 159 | 160 | * Support iPhoneX' safe area at bottom edge. [#issues/7](https://github.com/hacktons/convex_bottom_bar/issues/7) 161 | 162 | ## [1.1.0] 163 | 164 | * Add elevation attribute. 165 | 166 | ## [1.0.2+1] 167 | 168 | * Documentation fixes. 169 | 170 | ## [1.0.2] 171 | 172 | * Fix maintenance suggestions to get higher score 173 | 174 | ## [0.0.1] 175 | 176 | * Publish the packages to pub.dev 177 | 178 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README-zh.md: -------------------------------------------------------------------------------- 1 |

appBar preview

2 |

3 | pub.dev 4 | github 5 | coverage 6 | likes 7 | popularity 8 | build status 9 | license 10 |

11 |

12 | English 13 | | 简体中文 14 |

15 | 16 | --- 17 | 18 | ConvexBottomBar是一个底部导航栏组件,用于展现凸起的TAB效果,支持多种内置样式与动画交互。你可以在[https://appbar.codemagic.app](https://appbar.codemagic.app)上找到在线样例。 19 | 20 | **convex_bottom_bar 现在是一个 [Flutter Favorite](https://flutter.dev/docs/development/packages-and-plugins/favorites) 插件库!** 21 | 22 | 23 | 24 | 25 | 26 | 以下是一些支持的预定义样式: 27 | 28 | | **fixed** | **react** | **badge chip** | 29 | |:---------------------------------:|:--------------------------------:|:-------------------------:| 30 | | ![](doc/appbar-fixed.gif) | ![](doc/appbar-react.gif) | ![](doc/appbar-badge.gif) | 31 | | **fixedCircle** | **reactCircle** | **flip** | 32 | | ![](doc/appbar-fixed-circle.gif) | ![](doc/appbar-react-circle.gif) | ![](doc/appbar-flip.gif) | 33 | | **textIn** | **titled** | **tab image** | 34 | | ![](doc/appbar-textIn.gif) | ![](doc/appbar-titled.gif) | ![](doc/appbar-image.gif) | 35 | | **button** | **fixed corner** | | 36 | | ![](doc/appbar-single-button.png) | ![](doc/appbar-corner-fixed.png) | | 37 | 38 | ## 快速上手 39 | 40 | 通常ConvexAppBar可以通过设置bottomNavigationBar与Scaffold一起使用。 41 | 42 | ConvexAppBar具有两个构造函数,ConvexAppBar()将使用默认样式来简化选项卡的创建。 43 | 44 | 将此添加到您程序包的pubspec.yaml文件中,注意使用最新版本[![Pub](https://img.shields.io/pub/v/convex_bottom_bar.svg)](https://pub.dartlang.org/packages/convex_bottom_bar): 45 | 46 | ```yaml 47 | dependencies: 48 | convex_bottom_bar: ^latest_version 49 | ``` 50 | 51 | ```dart 52 | import 'package:convex_bottom_bar/convex_bottom_bar.dart'; 53 | 54 | Scaffold( 55 | bottomNavigationBar: ConvexAppBar( 56 | items: [ 57 | TabItem(icon: Icons.home, title: 'Home'), 58 | TabItem(icon: Icons.map, title: 'Discovery'), 59 | TabItem(icon: Icons.add, title: 'Add'), 60 | TabItem(icon: Icons.message, title: 'Message'), 61 | TabItem(icon: Icons.people, title: 'Profile'), 62 | ], 63 | onTap: (int i) => print('click index=$i'), 64 | ) 65 | ); 66 | ``` 67 | 68 | **Flutter Version Support** 69 | 由于Flutter迭代非常快。SDK本身有可能出现不兼容的API变更,我们将继续支持flutter稳定版本,非稳定的beta、dev channel 70 | 通过单独版本号进行兼容。 71 | 72 | | **Stable Flutter Version** | **Package Version** | **More** | 73 | |:--------------------------:|:-------------------:|:------------------------------------------:| 74 | | >=3.7.0 | >=3.2.0 | 从v3.7.0版本DefaultTabController的API有变更 | 75 | | >=1.20 | >=2.4.0 | 从 v1.20开始, Stack组件的API发送不兼容变更 | 76 | | <1.20 | <2.4.0 | v1.20稳定版发布后,我们对老版本如v1.17, v1.12 的支持将不再继续更新 | 77 | 78 | 如果你只需要一个单独的按钮,不妨试试 `ConvexButton`. 79 | 80 | ## 功能 81 | 82 | * 提供多种内部样式 83 | * 能够更改AppBar的主题 84 | * 提供Builder API以自定义新样式 85 | * 在AppBar上添加徽章 86 | * 支持优雅的过渡动画 87 | * 提供Hook API来重载一些内部样式 88 | * RTL布局支持 89 | 90 | ## Table of contents 91 | 92 | - [主题](#主题) 93 | - [角标](#角标) 94 | - [单独按钮](#单独按钮) 95 | - [样式重载](#样式重载) 96 | - [RTL支持](#RTL支持) 97 | - [自定义样例](#自定义样例) 98 | - [常见问题](#常见问题) 99 | - [支持](#支持) 100 | 101 | ## 角标 102 | 如果需要在TAB上添加徽章/角标,请使用`ConvexAppBar.badge`来构建。 103 | 104 | [![badge demo](doc/badge-demo-preview.gif)](doc/badge-demo.mp4 "badge demo") 105 | 106 | ```dart 107 | ConvexAppBar.badge({0: '99+', 1: Icons.assistant_photo, 2: Colors.redAccent}, 108 | items: [ 109 | TabItem(icon: Icons.home, title: 'Home'), 110 | TabItem(icon: Icons.map, title: 'Discovery'), 111 | TabItem(icon: Icons.add, title: 'Add'), 112 | ], 113 | onTap: (int i) => print('click index=$i'), 114 | ); 115 | ``` 116 | 117 | `badge()`方法接受一个角标数组; 角标是带有选项卡项的映射,每个条目的值可以是String,IconData,Color或Widget。 118 | 119 | ## 单独按钮 120 | ![button](doc/appbar-single-shape.png) 121 | 122 | ```dart 123 | Scaffold( 124 | appBar: AppBar(title: const Text('ConvexButton Example')), 125 | body: Center(child: Text('count $count')), 126 | bottomNavigationBar: ConvexButton.fab( 127 | onTap: () => setState(() => count++), 128 | ), 129 | ); 130 | ``` 131 | 132 | ## 主题 133 | AppBar默认使用内置样式,您可能需要为其设置主题。 以下是一些支持的属性: 134 | 135 | ![](doc/appbar-theming.png) 136 | 137 | | Attributes | Description | 138 | |-----------------|--------------------------------------------------------| 139 | | backgroundColor | AppBar 背景 | 140 | | gradient | 渐变属性,可以覆盖backgroundColor | 141 | | height | AppBar 高度 | 142 | | color | icon/text 的颜色值 | 143 | | activeColor | icon/text 的**选中态**颜色值 | 144 | | curveSize | 凸形大小 | 145 | | top | 凸形到AppBar上边缘的距离 | 146 | | style | 支持的样式: **fixed, fixedCircle, react, reactCircle**, ... | 147 | | chipBuilder | 角标构造器builder, **ConvexAppBar.badge**会使用默认样式 | 148 | 149 | ## 样式重载 150 | 重载Tab内置样式。 该API与`ConvexAppBar.builder`不同,为了满足您可能需要更新选项卡样式而不定义新的选项卡样式。 151 | **温馨提示:** 152 | 则此Hook能力是有限的,如果您提供的尺寸与内部样式不匹配,并且可能导致`overflow broken`。 153 | 154 | ```dart 155 | StyleProvider( 156 | style: Style(), 157 | child: ConvexAppBar( 158 | initialActiveIndex: 1, 159 | height: 50, 160 | top: -30, 161 | curveSize: 100, 162 | style: TabStyle.fixedCircle, 163 | items: [ 164 | TabItem(icon: Icons.link), 165 | TabItem(icon: Icons.import_contacts), 166 | TabItem(title: "2020", icon: Icons.work), 167 | ], 168 | backgroundColor: _tabBackgroundColor, 169 | ), 170 | ) 171 | class Style extends StyleHook { 172 | @override 173 | double get activeIconSize => 40; 174 | 175 | @override 176 | double get activeIconMargin => 10; 177 | 178 | @override 179 | double get iconSize => 20; 180 | 181 | @override 182 | TextStyle textStyle(Color color) { 183 | return TextStyle(fontSize: 20, color: color); 184 | } 185 | } 186 | ``` 187 | 188 | ## RTL支持 189 | RTL的内部适配,如果你配置了Directionality,将自动支持ltf和rtl两种布局风格。 190 | ```dart 191 | Directionality( 192 | textDirection: TextDirection.rtl, 193 | child: Scaffold(body:ConvexAppBar(/*TODO ...*/)), 194 | ) 195 | ``` 196 | 197 | ## 自定义样例 198 | 199 | 如果默认样式与您的情况不符,请尝试使用`ConvexAppBar.builder()`,它可以让您自定义几乎所有TAB样式。 200 | ```dart 201 | Scaffold( 202 | bottomNavigationBar: ConvexAppBar.builder( 203 | count: 5, 204 | backgroundColor: Colors.blue, 205 | itemBuilder: Builder(), 206 | ) 207 | ); 208 | 209 | /*user defined class*/ 210 | class Builder extends DelegateBuilder { 211 | @override 212 | Widget build(BuildContext context, int index, bool active) { 213 | return Text('TAB $index'); 214 | } 215 | } 216 | ``` 217 | 218 | 完整的自定义示例可以在[example](example)中找到。 219 | 220 | ## 常见问题 221 | 如您在使用过程中有新功能建议或者遇到问题,请移步至[issue tracker](https://github.com/hacktons/convex_bottom_bar/issues)提交。 222 | 223 | * [如何拦截Tab事件](doc/how-to-block-tab-event.md) 224 | * [Flutter dev/beta channel运行崩溃](doc/issue-crash-on-flutter-dev-channel.md) 225 | * [动态修改选中的TAB](doc/issue-change-active-tab-index.md) 226 | * [如何给TAB添加图片而不是ICON](doc/issue-image-for-actionitem.md) 227 | * [如何移除AppBar的边缘阴影](doc/issue-remove-elevation.md) 228 | 229 | ## 支持 230 | 如果对你有帮助,微信扫码请作者喝杯咖啡 :) 231 | 232 | ![donate](doc/donate-wechat.jpeg) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

appBar preview

2 |

3 | pub.dev 4 | github 5 | coverage 6 | likes 7 | popularity 8 | build status 9 | license 10 |

11 |

12 | English 13 | | 简体中文 14 |

15 | 16 | --- 17 | 18 | The official BottomAppBar can only display a notch FAB with an app bar, and sometimes we need a convex FAB. BottomAppBar and NotchShape's implementation inspires this ConvexAppBar. 19 | 20 | Online example can be found at [https://appbar.codemagic.app](https://appbar.codemagic.app). 21 | 22 | **convex_bottom_bar is now a [Flutter Favorite](https://flutter.dev/docs/development/packages-and-plugins/favorites) package!** 23 | 24 | 25 | 26 | 27 | 28 | Here are some supported style: 29 | 30 | | **fixed** | **react** | **badge chip** | 31 | |:---------------------------------:|:--------------------------------:|:-------------------------:| 32 | | ![](doc/appbar-fixed.gif) | ![](doc/appbar-react.gif) | ![](doc/appbar-badge.gif) | 33 | | **fixedCircle** | **reactCircle** | **flip** | 34 | | ![](doc/appbar-fixed-circle.gif) | ![](doc/appbar-react-circle.gif) | ![](doc/appbar-flip.gif) | 35 | | **textIn** | **titled** | **tab image** | 36 | | ![](doc/appbar-textIn.gif) | ![](doc/appbar-titled.gif) | ![](doc/appbar-image.gif) | 37 | | **button** | **fixed corner** | | 38 | | ![](doc/appbar-single-button.png) | ![](doc/appbar-corner-fixed.png) | | 39 | 40 | ## How to use 41 | Typically ConvexAppBar can work with `Scaffold` by setup its `bottomNavigationBar`. 42 | 43 | The `ConvexAppBar` has two constructors. The `ConvexAppBar()` will use the default style to simplify the tab creation. 44 | 45 | Add this to your package's pubspec.yaml file, use the latest version [![Pub](https://img.shields.io/pub/v/convex_bottom_bar.svg)](https://pub.dartlang.org/packages/convex_bottom_bar): 46 | 47 | ```yaml 48 | dependencies: 49 | convex_bottom_bar: ^latest_version 50 | ``` 51 | 52 | ```dart 53 | import 'package:convex_bottom_bar/convex_bottom_bar.dart'; 54 | 55 | Scaffold( 56 | bottomNavigationBar: ConvexAppBar( 57 | items: [ 58 | TabItem(icon: Icons.home, title: 'Home'), 59 | TabItem(icon: Icons.map, title: 'Discovery'), 60 | TabItem(icon: Icons.add, title: 'Add'), 61 | TabItem(icon: Icons.message, title: 'Message'), 62 | TabItem(icon: Icons.people, title: 'Profile'), 63 | ], 64 | onTap: (int i) => print('click index=$i'), 65 | ) 66 | ); 67 | ``` 68 | 69 | **Flutter Version Support** 70 | As Flutter is developing fast. There can be breaking changes. We will be trying to support the 71 | stable version and beta version through different package versions. 72 | 73 | | **Stable Flutter Version** | **Package Version** | **More** | 74 | |:--------------------------:|:-------------------:|:--------------------------------------------------------------------------:| 75 | | >=3.7.0 | >=3.2.0 | Since v3.7.0, the stable version changed the DefaultTabController api | 76 | | >=1.20 | >=2.4.0 | Since v1.20, the stable version changed the Stack api | 77 | | <1.20 | <=2.3.0 | Support for stable version such as v1.17, v1.12 is not going to be updated | 78 | 79 | ## Features 80 | * Provide multiple internal styles 81 | * Ability to change the theme of AppBar 82 | * Provide builder API to customize a new style 83 | * Add badge on the tab menu 84 | * Elegant transition animation 85 | * Provide hook API to override some of the internal styles 86 | * RTL support 87 | 88 | ## Table of contents 89 | 90 | - [Theming](#theming) 91 | - [Badge](#badge) 92 | - [Single Button](#single-button) 93 | - [Style Hook](#style-hook) 94 | - [RTL Support](#rtl-support) 95 | - [Custom Example](#custom-example) 96 | - [FAQ](#faq) 97 | - [Donate](#donate) 98 | 99 | ## Theming 100 | The bar will use default style, you may want to theme it. Here are some supported attributes: 101 | 102 | ![](doc/appbar-theming.png) 103 | 104 | | Attributes | Description | 105 | |-----------------|--------------------------------------------------------------------------------------| 106 | | backgroundColor | AppBar background | 107 | | gradient | gradient will override backgroundColor | 108 | | height | AppBar height | 109 | | color | tab icon/text color | 110 | | activeColor | tab icon/text color **when selected** | 111 | | curveSize | size of the convex shape | 112 | | top | top edge of the convex shape relative to AppBar | 113 | | cornerRadius | draw the background with topLeft and topRight corner; Only work with fixed tab style | 114 | | style | style to describe the convex shape: **fixed, fixedCircle, react, reactCircle**, ... | 115 | | chipBuilder | custom badge builder, use **ConvexAppBar.badge** for default badge | 116 | 117 | ## Badge 118 | 119 | If you need to add a badge on the tab, use the `ConvexAppBar.badge` to get it done. 120 | 121 | [![badge demo](doc/badge-demo-preview.gif)](doc/badge-demo.mp4 "badge demo") 122 | 123 | ```dart 124 | ConvexAppBar.badge({0: '99+', 1: Icons.assistant_photo, 2: Colors.redAccent}, 125 | items: [ 126 | TabItem(icon: Icons.home, title: 'Home'), 127 | TabItem(icon: Icons.map, title: 'Discovery'), 128 | TabItem(icon: Icons.add, title: 'Add'), 129 | ], 130 | onTap: (int i) => print('click index=$i'), 131 | ); 132 | ``` 133 | 134 | The `badge()` method accepts an array of badges; The `badges` is a map with tab items. Each value of entry can be either `String`, `IconData`, `Color` or `Widget`. 135 | 136 | ## Single Button 137 | 138 | If you only need a single button, checkout the `ConvexButton`. 139 | 140 | ![button](doc/appbar-single-shape.png) 141 | 142 | ```dart 143 | Scaffold( 144 | appBar: AppBar(title: const Text('ConvexButton Example')), 145 | body: Center(child: Text('count $count')), 146 | bottomNavigationBar: ConvexButton.fab( 147 | onTap: () => setState(() => count++), 148 | ), 149 | ); 150 | ``` 151 | 152 | ## Style Hook 153 | Hook for internal tab style. Unlike the `ConvexAppBar.builder`, you may want to update the tab style without defining a new tab style. 154 | 155 | **Warning:** 156 | This hook is limited and can lead to `overflow broken` if the size you provide does not match with internal style. 157 | 158 | ```dart 159 | StyleProvider( 160 | style: Style(), 161 | child: ConvexAppBar( 162 | initialActiveIndex: 1, 163 | height: 50, 164 | top: -30, 165 | curveSize: 100, 166 | style: TabStyle.fixedCircle, 167 | items: [ 168 | TabItem(icon: Icons.link), 169 | TabItem(icon: Icons.import_contacts), 170 | TabItem(title: "2020", icon: Icons.work), 171 | ], 172 | backgroundColor: _tabBackgroundColor, 173 | ), 174 | ) 175 | class Style extends StyleHook { 176 | @override 177 | double get activeIconSize => 40; 178 | 179 | @override 180 | double get activeIconMargin => 10; 181 | 182 | @override 183 | double get iconSize => 20; 184 | 185 | @override 186 | TextStyle textStyle(Color color) { 187 | return TextStyle(fontSize: 20, color: color); 188 | } 189 | } 190 | ``` 191 | 192 | ## RTL Support 193 | RTL is supported internally, and if you define the TextDirection inside the app, the AppBar should work fine. 194 | Both RTL and LTR can be configured through `Directionality`: 195 | ```dart 196 | Directionality( 197 | textDirection: TextDirection.rtl, 198 | child: Scaffold(body:ConvexAppBar(/*TODO ...*/)), 199 | ) 200 | ``` 201 | 202 | ## Custom Example 203 | 204 | If the default style does not match your situation, try with `ConvexAppBar.builder()`, allowing you to custom nearly all the tab features. 205 | 206 | ```dart 207 | Scaffold( 208 | bottomNavigationBar: ConvexAppBar.builder( 209 | count: 5, 210 | backgroundColor: Colors.blue, 211 | itemBuilder: Builder(), 212 | ) 213 | ); 214 | 215 | // user defined class 216 | class Builder extends DelegateBuilder { 217 | @override 218 | Widget build(BuildContext context, int index, bool active) { 219 | return Text('TAB $index'); 220 | } 221 | } 222 | ``` 223 | 224 | Full custom example can be found at [example](example). 225 | 226 | ## FAQ 227 | Please file feature requests and bugs at the [issue tracker](https://github.com/hacktons/convex_bottom_bar/issues). 228 | 229 | * [How to block tab event?](doc/how-to-block-tab-event.md) 230 | * [Crash on flutter dev/beta channel](doc/issue-crash-on-flutter-dev-channel.md) 231 | * [Change active tab index programmatically](doc/issue-change-active-tab-index.md) 232 | * [Using an image instead of an icon for actionItem](doc/issue-image-for-actionitem.md) 233 | * [Is there anyway to remove elevation in the bottom bar?](doc/issue-remove-elevation.md) 234 | 235 | ## Donate 236 | You like the package ? Buy me a coffee :) 237 | 238 | [![ko-fi](doc/donate-kofi1.png)](https://ko-fi.com/hacktons) 239 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # Defines a default set of lint rules enforced for 2 | # projects at Google. For details and rationale, 3 | # see https://github.com/dart-lang/pedantic#enabled-lints. 4 | include: package:pedantic/analysis_options.yaml 5 | 6 | linter: 7 | rules: 8 | - public_member_api_docs 9 | analyzer: 10 | exclude: 11 | - example/** 12 | - test/** 13 | -------------------------------------------------------------------------------- /ci: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | ################################################################## 3 | ## 4 | ## Test case for CI building 5 | ## 6 | ## 7 | ## Author: Chaobin Wu 8 | ## Email : chaobinwu89@gmail.com 9 | ## 10 | ################################################################# 11 | tag='[CI]' 12 | error() { 13 | echo -e "\033[1m$tag\033[0m \033[31m$*\033[0m" 14 | } 15 | info() { 16 | echo -e "\033[1m$tag\033[0m \033[32m$*\033[0m" 17 | } 18 | warning() { 19 | echo -e "\033[1m$tag\033[0m \033[33m$*\033[0m" 20 | } 21 | 22 | die() { 23 | error "$*" 24 | exit 1 25 | } 26 | info "Step 1 Run custom lint rules" 27 | dart analyze lib ci-analysis_options.yaml >build/lint-result.txt 28 | # n lints found. => issue found, No issues found! => success 29 | count=$(grep -c 'lints found.' Original designed for [#issue 98](https://github.com/hacktons/convex_bottom_bar/issues/98) 8 | 9 | ## Sample 10 | ![tab-hook.gif](tab-hook.gif) 11 | 12 | ```dart 13 | DefaultTabController( 14 | length: items.length, 15 | child: Scaffold( 16 | appBar: AppBar(title: const Text('Custom ConvexAppBar')), 17 | body: TabBarView( 18 | physics: NeverScrollableScrollPhysics(), 19 | children: items.map((i) => Center(child: Text(i.title))).toList(), 20 | ), 21 | bottomNavigationBar: ConvexAppBar( 22 | style: TabStyle.fixedCircle, 23 | items: [ 24 | TabItem(title: '2019', icon: Icons.link), 25 | TabItem( 26 | icon: Container( 27 | decoration: BoxDecoration( 28 | shape: BoxShape.circle, 29 | color: Color(0xFFFF5722), 30 | ), 31 | child: Icon(Icons.add, color: Colors.white, size: 40), 32 | )), 33 | TabItem(title: "2020", icon: Icons.work), 34 | ], 35 | onTabNotify: (i) { 36 | var intercept = i == 1; 37 | if (intercept) { 38 | Navigator.pushNamed(context, '/fab'); 39 | } 40 | return !intercept; 41 | }, 42 | onTap: (i) => debugPrint('click $i'), 43 | ), 44 | )) 45 | ``` 46 | -------------------------------------------------------------------------------- /doc/issue-change-active-tab-index.md: -------------------------------------------------------------------------------- 1 | # Change active tab index programmaticlly 2 | 3 | There ae some cases that you may want to change the activate tab index; 4 | * define a custom initial index 5 | * change index according to PageView/TabBarView 6 | 7 | ## Change initial index 8 | 9 | The `ConvexAppbar` are exposed with `initialActivieIndex`, this value will be used when the appbar are constructed. 10 | 11 | ## Work with PageView/TabBarView 12 | The TabBarView use PageView internal, both support swipe gesture to change current page content; 13 | 14 | ![](tabcontroller_pageview.gif) 15 | 16 | ConvexAppBar can work with `TabController` similar with `TabBar`; 17 | 18 | In order to change the index of tab item, config the AppBar with instance of `TabController`; To simplify the code, you can use `DefaultTabController`: 19 | 20 | **Example 1** 21 | ```dart 22 | DefaultTabController( 23 | length: 5, 24 | child: Scaffold( 25 | appBar: AppBar(title: const Text('Custom ConvexAppBar')), 26 | body: TabBarView( 27 | children: ['A','B','C','D','E'] 28 | .map((i) => Center(child: Text('$i'))) 29 | .toList(growable: false), 30 | ), 31 | bottomNavigationBar: ConvexAppBar(/* some config*/), 32 | ), 33 | ); 34 | ``` 35 | 36 | **Example 2** 37 | ```dart 38 | Scaffold( 39 | appBar: AppBar(title: const Text('Custom ConvexAppBar')), 40 | body: TabBarView( 41 | controller: _tabController, 42 | children: ['A','B','C','D','E'] 43 | .map((i) => Center(child: Text('$i'))) 44 | .toList(growable: false), 45 | ), 46 | bottomNavigationBar: ConvexAppBar(controller: _tabController/* some config*/), 47 | ); 48 | ``` 49 | ## The raw way 50 | If you don't use TabController at all, then you have to update tab index manually through `ConvexAppBarState`. 51 | This usually requires a defined `GlobalKey` set with `ConvexAppBar`: 52 | 53 | ```dart 54 | // define field instance 55 | GlobalKey _appBarKey = GlobalKey(); 56 | // construct with key 57 | ConvexAppBar(key: _appBarKey, /* ... */); 58 | // access related State when necessary such as onPageChanged 59 | _appBarKey.currentState.animateTo(2/* index*/); 60 | ``` -------------------------------------------------------------------------------- /doc/issue-crash-on-flutter-dev-channel.md: -------------------------------------------------------------------------------- 1 | # Crash on Flutter dev/beta channel 2 | 3 | As Flutter is developing fast. There can be breaking changes, we will trying to support the 4 | stable version and beta version through different package version. 5 | 6 | If you're using unstable Flutter channel, please follow the table bellow to use the proper package version. 7 | 8 | | **Stable Flutter Version** | **Package Version** | **More** | 9 | | :------------------------: | :-----------------: | :----------------------------------------------------------: | 10 | | >=1.20 | >=2.4.0 | Since v1.20, the stable version changed the Stack api | 11 | | <1.20 | <=2.3.0 | Support for stable version such as v1.17, v1.12 is not going to be updated | -------------------------------------------------------------------------------- /doc/issue-image-for-actionitem.md: -------------------------------------------------------------------------------- 1 | # Using an image instead of an icon for actionItem 2 | 3 | `TabItem` support both IconData and Widget. 4 | 5 | ```dart 6 | TabItem(icon: Image.asset('images/sample.png'), title: 'Discovery'), 7 | ``` -------------------------------------------------------------------------------- /doc/issue-remove-elevation.md: -------------------------------------------------------------------------------- 1 | # Is there anyway to remove elevation in the bottom bar 2 | 3 | You can remove the shadow by set elevation to 0. 4 | 5 | ```dart 6 | ConvexAppBar(elevation: 0); 7 | ``` 8 | 9 | -------------------------------------------------------------------------------- /doc/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/doc/preview.png -------------------------------------------------------------------------------- /doc/tab-hook.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/doc/tab-hook.gif -------------------------------------------------------------------------------- /doc/tabcontroller_pageview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/doc/tabcontroller_pageview.gif -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | 32 | # Android related 33 | **/android/**/gradle-wrapper.jar 34 | **/android/.gradle 35 | **/android/captures/ 36 | **/android/gradlew 37 | **/android/gradlew.bat 38 | **/android/local.properties 39 | **/android/**/GeneratedPluginRegistrant.java 40 | 41 | # iOS/XCode related 42 | **/ios/**/*.mode1v3 43 | **/ios/**/*.mode2v3 44 | **/ios/**/*.moved-aside 45 | **/ios/**/*.pbxuser 46 | **/ios/**/*.perspectivev3 47 | **/ios/**/*sync/ 48 | **/ios/**/.sconsign.dblite 49 | **/ios/**/.tags* 50 | **/ios/**/.vagrant/ 51 | **/ios/**/DerivedData/ 52 | **/ios/**/Icon? 53 | **/ios/**/Pods/ 54 | **/ios/**/.symlinks/ 55 | **/ios/**/profile 56 | **/ios/**/xcuserdata 57 | **/ios/.generated/ 58 | **/ios/Flutter/App.framework 59 | **/ios/Flutter/Flutter.framework 60 | **/ios/Flutter/Generated.xcconfig 61 | **/ios/Flutter/app.flx 62 | **/ios/Flutter/app.zip 63 | **/ios/Flutter/flutter_assets/ 64 | **/ios/Flutter/flutter_export_environment.sh 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | 75 | pubspec.lock -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: cc949a8e8b9cf394b9290a8e80f87af3e207dce5 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # convex_bottom_bar_example 2 | 3 | Demonstrates how to use the convex_bottom_bar plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | ndkVersion flutter.ndkVersion 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 47 | applicationId "com.example.convexappbar" 48 | // You can update the following values to match your application needs. 49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 50 | minSdkVersion flutter.minSdkVersion 51 | targetSdkVersion flutter.targetSdkVersion 52 | versionCode flutterVersionCode.toInteger() 53 | versionName flutterVersionName 54 | } 55 | 56 | buildTypes { 57 | release { 58 | // TODO: Add your own signing config for the release build. 59 | // Signing with the debug keys for now, so `flutter run --release` works. 60 | signingConfig signingConfigs.debug 61 | } 62 | } 63 | } 64 | 65 | flutter { 66 | source '../..' 67 | } 68 | 69 | dependencies { 70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 71 | } 72 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/convexappbar/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.convexappbar 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.10' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/images/sample-1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/images/sample-1-2.png -------------------------------------------------------------------------------- /example/images/sample-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/images/sample-1.png -------------------------------------------------------------------------------- /example/images/sample-2-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/images/sample-2-2.png -------------------------------------------------------------------------------- /example/images/sample-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/images/sample-2.png -------------------------------------------------------------------------------- /example/images/sample-3-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/images/sample-3-2.png -------------------------------------------------------------------------------- /example/images/sample-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/images/sample-3.png -------------------------------------------------------------------------------- /example/images/sample-4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/images/sample-4-2.png -------------------------------------------------------------------------------- /example/images/sample-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/images/sample-4.png -------------------------------------------------------------------------------- /example/images/sample-5-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/images/sample-5-2.png -------------------------------------------------------------------------------- /example/images/sample-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/images/sample-5.png -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | AppBarDemo 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/lib/color_item_view.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 chaobinwu89@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/cupertino.dart'; 18 | import 'package:flutter/material.dart'; 19 | 20 | class ColorItemView extends StatelessWidget { 21 | final Color color; 22 | 23 | ColorItemView(this.color); 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Container( 28 | color: color, 29 | alignment: Alignment.center, 30 | child: Text( 31 | color.value.toRadixString(16).toUpperCase(), 32 | style: const TextStyle(color: Colors.white), 33 | ), 34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /example/lib/components/chip_item.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 chaobinwu89@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/cupertino.dart'; 18 | import 'package:flutter/material.dart'; 19 | 20 | import '../model/badge.dart'; 21 | 22 | class ChipItem extends StatelessWidget { 23 | const ChipItem(this.chips, this.selectedChip, this.onChanged); 24 | 25 | final List chips; 26 | final SampleBadge? selectedChip; 27 | final ValueChanged onChanged; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Row( 32 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 33 | children: chips.map((SampleBadge? chip) { 34 | return GestureDetector( 35 | onTap: () => onChanged(chip), 36 | child: Container( 37 | height: 40, 38 | width: 80, 39 | padding: EdgeInsets.all(8.0), 40 | decoration: BoxDecoration( 41 | border: Border.all( 42 | color: chip == selectedChip 43 | ? Colors.black 44 | : const Color(0xFFD5D7DA), 45 | width: 2), 46 | ), 47 | child: chip == null 48 | ? Center(child: Text('clear')) 49 | : Center( 50 | child: Material( 51 | shape: RoundedRectangleBorder( 52 | borderRadius: 53 | BorderRadius.circular(chip.borderRadius ?? 20), 54 | ), 55 | type: MaterialType.card, 56 | color: chip.badgeColor ?? Colors.redAccent, 57 | child: Padding( 58 | padding: 59 | chip.padding ?? EdgeInsets.only(left: 4, right: 4), 60 | child: Text( 61 | chip.text, 62 | style: const TextStyle(color: Colors.white), 63 | ), 64 | ), 65 | ), 66 | ), 67 | ), 68 | ); 69 | }).toList(), 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /example/lib/components/choose_tab_item.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 chaobinwu89@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:convex_bottom_bar/convex_bottom_bar.dart'; 18 | import 'package:flutter/cupertino.dart'; 19 | import 'package:flutter/material.dart'; 20 | 21 | import '../model/choice_value.dart'; 22 | import 'radio_item.dart'; 23 | 24 | class ChooseTabItem extends StatelessWidget { 25 | const ChooseTabItem(this.tabTypes, this.selectedType, this.onChanged); 26 | 27 | final List>> tabTypes; 28 | final ChoiceValue> selectedType; 29 | final ValueChanged>?>? onChanged; 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Row( 34 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 35 | children: tabTypes.map((ChoiceValue> type) { 36 | return Expanded( 37 | child: RadioItem>(type, selectedType, onChanged), 38 | ); 39 | }).toList(), 40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /example/lib/components/colors_item.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 chaobinwu89@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/cupertino.dart'; 18 | import 'package:flutter/material.dart'; 19 | 20 | import '../model/named_color.dart'; 21 | 22 | class ColorsItem extends StatelessWidget { 23 | const ColorsItem(this.colors, this.selectedColor, this.onChanged); 24 | 25 | final List colors; 26 | final Color selectedColor; 27 | final ValueChanged onChanged; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Row( 32 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 33 | children: colors.map((NamedColor namedColor) { 34 | return RawMaterialButton( 35 | onPressed: () { 36 | onChanged(namedColor.color); 37 | }, 38 | constraints: const BoxConstraints.tightFor( 39 | width: 32.0, 40 | height: 32.0, 41 | ), 42 | fillColor: namedColor.color, 43 | shape: CircleBorder( 44 | side: BorderSide( 45 | color: namedColor.color == selectedColor 46 | ? Colors.black 47 | : const Color(0xFFD5D7DA), 48 | width: 2.0, 49 | ), 50 | ), 51 | child: Semantics( 52 | value: namedColor.name, 53 | selected: namedColor.color == selectedColor, 54 | ), 55 | ); 56 | }).toList(), 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /example/lib/components/gradient_item.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 chaobinwu89@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/cupertino.dart'; 18 | import 'package:flutter/material.dart'; 19 | 20 | class GradientItem extends StatelessWidget { 21 | const GradientItem(this.colors, this.selectedColor, this.onChanged); 22 | 23 | final List colors; 24 | final Gradient? selectedColor; 25 | final ValueChanged onChanged; 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | return Row( 30 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 31 | children: colors.map((Gradient? namedColor) { 32 | return GestureDetector( 33 | onTap: () => onChanged(namedColor), 34 | child: Container( 35 | height: 40, 36 | width: 80, 37 | padding: EdgeInsets.all(8.0), 38 | decoration: BoxDecoration( 39 | border: Border.all( 40 | color: namedColor == selectedColor 41 | ? Colors.black 42 | : const Color(0xFFD5D7DA), 43 | width: 2), 44 | gradient: namedColor, 45 | color: namedColor != null ? Colors.grey : null, 46 | ), 47 | child: namedColor == null ? Center(child: Text('clear')) : null, 48 | ), 49 | ); 50 | }).toList(), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/lib/components/heading.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 chaobinwu89@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/cupertino.dart'; 18 | import 'package:flutter/material.dart'; 19 | 20 | class Heading extends StatelessWidget { 21 | const Heading(this.text); 22 | 23 | final String text; 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | final ThemeData theme = Theme.of(context); 28 | return Container( 29 | height: 48.0, 30 | padding: const EdgeInsetsDirectional.only(start: 56.0), 31 | alignment: AlignmentDirectional.centerStart, 32 | child: Text( 33 | text, 34 | style: TextStyle( 35 | color: theme.primaryColor, 36 | ), 37 | ), 38 | ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /example/lib/components/radio_item.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 chaobinwu89@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/cupertino.dart'; 18 | import 'package:flutter/material.dart'; 19 | 20 | import '../model/choice_value.dart'; 21 | 22 | // copy of _RadioItem from flutter gallery 23 | class RadioItem extends StatelessWidget { 24 | const RadioItem(this.value, this.groupValue, this.onChanged); 25 | 26 | final ChoiceValue value; 27 | final ChoiceValue groupValue; 28 | final ValueChanged?>? onChanged; 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | return Container( 33 | height: 56.0, 34 | padding: const EdgeInsetsDirectional.only(start: 16.0), 35 | alignment: AlignmentDirectional.centerStart, 36 | child: MergeSemantics( 37 | child: Row( 38 | children: [ 39 | Radio>( 40 | value: value, 41 | groupValue: groupValue, 42 | onChanged: onChanged, 43 | ), 44 | Expanded( 45 | child: Semantics( 46 | container: true, 47 | button: true, 48 | label: value.label, 49 | child: GestureDetector( 50 | behavior: HitTestBehavior.opaque, 51 | onTap: () { 52 | if (onChanged != null) { 53 | onChanged!(value); 54 | } 55 | }, 56 | child: Text(value.title), 57 | ), 58 | ), 59 | ), 60 | ], 61 | ), 62 | ), 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /example/lib/convex_button_demo.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:convex_bottom_bar/convex_bottom_bar.dart'; 18 | import 'package:flutter/cupertino.dart'; 19 | import 'package:flutter/material.dart'; 20 | 21 | class ConvexButtonDemo extends StatefulWidget { 22 | @override 23 | State createState() { 24 | return _State(); 25 | } 26 | } 27 | 28 | class _State extends State { 29 | int count = 0; 30 | 31 | @override 32 | Widget build(BuildContext context) { 33 | return Scaffold( 34 | appBar: AppBar(title: const Text('ConvexButton Example')), 35 | body: Center(child: Text('count $count')), 36 | bottomNavigationBar: ConvexButton.fab( 37 | onTap: () => setState(() => count++), 38 | ), 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /example/lib/custom_appbar_sample.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 chaobinwu89@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:convex_bottom_bar/convex_bottom_bar.dart'; 18 | import 'package:flutter/material.dart'; 19 | 20 | import 'color_item_view.dart'; 21 | 22 | class CustomAppBarDemo extends StatefulWidget { 23 | @override 24 | State createState() { 25 | return _State(); 26 | } 27 | } 28 | 29 | class _State extends State 30 | with SingleTickerProviderStateMixin { 31 | List items = [ 32 | TabItem(icon: Icons.home, title: 'Home'), 33 | TabItem(icon: Icons.map, title: 'Discovery'), 34 | TabItem(icon: Icons.plus_one, title: 'Add'), 35 | ]; 36 | 37 | static const paletteColors = [ 38 | Color(0xFFf44336), 39 | Color(0xFFE91E63), 40 | Color(0xFF9C27B0), 41 | Color(0xFF673AB7), 42 | Color(0xFF3F51B5), 43 | Color(0xFF2196F3), 44 | Color(0xFF00BCD4), 45 | Color(0xFF009688), 46 | Color(0xFF4CAF50), 47 | Color(0xFF8BC34A), 48 | Color(0xFFCDDC39), 49 | Color(0xFFFFEB3B), 50 | Color(0xFFFFC107), 51 | Color(0xFFFF9800), 52 | Color(0xFFFF5722), 53 | Color(0xFF795548), 54 | Color(0xFF9E9E9E), 55 | Color(0xFF607D8B), 56 | ]; 57 | Color _tabBackgroundColor = paletteColors[5]; 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | return DefaultTabController( 62 | initialIndex: 0, 63 | length: items.length, 64 | child: Scaffold( 65 | appBar: AppBar(title: const Text('Custom ConvexAppBar')), 66 | body: TabBarView( 67 | physics: NeverScrollableScrollPhysics(), 68 | children: items 69 | .map((i) => i.title == 'Discovery' 70 | ? paletteBody() 71 | : Center( 72 | child: Text( 73 | '<\t\t${i.title}\t\t>', 74 | style: TextStyle(fontSize: 30), 75 | ))) 76 | .toList(growable: false), 77 | ), 78 | bottomNavigationBar: StyleProvider( 79 | style: Style(), 80 | child: ConvexAppBar( 81 | disableDefaultTabController: true, 82 | initialActiveIndex: 0, 83 | height: 50, 84 | top: -30, 85 | curveSize: 100, 86 | style: TabStyle.fixedCircle, 87 | items: [ 88 | TabItem(title: '2019', icon: Icons.link), 89 | TabItem( 90 | icon: Container( 91 | decoration: BoxDecoration( 92 | shape: BoxShape.circle, 93 | color: Color(0xFFFF5722), 94 | ), 95 | child: Icon(Icons.add, color: Colors.white, size: 40), 96 | )), 97 | TabItem(title: "2020", icon: Icons.work), 98 | ], 99 | backgroundColor: _tabBackgroundColor, 100 | cornerRadius: 25, 101 | onTabNotify: (i) { 102 | var intercept = i == 1; 103 | if (intercept) { 104 | Navigator.pushNamed(context, '/fab'); 105 | } 106 | return !intercept; 107 | }, 108 | onTap: (i) => debugPrint('click $i'), 109 | ), 110 | ), 111 | )); 112 | } 113 | 114 | Widget builder() { 115 | return ConvexAppBar.builder( 116 | itemBuilder: _CustomBuilder(items, _tabBackgroundColor), 117 | count: items.length, 118 | backgroundColor: _tabBackgroundColor, 119 | ); 120 | } 121 | 122 | Container tabContent(TabItem data, Color color) { 123 | return Container( 124 | height: 50, 125 | padding: EdgeInsets.only(bottom: 2), 126 | child: Column( 127 | mainAxisAlignment: MainAxisAlignment.end, 128 | children: [ 129 | Icon(data.icon, color: color), 130 | Text(data.title != null ? data.title! : "", 131 | style: TextStyle(color: color)) 132 | ], 133 | )); 134 | } 135 | 136 | GridView paletteBody() { 137 | return GridView.count( 138 | crossAxisCount: 5, 139 | childAspectRatio: 1, 140 | mainAxisSpacing: 1, 141 | crossAxisSpacing: 1, 142 | children: paletteColors 143 | .map((c) => GestureDetector( 144 | child: ColorItemView(c), 145 | onTap: () => _onColorChanged(c), 146 | )) 147 | .toList(), 148 | ); 149 | } 150 | 151 | void _onColorChanged(Color color) { 152 | setState(() { 153 | _tabBackgroundColor = color; 154 | }); 155 | } 156 | } 157 | 158 | class _CustomBuilder extends DelegateBuilder { 159 | final List items; 160 | final Color _tabBackgroundColor; 161 | 162 | _CustomBuilder(this.items, this._tabBackgroundColor); 163 | 164 | @override 165 | Widget build(BuildContext context, int index, bool active) { 166 | var navigationItem = items[index]; 167 | var _color = active ? Colors.white : Colors.white60; 168 | 169 | if (index == items.length ~/ 2) { 170 | return Container( 171 | width: 60, 172 | height: 60, 173 | margin: EdgeInsets.all(5), 174 | decoration: BoxDecoration(shape: BoxShape.circle, color: _color), 175 | child: Icon( 176 | Icons.add, 177 | size: 40, 178 | color: _tabBackgroundColor, 179 | ), 180 | ); 181 | } 182 | var _icon = active 183 | ? navigationItem.activeIcon ?? navigationItem.icon 184 | : navigationItem.icon; 185 | var _title = navigationItem.title ?? ""; 186 | return Container( 187 | color: Colors.transparent, 188 | padding: EdgeInsets.only(bottom: 2), 189 | child: Column( 190 | mainAxisAlignment: MainAxisAlignment.end, 191 | children: [ 192 | Icon(_icon, color: _color), 193 | Text(_title, style: TextStyle(color: _color)) 194 | ], 195 | ), 196 | ); 197 | } 198 | 199 | @override 200 | bool fixed() { 201 | return true; 202 | } 203 | } 204 | 205 | class Style extends StyleHook { 206 | @override 207 | double get activeIconSize => 40; 208 | 209 | @override 210 | double get activeIconMargin => 10; 211 | 212 | @override 213 | double get iconSize => 20; 214 | 215 | @override 216 | TextStyle textStyle(Color color, String? fontFamily) { 217 | return TextStyle(fontSize: 20, color: color, fontFamily: fontFamily); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /example/lib/data.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 chaobinwu89@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:convex_bottom_bar/convex_bottom_bar.dart'; 18 | import 'package:flutter/cupertino.dart'; 19 | import 'package:flutter/material.dart'; 20 | 21 | import 'model/badge.dart'; 22 | import 'model/choice_value.dart'; 23 | import 'model/named_color.dart'; 24 | 25 | /// tab config used in example 26 | class Data { 27 | static const gradients = [ 28 | null, 29 | LinearGradient( 30 | begin: Alignment.topLeft, 31 | end: Alignment.bottomRight, 32 | colors: [Colors.blue, Colors.redAccent, Colors.green, Colors.blue], 33 | tileMode: TileMode.repeated, 34 | ), 35 | LinearGradient( 36 | begin: Alignment.center, 37 | end: Alignment(-1, 1), 38 | colors: [Colors.redAccent, Colors.green, Colors.blue], 39 | tileMode: TileMode.repeated, 40 | ), 41 | RadialGradient( 42 | center: const Alignment(0, 0), // near the top right 43 | radius: 5, 44 | colors: [Colors.green, Colors.blue, Colors.redAccent], 45 | ) 46 | ]; 47 | 48 | static const namedColors = [ 49 | NamedColor(Colors.blue, 'Blue'), 50 | NamedColor(Color(0xFFf44336), 'Red'), 51 | NamedColor(Color(0xFF673AB7), 'Purple'), 52 | NamedColor(Color(0xFF009688), 'Green'), 53 | NamedColor(Color(0xFFFFC107), 'Yellow'), 54 | NamedColor(Color(0xFF607D8B), 'Grey'), 55 | ]; 56 | static const namedShadowColors = [ 57 | NamedColor(Colors.black38, 'Black'), 58 | NamedColor(Color(0xeef44336), 'Red'), 59 | NamedColor(Color(0xee673AB7), 'Purple'), 60 | NamedColor(Color(0xee009688), 'Green'), 61 | NamedColor(Color(0xeeFFC107), 'Yellow'), 62 | NamedColor(Color(0xee607D8B), 'Grey'), 63 | ]; 64 | static const badges = [ 65 | null, 66 | SampleBadge('1'), 67 | SampleBadge('hot', 68 | badgeColor: Colors.orange, padding: EdgeInsets.only(left: 7, right: 7)), 69 | SampleBadge('99+', borderRadius: 2) 70 | ]; 71 | 72 | static const curves = [ 73 | ChoiceValue( 74 | title: 'Curves.bounceInOut', 75 | label: 'The curve bounceInOut is used', 76 | value: Curves.bounceInOut, 77 | ), 78 | ChoiceValue( 79 | title: 'Curves.decelerate', 80 | value: Curves.decelerate, 81 | label: 'The curve decelerate is used', 82 | ), 83 | ChoiceValue( 84 | title: 'Curves.easeInOut', 85 | value: Curves.easeInOut, 86 | label: 'The curve easeInOut is used', 87 | ), 88 | ChoiceValue( 89 | title: 'Curves.fastOutSlowIn', 90 | value: Curves.fastOutSlowIn, 91 | label: 'The curve fastOutSlowIn is used', 92 | ), 93 | ]; 94 | 95 | static List items({bool image = false}) { 96 | if (image) { 97 | return [ 98 | TabItem( 99 | icon: Image.asset('images/sample-1.png'), 100 | activeIcon: Image.asset('images/sample-1-2.png'), 101 | title: 'Happy', 102 | ), 103 | TabItem( 104 | icon: Image.asset('images/sample-2.png'), 105 | activeIcon: Image.asset('images/sample-2-2.png'), 106 | title: 'New'), 107 | TabItem( 108 | icon: Image.asset('images/sample-3.png'), 109 | activeIcon: Image.asset('images/sample-3-2.png'), 110 | title: 'Year', 111 | ), 112 | TabItem( 113 | icon: Image.asset('images/sample-4.png'), 114 | activeIcon: Image.asset('images/sample-4-2.png'), 115 | title: '20', 116 | ), 117 | TabItem( 118 | icon: Image.asset('images/sample-5.png'), 119 | activeIcon: Image.asset('images/sample-5-2.png'), 120 | title: '20', 121 | ), 122 | ]; 123 | } 124 | return [ 125 | TabItem(icon: Icons.home, title: 'Home'), 126 | TabItem(icon: Icons.map, title: "Discovery"), 127 | TabItem(icon: Icons.publish, title: "Publish"), 128 | TabItem(icon: Icons.message, title: 'Message'), 129 | TabItem(icon: Icons.people, title: 'Profile'), 130 | ]; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /example/lib/default_appbar_demo.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 chaobinwu89@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:convex_bottom_bar/convex_bottom_bar.dart'; 18 | import 'package:flutter/foundation.dart'; 19 | import 'package:flutter/material.dart'; 20 | import 'package:flutter/rendering.dart'; 21 | import 'package:flutter/widgets.dart'; 22 | 23 | import 'components/chip_item.dart'; 24 | import 'components/choose_tab_item.dart'; 25 | import 'components/colors_item.dart'; 26 | import 'components/gradient_item.dart'; 27 | import 'components/heading.dart'; 28 | import 'components/radio_item.dart'; 29 | import 'data.dart'; 30 | import 'model/badge.dart'; 31 | import 'model/choice_value.dart'; 32 | 33 | class DefaultAppBarDemo extends StatefulWidget { 34 | @override 35 | State createState() { 36 | return _State(); 37 | } 38 | } 39 | 40 | class _State extends State 41 | with SingleTickerProviderStateMixin { 42 | static const kStyles = [ 43 | ChoiceValue( 44 | title: 'TabStyle.react', 45 | label: 'Appbar use react style', 46 | value: TabStyle.react, 47 | ), 48 | ChoiceValue( 49 | title: 'TabStyle.reactCircle', 50 | label: 'Appbar use reactCircle style', 51 | value: TabStyle.reactCircle, 52 | ), 53 | ChoiceValue( 54 | title: kIsWeb 55 | ? 'TabStyle.flip (Flutter Web is not supported)' 56 | : 'TabStyle.flip', 57 | label: 'Appbar use flip style', 58 | value: TabStyle.flip, 59 | ), 60 | ChoiceValue( 61 | title: 'TabStyle.textIn', 62 | label: 'Appbar use textIn style', 63 | value: TabStyle.textIn, 64 | ), 65 | ChoiceValue( 66 | title: 'TabStyle.titled', 67 | label: 'Appbar use titled style', 68 | value: TabStyle.titled, 69 | ), 70 | ChoiceValue( 71 | title: 'TabStyle.fixed', 72 | label: 'Appbar use fixed style', 73 | value: TabStyle.fixed, 74 | ), 75 | ChoiceValue( 76 | title: 'TabStyle.fixedCircle', 77 | label: 'Appbar use fixedCircle style', 78 | value: TabStyle.fixedCircle, 79 | ), 80 | ]; 81 | 82 | static final kTabTypes = [ 83 | ChoiceValue>( 84 | title: 'Icon Tab', 85 | label: 'Appbar use icon with Tab', 86 | value: Data.items(image: false), 87 | ), 88 | ChoiceValue>( 89 | title: 'Image Tab', 90 | label: 'Appbar use image with Tab', 91 | value: Data.items(image: true), 92 | ), 93 | ]; 94 | var _tabItems = kTabTypes.first; 95 | 96 | ChoiceValue _style = kStyles.first; 97 | ChoiceValue _curve = Data.curves.first; 98 | Color _barColor = Data.namedColors.first.color; 99 | Color _shadowColor = Data.namedColors.first.color; 100 | Gradient? _gradient = Data.gradients.first; 101 | SampleBadge? _badge; 102 | TabController? _tabController; 103 | TextDirection _textDirection = TextDirection.ltr; 104 | 105 | @override 106 | void initState() { 107 | super.initState(); 108 | _tabController = TabController(length: _tabItems.value.length, vsync: this); 109 | } 110 | 111 | @override 112 | Widget build(BuildContext context) { 113 | var options = [ 114 | const Heading('Appbar Color'), 115 | ColorsItem(Data.namedColors, _barColor, _onBarColorChanged), 116 | const Heading('Shadow Color'), 117 | ColorsItem(Data.namedShadowColors, _shadowColor, _onShadowColorChanged), 118 | const Heading('Background Gradient'), 119 | GradientItem(Data.gradients, _gradient, _onGradientChanged), 120 | const Heading('Badge Chip'), 121 | ChipItem(Data.badges, _badge, _onBadgeChanged), 122 | const Heading('Tab Type'), 123 | ChooseTabItem(kTabTypes, _tabItems, _onTabItemTypeChanged), 124 | const Heading('Tab Style'), 125 | ]; 126 | options.addAll(kStyles.map((s) => RadioItem(s, _style, 127 | s.value == TabStyle.flip && kIsWeb ? _onNothing : _onStyleChanged))); 128 | if (_style.value != TabStyle.fixed && 129 | _style.value != TabStyle.fixedCircle) { 130 | options.add(const Heading('Animation Curve')); 131 | options.addAll( 132 | Data.curves.map((c) => RadioItem(c, _curve, _onCurveChanged))); 133 | } 134 | 135 | return Directionality( 136 | textDirection: _textDirection, 137 | child: Scaffold( 138 | appBar: AppBar( 139 | title: const Text('ConvexAppBar'), 140 | backgroundColor: _barColor, 141 | actions: [ 142 | IconButton( 143 | icon: Icon(_textDirection == TextDirection.rtl 144 | ? Icons.format_textdirection_r_to_l 145 | : Icons.format_textdirection_l_to_r), 146 | color: Colors.white, 147 | tooltip: _textDirection == TextDirection.rtl 148 | ? "Change to LTR" 149 | : "Change to RTL", 150 | onPressed: () { 151 | setState(() { 152 | _textDirection = _textDirection == TextDirection.ltr 153 | ? TextDirection.rtl 154 | : TextDirection.ltr; 155 | }); 156 | }, 157 | ), 158 | IconButton( 159 | icon: Icon(Icons.style), 160 | color: Colors.white, 161 | tooltip: "Custom style example", 162 | onPressed: () => Navigator.of(context).pushNamed('/custom'), 163 | ), 164 | IconButton( 165 | icon: Icon(Icons.radio_button_checked), 166 | color: Colors.white, 167 | tooltip: "convex button example", 168 | onPressed: () => Navigator.of(context).pushNamed('/fab'), 169 | ), 170 | IconButton( 171 | icon: Icon(Icons.looks_two), 172 | color: Colors.white, 173 | tooltip: "change tab by controller", 174 | onPressed: () { 175 | _tabController?.animateTo(2); 176 | }, 177 | ) 178 | ], 179 | ), 180 | body: TabBarView( 181 | controller: _tabController, 182 | children: _tabItems.value 183 | .map((i) => i.title == 'Home' || i.title == 'Happy' 184 | ? ListView(children: options) 185 | : Center( 186 | child: Text( 187 | '${i.title} World', 188 | style: TextStyle(fontSize: 30), 189 | ))) 190 | .toList(growable: false)), 191 | bottomNavigationBar: _badge == null 192 | ? ConvexAppBar( 193 | items: _tabItems.value, 194 | style: _style.value, 195 | curve: _curve.value, 196 | shadowColor: _shadowColor, 197 | backgroundColor: _barColor, 198 | gradient: _gradient, 199 | controller: _tabController, 200 | onTap: (int i) => debugPrint('select index=$i'), 201 | ) 202 | : ConvexAppBar.badge( 203 | { 204 | 3: _badge!.text, 205 | 4: Icons.assistant_photo, 206 | 2: Colors.redAccent 207 | }, 208 | badgePadding: _badge!.padding, 209 | badgeColor: _badge!.badgeColor, 210 | badgeBorderRadius: _badge!.borderRadius, 211 | badgeMargin: EdgeInsets.only(bottom: 20, left: 30), 212 | items: _tabItems.value, 213 | style: _style.value, 214 | curve: _curve.value, 215 | shadowColor: _shadowColor, 216 | backgroundColor: _barColor, 217 | gradient: _gradient, 218 | controller: _tabController, 219 | onTap: (int i) => debugPrint('select index=$i'), 220 | ), 221 | ), 222 | ); 223 | } 224 | 225 | void _onTabItemTypeChanged(ChoiceValue>? value) { 226 | if (value == null) { 227 | return; 228 | } 229 | setState(() { 230 | _tabItems = value; 231 | }); 232 | } 233 | 234 | void _onNothing(ChoiceValue? value) {} 235 | 236 | void _onStyleChanged(ChoiceValue? value) { 237 | if (value == null) { 238 | return; 239 | } 240 | setState(() { 241 | _style = value; 242 | }); 243 | } 244 | 245 | void _onCurveChanged(ChoiceValue? value) { 246 | if (value == null) { 247 | return; 248 | } 249 | setState(() { 250 | _curve = value; 251 | }); 252 | } 253 | 254 | void _onBarColorChanged(Color value) { 255 | setState(() { 256 | _barColor = value; 257 | }); 258 | } 259 | 260 | void _onShadowColorChanged(Color value) { 261 | setState(() { 262 | _shadowColor = value; 263 | }); 264 | } 265 | 266 | void _onGradientChanged(Gradient? value) { 267 | setState(() { 268 | _gradient = value; 269 | }); 270 | } 271 | 272 | void _onBadgeChanged(SampleBadge? value) { 273 | setState(() { 274 | _badge = value; 275 | }); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 chaobinwu89@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:convex_app_bar_example/convex_button_demo.dart'; 18 | import 'package:convex_app_bar_example/custom_appbar_sample.dart'; 19 | import 'package:convex_bottom_bar/convex_bottom_bar.dart'; 20 | import 'package:flutter/material.dart'; 21 | 22 | import 'default_appbar_demo.dart'; 23 | 24 | void main() => runApp(MyApp()); 25 | 26 | class MyApp extends StatefulWidget { 27 | @override 28 | State createState() => _State(); 29 | } 30 | 31 | class _State extends State { 32 | @override 33 | Widget build(BuildContext context) { 34 | return MaterialApp( 35 | initialRoute: "/", 36 | routes: { 37 | "/": (_) => HelloConvexAppBar(), 38 | "/bar": (BuildContext context) => DefaultAppBarDemo(), 39 | "/custom": (BuildContext context) => CustomAppBarDemo(), 40 | "/fab": (BuildContext context) => ConvexButtonDemo(), 41 | }, 42 | ); 43 | } 44 | } 45 | 46 | class HelloConvexAppBar extends StatelessWidget { 47 | @override 48 | Widget build(BuildContext context) { 49 | return Scaffold( 50 | appBar: AppBar(title: Text('Hello ConvexAppBar')), 51 | body: Center( 52 | child: TextButton( 53 | child: Text('Click to show full example'), 54 | onPressed: () => Navigator.of(context).pushNamed('/bar'), 55 | )), 56 | bottomNavigationBar: ConvexAppBar( 57 | style: TabStyle.react, 58 | items: [ 59 | TabItem(icon: Icons.list), 60 | TabItem(icon: Icons.calendar_today), 61 | TabItem(icon: Icons.assessment), 62 | ], 63 | initialActiveIndex: 1, 64 | onTap: (int i) => print('click index=$i'), 65 | ), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/lib/model/badge.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 chaobinwu89@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | class SampleBadge { 20 | final Color? color; 21 | final Color? badgeColor; 22 | final EdgeInsets? padding; 23 | final double? borderRadius; 24 | final String text; 25 | 26 | const SampleBadge( 27 | this.text, { 28 | this.color, 29 | this.badgeColor, 30 | this.padding, 31 | this.borderRadius, 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /example/lib/model/choice_value.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 chaobinwu89@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // copy of _ChoiceValue from flutter gallery 18 | class ChoiceValue { 19 | const ChoiceValue( 20 | {required this.value, required this.title, required this.label}); 21 | 22 | final T value; 23 | final String title; 24 | final String label; // For the Semantics widget that contains title 25 | 26 | @override 27 | String toString() => '$runtimeType("$title")'; 28 | } 29 | -------------------------------------------------------------------------------- /example/lib/model/named_color.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 chaobinwu89@gmail.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'dart:ui'; 18 | 19 | class NamedColor { 20 | const NamedColor(this.color, this.name); 21 | 22 | final Color color; 23 | final String name; 24 | } 25 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: convex_app_bar_example 2 | description: Demonstrates how to use the convex_app_bar plugin. 3 | publish_to: 'none' 4 | version: 1.0.0 5 | 6 | environment: 7 | sdk: ">=2.15.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | # The following adds the Cupertino Icons font to your application. 14 | # Use with the CupertinoIcons class for iOS style icons. 15 | cupertino_icons: ^0.1.2 16 | 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | 21 | convex_bottom_bar: 22 | path: ../ 23 | 24 | # For information on the generic Dart part of this file, see the 25 | # following page: https://dart.dev/tools/pub/pubspec 26 | 27 | # The following section is specific to Flutter. 28 | flutter: 29 | 30 | # The following line ensures that the Material Icons font is 31 | # included with your application, so that you can use the icons in 32 | # the material Icons class. 33 | uses-material-design: true 34 | 35 | # To add assets to your application, add an assets section, like this: 36 | assets: 37 | - images/ 38 | 39 | # An image asset can refer to one or more resolution-specific "variants", see 40 | # https://flutter.dev/assets-and-images/#resolution-aware. 41 | 42 | # For details regarding adding assets from package dependencies, see 43 | # https://flutter.dev/assets-and-images/#from-packages 44 | 45 | # To add custom fonts to your application, add a fonts section here, 46 | # in this "flutter" section. Each entry in this list should have a 47 | # "family" key with the font family name, and a "fonts" key with a 48 | # list giving the asset and other descriptors for the font. For 49 | # example: 50 | # fonts: 51 | # - family: Schyler 52 | # fonts: 53 | # - asset: fonts/Schyler-Regular.ttf 54 | # - asset: fonts/Schyler-Italic.ttf 55 | # style: italic 56 | # - family: Trajan Pro 57 | # fonts: 58 | # - asset: fonts/TrajanPro.ttf 59 | # - asset: fonts/TrajanPro_Bold.ttf 60 | # weight: 700 61 | # 62 | # For details regarding fonts from package dependencies, 63 | # see https://flutter.dev/custom-fonts/#from-packages 64 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/web/favicon.png -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hacktons/convex_bottom_bar/ddb58b0216eee90c3e88f36eda58d1ba1646ce96/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ConvexAppBar Example 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "short_name": "example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /lib/convex_bottom_bar.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | library convex_appbar; 18 | 19 | export 'src/bar.dart'; 20 | export 'src/interface.dart'; 21 | export 'src/item.dart'; 22 | export 'src/fab.dart'; 23 | -------------------------------------------------------------------------------- /lib/src/chip_builder.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | import 'package:flutter/widgets.dart'; 19 | 20 | import 'interface.dart'; 21 | 22 | /// Simple badge with num inside. 23 | class DefaultChipBuilder extends ChipBuilder { 24 | /// key-value map, stands for the badge data. 25 | final Map chips; 26 | 27 | /// Color of badge text. 28 | final Color textColor; 29 | 30 | /// Color of the badge chip. 31 | final Color badgeColor; 32 | 33 | /// Padding for badge. 34 | final EdgeInsets padding; 35 | 36 | /// Margin for badge. 37 | final EdgeInsets margin; 38 | 39 | /// Radius corner for badge. 40 | final double borderRadius; 41 | 42 | /// Create a chip builder 43 | DefaultChipBuilder( 44 | this.chips, { 45 | required this.textColor, 46 | required this.badgeColor, 47 | required this.padding, 48 | required this.margin, 49 | required this.borderRadius, 50 | }); 51 | 52 | @override 53 | Widget build(_, child, i, active) { 54 | var chip = chips[i]; 55 | if (chip == null || chip == '') { 56 | return child; 57 | } 58 | return Stack( 59 | alignment: Alignment.center, 60 | children: [child, asBadge(chip)], 61 | ); 62 | } 63 | 64 | /// Convert a chip data into [Widget]. 65 | /// 66 | /// * [chip] String, return a [Text] badge; 67 | /// * [chip] IconData, return a [Icon] badge; 68 | /// * [chip] Widget, return a [Widget] badge; 69 | Widget asBadge(dynamic chip) { 70 | if (chip is String) { 71 | return Positioned.fill( 72 | child: Align( 73 | alignment: Alignment.center, 74 | child: Container( 75 | margin: margin, 76 | padding: padding, 77 | decoration: BoxDecoration( 78 | shape: BoxShape.rectangle, 79 | color: badgeColor, 80 | borderRadius: BorderRadius.circular(borderRadius), 81 | ), 82 | child: Text(chip, style: TextStyle(color: textColor, fontSize: 12)), 83 | ), 84 | ), 85 | ); 86 | } else if (chip is IconData) { 87 | return Positioned.fill( 88 | child: Align( 89 | alignment: Alignment.center, 90 | child: Container( 91 | margin: margin, 92 | padding: padding, 93 | child: Icon(chip, color: badgeColor, size: 14), 94 | ), 95 | ), 96 | ); 97 | } else if (chip is Widget) { 98 | return Positioned.fill( 99 | child: Align( 100 | alignment: Alignment.center, 101 | child: Container(margin: margin, padding: padding, child: chip), 102 | ), 103 | ); 104 | } else if (chip is Color) { 105 | return Positioned.fill( 106 | child: Align( 107 | alignment: Alignment.center, 108 | child: Container( 109 | margin: margin, 110 | padding: padding, 111 | child: Container( 112 | decoration: BoxDecoration(shape: BoxShape.circle, color: chip), 113 | width: 10, 114 | height: 10, 115 | ), 116 | ), 117 | ), 118 | ); 119 | } else { 120 | return Container(); 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /lib/src/convex_shape.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | // Copyright 2014 The Flutter Authors. All rights reserved. 17 | // Use of this source code is governed by a BSD-style license that can be 18 | // found in the LICENSE file. 19 | 20 | import 'package:flutter/painting.dart'; 21 | import 'dart:math' as math; 22 | 23 | /// A convex shape which implemented [NotchedShape]. 24 | /// 25 | /// It's used to draw a convex shape for [ConvexAppBar], If you are interested about 26 | /// the math calculation, please refer to [CircularNotchedRectangle], it's based 27 | /// on Bezier curve; 28 | /// 29 | /// See also: 30 | /// 31 | /// * [CircularNotchedRectangle], a rectangle with a smooth circular notch. 32 | class ConvexNotchedRectangle extends NotchedShape { 33 | /// Draw the background with topLeft and topRight corner 34 | final double radius; 35 | 36 | /// Create Shape instance 37 | const ConvexNotchedRectangle({this.radius = 0}); 38 | 39 | @override 40 | Path getOuterPath(Rect host, Rect? guest) { 41 | if (guest == null || !host.overlaps(guest)) return Path()..addRect(host); 42 | 43 | // The guest's shape is a circle bounded by the guest rectangle. 44 | // So the guest's radius is half the guest width. 45 | final notchRadius = guest.width / 2.0; 46 | 47 | const s1 = 15.0; 48 | const s2 = 1.0; 49 | 50 | final r = notchRadius; 51 | final a = -1.0 * r - s2; 52 | final b = host.top - guest.center.dy; 53 | 54 | final n2 = math.sqrt(b * b * r * r * (a * a + b * b - r * r)); 55 | final p2xA = ((a * r * r) - n2) / (a * a + b * b); 56 | final p2xB = ((a * r * r) + n2) / (a * a + b * b); 57 | final p2yA = -math.sqrt(r * r - p2xA * p2xA); 58 | final p2yB = -math.sqrt(r * r - p2xB * p2xB); 59 | 60 | final p = List.filled(6, Offset.zero, growable: false); 61 | // p0, p1, and p2 are the control points for segment A. 62 | p[0] = Offset(a - s1, b); 63 | p[1] = Offset(a, b); 64 | final cmp = b < 0 ? -1.0 : 1.0; 65 | p[2] = cmp * p2yA > cmp * p2yB ? Offset(p2xA, p2yA) : Offset(p2xB, p2yB); 66 | 67 | // p3, p4, and p5 are the control points for segment B, which is a mirror 68 | // of segment A around the y axis. 69 | p[3] = Offset(-1.0 * p[2].dx, p[2].dy); 70 | p[4] = Offset(-1.0 * p[1].dx, p[1].dy); 71 | p[5] = Offset(-1.0 * p[0].dx, p[0].dy); 72 | 73 | // translate all points back to the absolute coordinate system. 74 | for (var i = 0; i < p.length; i += 1) { 75 | p[i] = p[i] + guest.center; 76 | //p[i] += padding; 77 | } 78 | 79 | return radius > 0 80 | ? (Path() 81 | ..moveTo(host.left, host.top + radius) 82 | ..arcToPoint(Offset(host.left + radius, host.top), 83 | radius: Radius.circular(radius)) 84 | ..lineTo(p[0].dx, p[0].dy) 85 | ..quadraticBezierTo(p[1].dx, p[1].dy, p[2].dx, p[2].dy) 86 | ..arcToPoint( 87 | p[3], 88 | radius: Radius.circular(notchRadius), 89 | clockwise: true, 90 | ) 91 | ..quadraticBezierTo(p[4].dx, p[4].dy, p[5].dx, p[5].dy) 92 | ..lineTo(host.right - radius, host.top) 93 | ..arcToPoint(Offset(host.right, host.top + radius), 94 | radius: Radius.circular(radius)) 95 | ..lineTo(host.right, host.bottom) 96 | ..lineTo(host.left, host.bottom) 97 | ..close()) 98 | : (Path() 99 | ..moveTo(host.left, host.top) 100 | ..lineTo(p[0].dx, p[0].dy) 101 | ..quadraticBezierTo(p[1].dx, p[1].dy, p[2].dx, p[2].dy) 102 | ..arcToPoint( 103 | p[3], 104 | radius: Radius.circular(notchRadius), 105 | clockwise: true, 106 | ) 107 | ..quadraticBezierTo(p[4].dx, p[4].dy, p[5].dx, p[5].dy) 108 | ..lineTo(host.right, host.top) 109 | ..lineTo(host.right, host.bottom) 110 | ..lineTo(host.left, host.bottom) 111 | ..close()); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/src/fab.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:convex_bottom_bar/src/painter.dart'; 18 | import 'package:flutter/material.dart'; 19 | 20 | /// Single convex button widget 21 | class ConvexButton extends StatelessWidget { 22 | static const _DEFAULT_SIZE = 60.0; 23 | static const _DEFAULT_TOP = 50.0; 24 | static const _DEFAULT_SIGMA = 2.0; 25 | static const _DEFAULT_THICKNESS = 4.0; 26 | 27 | /// Size of convex shape, should be lager than [top] 28 | final double? size; 29 | 30 | /// The distance to edge from the bottom of child widget. 31 | final double? top; 32 | 33 | /// Height of bottom border 34 | final double? thickness; 35 | 36 | /// Sigma for border 37 | final double? sigma; 38 | 39 | /// Optional child widget, default to be a widget of Icons.keyboard_voice 40 | final Widget child; 41 | 42 | /// Color for the button 43 | final Color? backgroundColor; 44 | 45 | /// Make new instance of [ConvexButton] 46 | const ConvexButton({ 47 | Key? key, 48 | this.size, 49 | this.sigma, 50 | required this.child, 51 | this.thickness, 52 | this.backgroundColor, 53 | this.top, 54 | }) : super(key: key); 55 | 56 | /// Make a centered convex button. 57 | /// 58 | /// ![](https://github.com/hacktons/convex_bottom_bar/raw/master/doc/appbar-single-shape.png) 59 | factory ConvexButton.fab({ 60 | Key? key, 61 | double? size, 62 | double? thickness, 63 | double? top, 64 | double? sigma, 65 | double iconSize = 32, 66 | double border = 2, 67 | Color color = Colors.redAccent, 68 | IconData icon = Icons.keyboard_voice, 69 | Color? backgroundColor, 70 | VoidCallback? onTap, 71 | }) { 72 | thickness = thickness ?? _DEFAULT_THICKNESS; 73 | var fab = Container( 74 | margin: EdgeInsets.only(bottom: thickness), 75 | decoration: BoxDecoration( 76 | shape: BoxShape.circle, 77 | border: Border.all(color: color, width: border), 78 | ), 79 | child: Icon(icon, color: color, size: iconSize), 80 | ); 81 | return ConvexButton( 82 | key: key, 83 | size: size, 84 | thickness: thickness, 85 | top: top, 86 | backgroundColor: backgroundColor, 87 | sigma: sigma, 88 | child: GestureDetector(onTap: onTap, child: fab), 89 | ); 90 | } 91 | 92 | @override 93 | Widget build(BuildContext context) { 94 | return Stack(alignment: Alignment.bottomCenter, children: [ 95 | Container( 96 | height: thickness ?? _DEFAULT_THICKNESS, 97 | width: double.infinity, 98 | child: CustomPaint( 99 | painter: ConvexPainter( 100 | top: -(top ?? _DEFAULT_TOP), 101 | width: size ?? _DEFAULT_SIZE, 102 | height: size ?? _DEFAULT_SIZE, 103 | color: backgroundColor ?? Colors.grey[50]!, 104 | sigma: sigma ?? _DEFAULT_SIGMA, 105 | leftPercent: const AlwaysStoppedAnimation(0.5), 106 | ), 107 | ), 108 | ), 109 | child, 110 | ]); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /lib/src/interface.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/widgets.dart'; 18 | 19 | /// Tab callback, [index] are tab index which is being clicked. 20 | typedef GestureTapIndexCallback = void Function(int index); 21 | 22 | /// Fire before [GestureTapIndexCallback] is handled, you may return false to block the tap event. 23 | typedef TapNotifier = bool Function(int index); 24 | 25 | /// Tab builder. 26 | /// * [context] BuildContent instance 27 | /// * [index] index of tab 28 | /// * [active] active state for tab index 29 | typedef CustomTabBuilder = Widget Function( 30 | BuildContext context, int index, bool active); 31 | 32 | /// Interface to apply any custom badge chip. 33 | abstract class ChipBuilder { 34 | /// Construct a new widget which represent the tab item with custom badge. 35 | /// 36 | /// * [context] BuildContext instance; 37 | /// * [child] the tab item Widget; 38 | /// * [index] index of the tab item; 39 | /// * [active] active state for the index; 40 | Widget build(BuildContext context, Widget child, int index, bool active); 41 | } 42 | 43 | /// Item builder. 44 | abstract class DelegateBuilder { 45 | /// Called when the tab item is build. 46 | /// * [context] BuildContext instance; 47 | /// * [index] tab index; 48 | /// * [active] tab state; 49 | Widget build(BuildContext context, int index, bool active); 50 | 51 | /// Whether the convex shape is fixed center or positioned according to selection. 52 | bool fixed() { 53 | return false; 54 | } 55 | } 56 | 57 | /// Default tab styles are configured with internal layout/size, these are not 58 | /// exposed like color or height information. You can use [ConvexAppBar.builder] 59 | /// to fully customize the tab widget. 60 | /// 61 | /// However, if you just want to override some of the internal config and willing 62 | /// to take risk of the modified effects, try with the config carefully. 63 | abstract class StyleHook { 64 | /// size of icon 65 | double? get iconSize; 66 | 67 | /// margin outside of icon 68 | double get activeIconMargin; 69 | 70 | /// size of convex icon 71 | double get activeIconSize; 72 | 73 | /// style for text label. 74 | /// 75 | /// Warning: 76 | /// Override the text size can lead to `layout overflow` warning, you may need 77 | /// to update the height of bar too. 78 | TextStyle textStyle(Color color, String? fontFamily); 79 | 80 | /// For styles with both ICON and label, omit the Text widget when label is null/empty 81 | bool get hideEmptyLabel { 82 | return true; 83 | } 84 | 85 | /// layout size are relative to icon size and margin 86 | double get layoutSize { 87 | return activeIconMargin * 4 + activeIconSize; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/src/item.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/cupertino.dart'; 18 | 19 | /// Tab item used for [ConvexAppBar]. 20 | class TabItem { 21 | /// this code is added by moein 22 | final String? fontFamily; 23 | 24 | /// Tab text. 25 | final String? title; 26 | 27 | /// IconData or Image. 28 | /// 29 | /// ![](https://github.com/hacktons/convex_bottom_bar/raw/master/doc/appbar-image.gif) 30 | final T icon; 31 | 32 | /// Optional if not provided ,[icon] is used. 33 | final T? activeIcon; 34 | 35 | /// Whether icon should blend with color. 36 | /// If [icon] is instance of [IconData] then blend is default to true, otherwise false 37 | final bool blend; 38 | 39 | /// Create item 40 | const TabItem({ 41 | this.fontFamily, 42 | this.title = '', 43 | required this.icon, 44 | this.activeIcon, 45 | bool? isIconBlend, 46 | }) : assert(icon is IconData || icon is Widget, 47 | 'TabItem only support IconData and Widget'), 48 | blend = isIconBlend ?? (icon is IconData); 49 | } 50 | -------------------------------------------------------------------------------- /lib/src/painter.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import 'convex_shape.dart'; 20 | import 'reused_gradient.dart'; 21 | 22 | /// Custom painter to draw the [ConvexNotchedRectangle] into canvas. 23 | class ConvexPainter extends CustomPainter { 24 | final _paint = Paint(); 25 | final _shadowPaint = Paint(); 26 | late ConvexNotchedRectangle _shape; 27 | final ReusedGradient _gradient = ReusedGradient(); 28 | 29 | /// Width of the convex shape. 30 | final double width; 31 | 32 | /// Height of the convex shape. 33 | final double height; 34 | 35 | /// Position in vertical which describe the offset of shape. 36 | final double top; 37 | 38 | /// Position in horizontal which describe the offset of shape. 39 | final Animation leftPercent; 40 | 41 | /// RLT support 42 | final TextDirection? textDirection; 43 | 44 | /// Create painter 45 | ConvexPainter({ 46 | required this.top, 47 | required this.width, 48 | required this.height, 49 | this.leftPercent = const AlwaysStoppedAnimation(0.5), 50 | this.textDirection, 51 | Color color = Colors.white, 52 | Color shadowColor = Colors.black38, 53 | double sigma = 2, 54 | Gradient? gradient, 55 | double? cornerRadius, 56 | }) : super(repaint: leftPercent) { 57 | _paint.color = color; 58 | try { 59 | _shadowPaint 60 | ..color = shadowColor 61 | ..maskFilter = MaskFilter.blur(BlurStyle.outer, sigma); 62 | } catch (e, s) { 63 | debugPrintStack(label: 'ElevationError', stackTrace: s); 64 | } 65 | _gradient.gradient = gradient; 66 | _shape = ConvexNotchedRectangle(radius: cornerRadius ?? 0); 67 | } 68 | 69 | @override 70 | void paint(Canvas canvas, Size size) { 71 | var host = Rect.fromLTWH(0, 0, size.width, size.height); 72 | var percent = textDirection == TextDirection.rtl 73 | ? (1 - leftPercent.value) 74 | : leftPercent.value; 75 | var guest = 76 | Rect.fromLTWH(size.width * percent - width / 2, top, width, height); 77 | _gradient.updateWith(_paint, size: host); 78 | var path = _shape.getOuterPath(host, guest); 79 | canvas.drawPath(path, _shadowPaint); 80 | canvas.drawPath(path, _paint); 81 | } 82 | 83 | @override 84 | bool shouldRepaint(ConvexPainter oldDelegate) { 85 | return oldDelegate.leftPercent.value != leftPercent.value || 86 | oldDelegate._paint != _paint; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /lib/src/reused_gradient.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/painting.dart'; 18 | 19 | /// Wrapper for [Gradient], we don't want to re-create instance frequently in 20 | /// hot method, such as paint(). 21 | class ReusedGradient { 22 | Gradient? _gradient; 23 | 24 | Shader? _shader; 25 | Rect? _size; 26 | 27 | /// Create gradient 28 | ReusedGradient(); 29 | 30 | /// Setter to reset the [Gradient] instance 31 | set gradient(Gradient? gradient) { 32 | _gradient = gradient; 33 | _size = null; 34 | } 35 | 36 | /// Update the paint with provided size 37 | void updateWith(Paint paint, {Rect? size}) { 38 | if (size == _size || size == null) { 39 | return; 40 | } 41 | if (_gradient == null) { 42 | return; 43 | } 44 | _shader ??= _gradient!.createShader(size); 45 | paint.shader = _shader; 46 | _size = size; 47 | } 48 | 49 | /// check if the gradient is valid or not 50 | bool get valid { 51 | return _size != null && _gradient != null; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/src/stack.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/rendering.dart'; 18 | import 'package:flutter/widgets.dart' as widget; 19 | 20 | /// The `Stack` widget has limited hit test when child is overflow with 21 | /// `Positioned`. This behavior is intentional. For more detail refer to 22 | /// the bug report: [Document that widgets in the overflow of stack do not 23 | /// respond to gestures](https://github.com/flutter/flutter/issues/19445). 24 | /// 25 | /// The optional way to enable the hit test is define a new Stack and remove 26 | /// the size checking when the Stack instance is overflow enable. 27 | /// 28 | class Stack extends widget.Stack { 29 | /// Create stack instance 30 | Stack({ 31 | widget.Key? key, 32 | AlignmentGeometry alignment = AlignmentDirectional.topStart, 33 | TextDirection? textDirection, 34 | StackFit fit = StackFit.loose, 35 | Clip clipBehavior = Clip.hardEdge, 36 | List children = const [], 37 | }) : super( 38 | key: key, 39 | alignment: alignment, 40 | textDirection: textDirection, 41 | fit: fit, 42 | clipBehavior: clipBehavior, 43 | children: children, 44 | ); 45 | 46 | @override 47 | RenderStack createRenderObject(widget.BuildContext context) { 48 | return _RenderStack( 49 | alignment: alignment, 50 | textDirection: textDirection ?? widget.Directionality.of(context), 51 | fit: fit, 52 | clipBehavior: clipBehavior, 53 | ); 54 | } 55 | } 56 | 57 | /// Enable overflow hitTest 58 | class _RenderStack extends RenderStack { 59 | _RenderStack({ 60 | List? children, 61 | AlignmentGeometry alignment = AlignmentDirectional.topStart, 62 | TextDirection? textDirection, 63 | StackFit fit = StackFit.loose, 64 | Clip clipBehavior = Clip.hardEdge, 65 | }) : super( 66 | children: children, 67 | alignment: alignment, 68 | textDirection: textDirection, 69 | clipBehavior: clipBehavior, 70 | fit: fit, 71 | ); 72 | 73 | @override 74 | bool hitTest(BoxHitTestResult result, {required Offset position}) { 75 | if (clipBehavior == Clip.none || size.contains(position)) { 76 | if (hitTestChildren(result, position: position) || 77 | hitTestSelf(position)) { 78 | result.add(BoxHitTestEntry(this, position)); 79 | return true; 80 | } 81 | } 82 | return false; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /lib/src/style/blend_image_icon.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/cupertino.dart'; 18 | import 'package:flutter/foundation.dart'; 19 | import 'package:flutter/material.dart'; 20 | 21 | /// Decorate the provided [Image] or [IconData]. 22 | class BlendImageIcon extends StatelessWidget { 23 | /// Create image widget 24 | const BlendImageIcon(this.image, {Key? key, this.color, this.size}) 25 | : assert(image is Widget || image is IconData, 26 | 'image must be IconData or Widget'), 27 | super(key: key); 28 | 29 | /// Color used for Icon and gradient. 30 | final Color? color; 31 | 32 | /// Child image. 33 | final T image; 34 | 35 | /// Size of icon. 36 | final double? size; 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | var s = size ?? IconTheme.of(context).size; 41 | if (image is Widget) { 42 | // flutter web do not support shader mask. (flutter v1.12.x) 43 | var showRawImage = kIsWeb || color == null; 44 | if (showRawImage) { 45 | return SizedBox( 46 | width: s, 47 | height: s, 48 | child: image as Widget, 49 | ); 50 | } 51 | return SizedBox( 52 | width: s, 53 | height: s, 54 | child: ShaderMask( 55 | shaderCallback: (Rect bounds) { 56 | return LinearGradient(colors: [color!, color!]) 57 | .createShader(bounds); 58 | }, 59 | blendMode: BlendMode.srcIn, 60 | child: image as Widget, 61 | ), 62 | ); 63 | } 64 | return Icon(image as IconData, size: s, color: color); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/src/style/fixed_circle_tab_style.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import '../item.dart'; 20 | import 'blend_image_icon.dart'; 21 | import 'inner_builder.dart'; 22 | 23 | /// Convex shape is fixed center with circle. 24 | class FixedCircleTabStyle extends InnerBuilder { 25 | /// Color used as background of appbar and circle icon. 26 | final Color backgroundColor; 27 | 28 | /// Index of the centered convex shape. 29 | final int convexIndex; 30 | 31 | /// Create style builder 32 | FixedCircleTabStyle( 33 | {required List items, 34 | required Color activeColor, 35 | required Color color, 36 | required this.backgroundColor, 37 | required this.convexIndex}) 38 | : super(items: items, activeColor: activeColor, color: color); 39 | 40 | @override 41 | Widget build(BuildContext context, int index, bool active) { 42 | var c = active ? activeColor : color; 43 | var item = items[index]; 44 | var style = ofStyle(context); 45 | var textStyle = style.textStyle(c, item.fontFamily); 46 | var margin = style.activeIconMargin; 47 | 48 | if (index == convexIndex) { 49 | final item = items[index]; 50 | return Container( 51 | // necessary otherwise the badge will not large enough 52 | width: style.layoutSize, 53 | height: style.layoutSize, 54 | decoration: BoxDecoration( 55 | shape: BoxShape.circle, 56 | color: c, 57 | ), 58 | margin: EdgeInsets.all(margin), 59 | child: BlendImageIcon( 60 | active ? item.activeIcon ?? item.icon : item.icon, 61 | size: style.activeIconSize, 62 | color: item.blend ? backgroundColor : null, 63 | ), 64 | ); 65 | } 66 | 67 | var noLabel = style.hideEmptyLabel && hasNoText(item); 68 | var icon = BlendImageIcon( 69 | active ? item.activeIcon ?? item.icon : item.icon, 70 | color: item.blend ? (c) : null, 71 | size: style.iconSize, 72 | ); 73 | var children = noLabel 74 | ? [icon] 75 | : [icon, Text(item.title ?? '', style: textStyle)]; 76 | return Container( 77 | padding: EdgeInsets.only(bottom: 2), 78 | child: Column( 79 | mainAxisAlignment: MainAxisAlignment.center, 80 | children: children, 81 | ), 82 | ); 83 | } 84 | 85 | @override 86 | bool fixed() { 87 | return true; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/src/style/fixed_tab_style.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import '../item.dart'; 20 | import 'blend_image_icon.dart'; 21 | import 'inner_builder.dart'; 22 | 23 | /// Convex shape is fixed center. 24 | class FixedTabStyle extends InnerBuilder { 25 | /// Index of the centered convex shape. 26 | final int convexIndex; 27 | 28 | /// Create style builder. 29 | FixedTabStyle({ 30 | required List items, 31 | required Color activeColor, 32 | required Color color, 33 | required this.convexIndex, 34 | }) : super(items: items, activeColor: activeColor, color: color); 35 | 36 | @override 37 | Widget build(BuildContext context, int index, bool active) { 38 | var c = active ? activeColor : color; 39 | var style = ofStyle(context); 40 | var item = items[index]; 41 | var textStyle = style.textStyle(c, item.fontFamily); 42 | 43 | if (index == convexIndex) { 44 | var item = items[convexIndex]; 45 | return Container( 46 | padding: EdgeInsets.only(bottom: 2), 47 | child: Column( 48 | mainAxisAlignment: MainAxisAlignment.end, 49 | children: [ 50 | BlendImageIcon( 51 | active ? item.activeIcon ?? item.icon : item.icon, 52 | color: item.blend ? (c) : null, 53 | size: style.activeIconSize, 54 | ), 55 | Text(item.title ?? '', style: textStyle) 56 | ], 57 | ), 58 | ); 59 | } 60 | 61 | var noLabel = style.hideEmptyLabel && hasNoText(item); 62 | var icon = BlendImageIcon( 63 | active ? item.activeIcon ?? item.icon : item.icon, 64 | size: style.iconSize, 65 | color: item.blend ? (c) : null, 66 | ); 67 | var children = noLabel 68 | ? [icon] 69 | : [icon, Text(item.title ?? '', style: textStyle)]; 70 | return Container( 71 | padding: EdgeInsets.only(bottom: 2), 72 | child: Column( 73 | mainAxisAlignment: MainAxisAlignment.end, 74 | children: children, 75 | ), 76 | ); 77 | } 78 | 79 | @override 80 | bool fixed() { 81 | return true; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /lib/src/style/flip_tab_style.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import '../item.dart'; 20 | import 'blend_image_icon.dart'; 21 | import 'inner_builder.dart'; 22 | import 'transition_container.dart'; 23 | 24 | /// Tab item are flipped when click. 25 | class FlipTabStyle extends InnerBuilder { 26 | /// Curve for flip transition. 27 | final Curve curve; 28 | 29 | /// Create style builder. 30 | FlipTabStyle({ 31 | required List items, 32 | required Color activeColor, 33 | required Color color, 34 | required this.curve, 35 | }) : super(items: items, activeColor: activeColor, color: color); 36 | 37 | @override 38 | Widget build(BuildContext context, int index, bool active) { 39 | var item = items[index]; 40 | var style = ofStyle(context); 41 | var textStyle = style.textStyle(activeColor, item.fontFamily); 42 | 43 | if (active) { 44 | var children = [ 45 | BlendImageIcon( 46 | item.activeIcon ?? item.icon, 47 | color: item.blend ? activeColor : null, 48 | size: style.activeIconSize, 49 | ), 50 | ]; 51 | var noLabel = style.hideEmptyLabel && hasNoText(item); 52 | if (!noLabel) { 53 | children.add(Text(item.title ?? '', style: textStyle)); 54 | } 55 | return TransitionContainer.flip( 56 | data: index, 57 | duration: Duration(milliseconds: 500), 58 | height: style.activeIconMargin + style.activeIconSize, 59 | bottomChild: Container( 60 | padding: EdgeInsets.only(bottom: 2), 61 | child: Column( 62 | mainAxisAlignment: MainAxisAlignment.end, 63 | children: children, 64 | ), 65 | ), 66 | topChild: Container( 67 | child: Center( 68 | child: BlendImageIcon( 69 | item.icon, 70 | color: item.blend ? color : null, 71 | size: style.iconSize, 72 | ), 73 | ), 74 | ), 75 | curve: curve, 76 | ); 77 | } 78 | return Center( 79 | child: BlendImageIcon( 80 | item.icon, 81 | color: item.blend ? color : null, 82 | size: style.iconSize, 83 | ), 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /lib/src/style/inner_builder.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/cupertino.dart'; 18 | 19 | import '../bar.dart'; 20 | import '../interface.dart'; 21 | import '../item.dart'; 22 | import 'internal_style_config.dart'; 23 | 24 | /// Simple builder which extend [DelegateBuilder] to provide some necessary config. 25 | abstract class InnerBuilder extends DelegateBuilder { 26 | /// List of [TabItem] stands for tabs. 27 | final List items; 28 | 29 | /// Color used when tab is active. 30 | final Color activeColor; 31 | 32 | /// Color used for tab. 33 | final Color color; 34 | 35 | /// Style hook to override the internal tab style 36 | StyleHook? _style; 37 | 38 | /// Create style builder. 39 | InnerBuilder( 40 | {required this.items, required this.activeColor, required this.color}); 41 | 42 | /// Get style config 43 | StyleHook ofStyle(BuildContext context) { 44 | return StyleProvider.of(context)?.style ?? (_style ??= InternalStyle()); 45 | } 46 | 47 | /// Return true if title text exists 48 | bool hasNoText(TabItem item) { 49 | return item.title == null || item.title!.isEmpty; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/src/style/internal_style_config.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import '../../convex_bottom_bar.dart'; 20 | 21 | /// Internal style configuration. 22 | class InternalStyle extends StyleHook { 23 | @override 24 | double? get iconSize { 25 | // use null will fallback to size of IconTheme 26 | return null; 27 | } 28 | 29 | @override 30 | double get activeIconMargin { 31 | return (ACTION_LAYOUT_SIZE - ACTION_INNER_BUTTON_SIZE) / 4; 32 | } 33 | 34 | @override 35 | double get activeIconSize { 36 | return ACTION_INNER_BUTTON_SIZE; 37 | } 38 | 39 | @override 40 | TextStyle textStyle(Color color, String? fontFamily) { 41 | return TextStyle(color: color, fontFamily: fontFamily); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/style/react_circle_tab_style.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import '../../convex_bottom_bar.dart'; 20 | import '../item.dart'; 21 | import 'blend_image_icon.dart'; 22 | import 'inner_builder.dart'; 23 | import 'transition_container.dart'; 24 | 25 | /// Convex shape is moved after selection. 26 | class ReactCircleTabStyle extends InnerBuilder { 27 | /// Color used as background of appbar and circle icon. 28 | final Color backgroundColor; 29 | 30 | /// Curve for tab transition. 31 | final Curve curve; 32 | 33 | /// Create style builder. 34 | ReactCircleTabStyle({ 35 | required List items, 36 | required Color activeColor, 37 | required Color color, 38 | required this.backgroundColor, 39 | required this.curve, 40 | }) : super(items: items, activeColor: activeColor, color: color); 41 | 42 | @override 43 | Widget build(BuildContext context, int index, bool active) { 44 | var item = items[index]; 45 | var style = ofStyle(context); 46 | var margin = style.activeIconMargin; 47 | if (active) { 48 | final item = items[index]; 49 | return TransitionContainer.scale( 50 | data: index, 51 | curve: curve, 52 | child: Container( 53 | // necessary otherwise the badge will not large enough 54 | width: style.layoutSize, 55 | height: style.layoutSize, 56 | margin: EdgeInsets.all(margin), 57 | decoration: BoxDecoration( 58 | shape: BoxShape.circle, 59 | color: active ? activeColor : color, 60 | ), 61 | child: BlendImageIcon( 62 | active ? item.activeIcon ?? item.icon : item.icon, 63 | size: style.activeIconSize, 64 | color: item.blend ? backgroundColor : null, 65 | ), 66 | ), 67 | ); 68 | } 69 | var textStyle = style.textStyle(color, item.fontFamily); 70 | var noLabel = style.hideEmptyLabel && hasNoText(item); 71 | var children = [ 72 | BlendImageIcon( 73 | active ? item.activeIcon ?? item.icon : item.icon, 74 | size: style.iconSize, 75 | color: item.blend ? color : null, 76 | ), 77 | ]; 78 | if (!noLabel) { 79 | children.add(Text(item.title ?? '', style: textStyle)); 80 | } 81 | return Container( 82 | padding: EdgeInsets.only(bottom: 2), 83 | child: Column( 84 | mainAxisAlignment: MainAxisAlignment.center, 85 | children: children, 86 | ), 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/src/style/react_tab_style.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import '../item.dart'; 20 | import 'blend_image_icon.dart'; 21 | import 'inner_builder.dart'; 22 | import 'transition_container.dart'; 23 | 24 | /// Convex shape is moved after selection. 25 | class ReactTabStyle extends InnerBuilder { 26 | /// Curve for tab transition. 27 | final Curve curve; 28 | 29 | /// Create style builder. 30 | ReactTabStyle({ 31 | required List items, 32 | required Color activeColor, 33 | required Color color, 34 | required this.curve, 35 | }) : super(items: items, activeColor: activeColor, color: color); 36 | 37 | @override 38 | Widget build(BuildContext context, int index, bool active) { 39 | var item = items[index]; 40 | var style = ofStyle(context); 41 | var noLabel = style.hideEmptyLabel && hasNoText(item); 42 | 43 | if (active) { 44 | var children = [ 45 | TransitionContainer.scale( 46 | data: index, 47 | curve: curve, 48 | child: BlendImageIcon( 49 | item.activeIcon ?? item.icon, 50 | color: item.blend ? activeColor : null, 51 | size: style.activeIconSize, 52 | ), 53 | ), 54 | ]; 55 | if (!noLabel) { 56 | children.add(Text(item.title ?? '', 57 | style: style.textStyle(activeColor, item.fontFamily))); 58 | } 59 | return Container( 60 | padding: const EdgeInsets.only(bottom: 2), 61 | child: Column( 62 | mainAxisAlignment: 63 | noLabel ? MainAxisAlignment.center : MainAxisAlignment.end, 64 | children: children, 65 | ), 66 | ); 67 | } 68 | var children = [ 69 | BlendImageIcon(item.icon, 70 | color: item.blend ? color : null, size: style.iconSize), 71 | ]; 72 | if (!noLabel) { 73 | children.add(Text(item.title ?? '', 74 | style: style.textStyle(color, item.fontFamily))); 75 | } 76 | return Container( 77 | padding: const EdgeInsets.only(bottom: 2), 78 | child: Column( 79 | mainAxisAlignment: 80 | noLabel ? MainAxisAlignment.center : MainAxisAlignment.end, 81 | children: children, 82 | ), 83 | ); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/src/style/styles.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/cupertino.dart'; 18 | 19 | import '../bar.dart'; 20 | import '../interface.dart'; 21 | import '../item.dart'; 22 | import 'fixed_circle_tab_style.dart'; 23 | import 'fixed_tab_style.dart'; 24 | import 'flip_tab_style.dart'; 25 | import 'react_circle_tab_style.dart'; 26 | import 'react_tab_style.dart'; 27 | import 'textin_tab_style.dart'; 28 | import 'titled_tab_style.dart'; 29 | 30 | /// Factory method to return the [DelegateBuilder] for each [TabStyle]. 31 | DelegateBuilder supportedStyle( 32 | TabStyle style, { 33 | required List items, 34 | required Color color, 35 | required Color activeColor, 36 | required Color backgroundColor, 37 | required Curve curve, 38 | }) { 39 | assert(items.isNotEmpty, 'items should not be empty'); 40 | assert( 41 | ((style == TabStyle.fixed || style == TabStyle.fixedCircle) && 42 | items.length.isOdd) || 43 | (style != TabStyle.fixed && style != TabStyle.fixedCircle), 44 | 'item count should be an odd number when using fixed/fixedCircle'); 45 | DelegateBuilder builder; 46 | switch (style) { 47 | case TabStyle.fixed: 48 | builder = FixedTabStyle( 49 | items: items, 50 | color: color, 51 | activeColor: activeColor, 52 | convexIndex: items.length ~/ 2, 53 | ); 54 | break; 55 | case TabStyle.fixedCircle: 56 | builder = FixedCircleTabStyle( 57 | items: items, 58 | color: color, 59 | activeColor: activeColor, 60 | backgroundColor: backgroundColor, 61 | convexIndex: items.length ~/ 2, 62 | ); 63 | break; 64 | case TabStyle.react: 65 | builder = ReactTabStyle( 66 | items: items, 67 | color: color, 68 | activeColor: activeColor, 69 | curve: curve, 70 | ); 71 | break; 72 | case TabStyle.reactCircle: 73 | builder = ReactCircleTabStyle( 74 | items: items, 75 | color: color, 76 | activeColor: activeColor, 77 | backgroundColor: backgroundColor, 78 | curve: curve, 79 | ); 80 | break; 81 | case TabStyle.textIn: 82 | assert(items.every((it) => it.title != null && it.title!.isNotEmpty), 83 | 'title is necessary for TabStyle.textIn'); 84 | builder = TextInTabStyle( 85 | items: items, 86 | color: color, 87 | activeColor: activeColor, 88 | curve: curve, 89 | ); 90 | break; 91 | case TabStyle.titled: 92 | assert(items.every((it) => it.title != null && it.title!.isNotEmpty), 93 | 'title is necessary for TabStyle.titled'); 94 | builder = TitledTabStyle( 95 | items: items, 96 | color: color, 97 | activeColor: activeColor, 98 | curve: curve, 99 | backgroundColor: backgroundColor, 100 | ); 101 | break; 102 | case TabStyle.flip: 103 | builder = FlipTabStyle( 104 | items: items, 105 | color: color, 106 | activeColor: activeColor, 107 | curve: curve, 108 | ); 109 | break; 110 | default: 111 | builder = ReactCircleTabStyle( 112 | items: items, 113 | color: color, 114 | activeColor: activeColor, 115 | backgroundColor: backgroundColor, 116 | curve: curve, 117 | ); 118 | break; 119 | } 120 | return builder; 121 | } 122 | -------------------------------------------------------------------------------- /lib/src/style/textin_tab_style.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import '../item.dart'; 20 | import 'blend_image_icon.dart'; 21 | import 'inner_builder.dart'; 22 | import 'transition_container.dart'; 23 | 24 | /// Tab icon, text animated with pop transition. 25 | class TextInTabStyle extends InnerBuilder { 26 | /// Curve for tab transition. 27 | final Curve curve; 28 | 29 | /// Create style builder. 30 | TextInTabStyle({ 31 | required List items, 32 | required Color activeColor, 33 | required Color color, 34 | required this.curve, 35 | }) : super(items: items, activeColor: activeColor, color: color); 36 | 37 | @override 38 | Widget build(BuildContext context, int index, bool active) { 39 | var item = items[index]; 40 | var style = ofStyle(context); 41 | if (active) { 42 | var textStyle = style.textStyle(activeColor, item.fontFamily); 43 | return Container( 44 | padding: const EdgeInsets.only(bottom: 2), 45 | child: Column( 46 | mainAxisAlignment: MainAxisAlignment.center, 47 | children: [ 48 | TransitionContainer.scale( 49 | data: index, 50 | curve: curve, 51 | child: BlendImageIcon( 52 | item.activeIcon ?? item.icon, 53 | color: item.blend ? activeColor : null, 54 | size: style.activeIconSize, 55 | ), 56 | ), 57 | TransitionContainer.slide( 58 | curve: curve, 59 | child: Text(item.title ?? '', style: textStyle), 60 | ), 61 | ], 62 | ), 63 | ); 64 | } 65 | 66 | return Center( 67 | child: BlendImageIcon(item.icon, 68 | size: style.iconSize, color: item.blend ? color : null), 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/src/style/titled_tab_style.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import '../item.dart'; 20 | import 'blend_image_icon.dart'; 21 | import 'inner_builder.dart'; 22 | import 'transition_container.dart'; 23 | 24 | /// Tab icon, text animated with pop transition. 25 | class TitledTabStyle extends InnerBuilder { 26 | /// Curve for tab transition. 27 | final Curve curve; 28 | 29 | /// Color used as background of appbar and circle icon. 30 | final Color backgroundColor; 31 | 32 | /// Previous active tab index. 33 | int _preActivate = -1; 34 | 35 | /// Create style builder. 36 | TitledTabStyle({ 37 | required List items, 38 | required Color activeColor, 39 | required Color color, 40 | required this.curve, 41 | required this.backgroundColor, 42 | }) : super(items: items, activeColor: activeColor, color: color); 43 | 44 | @override 45 | Widget build(BuildContext context, int index, bool active) { 46 | var pre = _preActivate; 47 | if (active) { 48 | _preActivate = index; 49 | } 50 | var item = items[index]; 51 | var style = ofStyle(context); 52 | var margin = style.activeIconMargin; 53 | 54 | if (active) { 55 | return TransitionContainer.slide( 56 | data: index, 57 | duration: Duration(milliseconds: 200), 58 | curve: curve, 59 | child: Container( 60 | // necessary otherwise the badge will not large enough 61 | width: style.layoutSize, 62 | height: style.layoutSize, 63 | margin: EdgeInsets.all(margin), 64 | decoration: BoxDecoration(shape: BoxShape.circle, color: activeColor), 65 | child: BlendImageIcon( 66 | item.activeIcon ?? item.icon, 67 | size: style.activeIconSize, 68 | color: item.blend ? backgroundColor : null, 69 | ), 70 | ), 71 | ); 72 | } 73 | 74 | var textStyle = style.textStyle(activeColor, item.fontFamily); 75 | if (pre == index) { 76 | return Stack( 77 | clipBehavior: Clip.hardEdge, 78 | alignment: Alignment.center, 79 | children: [ 80 | Text(item.title ?? '', style: textStyle), 81 | TransitionContainer.slide( 82 | reverse: true, 83 | curve: curve, 84 | child: Container( 85 | margin: EdgeInsets.all(margin), 86 | decoration: BoxDecoration( 87 | shape: BoxShape.circle, 88 | color: activeColor, 89 | ), 90 | child: BlendImageIcon( 91 | item.activeIcon ?? item.icon, 92 | size: style.activeIconSize, 93 | color: item.blend ? backgroundColor : null, 94 | ), 95 | ), 96 | ) 97 | ], 98 | ); 99 | } 100 | return Center(child: Text(item.title ?? '', style: textStyle)); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/src/style/transition_container.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:flutter/material.dart'; 18 | 19 | import 'transition_container_builder.dart'; 20 | 21 | /// Add controller with provided transition api, such as [SlideTransition], [ScaleTransition]. 22 | class TransitionContainer extends StatefulWidget { 23 | /// Build transition. 24 | final TransitionContainerBuilder builder; 25 | 26 | /// Transition duration. 27 | final Duration? duration; 28 | 29 | /// Control whether the animation should be skipped when widget change. 30 | final int? data; 31 | 32 | /// Wrap a widget with scale transition. 33 | TransitionContainer.scale({ 34 | required Widget child, 35 | required Curve curve, 36 | this.duration, 37 | this.data, 38 | }) : builder = ScaleBuilder(curve: curve, child: child); 39 | 40 | /// Wrap a widget with slide transition. 41 | TransitionContainer.slide({ 42 | required Widget child, 43 | required Curve curve, 44 | this.duration, 45 | bool reverse = false, 46 | this.data, 47 | }) : builder = SlideBuilder(curve: curve, child: child, reverse: reverse); 48 | 49 | /// Wrap a widget with flip transition. 50 | TransitionContainer.flip({ 51 | required Widget topChild, 52 | required Widget bottomChild, 53 | required Curve curve, 54 | required double height, 55 | this.duration, 56 | this.data, 57 | }) : builder = FlipBuilder( 58 | height, 59 | curve: curve, 60 | topChild: topChild, 61 | bottomChild: bottomChild, 62 | ); 63 | 64 | @override 65 | _State createState() { 66 | return _State(); 67 | } 68 | } 69 | 70 | class _State extends State with TickerProviderStateMixin { 71 | AnimationController? animationController; 72 | late Animation animation; 73 | 74 | @override 75 | void initState() { 76 | super.initState(); 77 | _setAnimation(); 78 | } 79 | 80 | void _setAnimation() { 81 | final controller = AnimationController( 82 | vsync: this, 83 | duration: widget.duration ?? Duration(milliseconds: 150), 84 | )..addListener(() => setState(() {})); 85 | controller.forward(); 86 | animation = widget.builder.animation(controller); 87 | animationController = controller; 88 | } 89 | 90 | @override 91 | void didUpdateWidget(TransitionContainer oldWidget) { 92 | super.didUpdateWidget(oldWidget); 93 | if (oldWidget.builder.runtimeType != widget.builder.runtimeType) { 94 | animationController?.dispose(); 95 | _setAnimation(); 96 | } else { 97 | if (widget.data == oldWidget.data) { 98 | return; 99 | } 100 | animationController?.reset(); 101 | animationController?.forward(); 102 | } 103 | } 104 | 105 | @override 106 | void dispose() { 107 | animationController?.dispose(); 108 | super.dispose(); 109 | } 110 | 111 | @override 112 | Widget build(BuildContext context) { 113 | return widget.builder.build(animation); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /lib/src/style/transition_container_builder.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'dart:math'; 18 | 19 | import 'package:flutter/cupertino.dart'; 20 | 21 | import 'transition_container.dart'; 22 | 23 | /// Interface to provide a transition, work with [TransitionContainer]. 24 | abstract class TransitionContainerBuilder { 25 | /// Curve for animation. 26 | final Curve curve; 27 | 28 | /// Create style builder. 29 | TransitionContainerBuilder(this.curve); 30 | 31 | /// Animation used for widget. 32 | Animation animation(AnimationController controller); 33 | 34 | /// Return animated widget with provided animation. 35 | Widget build(Animation animation); 36 | } 37 | 38 | /// Scale transition builder. 39 | class ScaleBuilder extends TransitionContainerBuilder { 40 | /// The target widget to scale with. 41 | Widget child; 42 | 43 | @override 44 | Animation animation(AnimationController controller) { 45 | return CurvedAnimation(parent: controller, curve: curve); 46 | } 47 | 48 | @override 49 | Widget build(Animation animation) { 50 | return ScaleTransition(scale: animation, child: child); 51 | } 52 | 53 | /// Create scale builder 54 | ScaleBuilder({required Curve curve, required this.child}) : super(curve); 55 | } 56 | 57 | /// Slide transition builder. 58 | class SlideBuilder extends TransitionContainerBuilder { 59 | /// The target widget to slide with. 60 | Widget child; 61 | 62 | /// slide direction. 63 | final bool reverse; 64 | 65 | /// Create slide builder. 66 | SlideBuilder( 67 | {required Curve curve, required this.child, required this.reverse}) 68 | : super(curve); 69 | 70 | @override 71 | Widget build(Animation animation) { 72 | return SlideTransition(position: animation, child: child); 73 | } 74 | 75 | @override 76 | Animation animation(AnimationController controller) { 77 | return Tween( 78 | begin: reverse ? Offset.zero : const Offset(0.0, 2.0), 79 | end: reverse ? const Offset(0.0, 2.0) : Offset.zero, 80 | ).animate(CurvedAnimation(parent: controller, curve: curve)); 81 | } 82 | } 83 | 84 | /// This flip animation is origin from [https://github.com/deven98/flip_box_bar/blob/master/lib/src/flip_box.dart] 85 | /// UX => ![](https://cdn.dribbble.com/users/1094383/screenshots/4811135/800_5.gif). 86 | class FlipBuilder extends TransitionContainerBuilder { 87 | /// Top widget. 88 | final Widget topChild; 89 | 90 | /// Bottom widget. 91 | final Widget bottomChild; 92 | 93 | /// Size of builder. 94 | final double height; 95 | 96 | /// Create flip builder 97 | FlipBuilder( 98 | this.height, { 99 | required Curve curve, 100 | required this.topChild, 101 | required this.bottomChild, 102 | }) : super(curve); 103 | 104 | @override 105 | Animation animation(AnimationController controller) { 106 | return Tween(begin: 0.0, end: pi / 2).animate( 107 | CurvedAnimation(parent: controller, curve: curve), 108 | ); 109 | } 110 | 111 | @override 112 | Widget build(Animation animation) { 113 | return Container( 114 | child: Stack( 115 | children: [ 116 | Transform( 117 | alignment: Alignment.bottomCenter, 118 | transform: Matrix4.identity() 119 | ..setEntry(3, 2, 0.001) 120 | ..translate(0.0, (cos(animation.value) * (height / 2)), 121 | ((height / 2) * sin(animation.value))) 122 | ..rotateX(-(pi / 2) + animation.value), 123 | child: Container( 124 | child: Center(child: bottomChild), 125 | ), 126 | ), 127 | animation.value < (85 * pi / 180) 128 | ? Transform( 129 | alignment: Alignment.bottomCenter, 130 | transform: Matrix4.identity() 131 | ..setEntry(3, 2, 0.001) 132 | ..translate( 133 | 0.0, 134 | -(height / 2) * sin(animation.value), 135 | ((height / 2) * cos(animation.value)), 136 | ) 137 | ..rotateX(animation.value), 138 | child: Container( 139 | alignment: Alignment.bottomCenter, 140 | child: Center(child: topChild), 141 | ), 142 | ) 143 | : Container(), 144 | ], 145 | ), 146 | ); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: convex_bottom_bar 2 | description: A Flutter package which implements a ConvexAppBar to show a convex tab in the bottom bar. Theming supported. 3 | version: 3.2.0 4 | homepage: https://github.com/hacktons/convex_bottom_bar 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | dev_dependencies: 14 | flutter_test: 15 | sdk: flutter 16 | pedantic: ^1.10.0-nullsafety.3 17 | 18 | # For information on the generic Dart part of this file, see the 19 | # following page: https://dart.dev/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.dev/assets-and-images/#from-packages 31 | # 32 | # An image asset can refer to one or more resolution-specific "variants", see 33 | # https://flutter.dev/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.dev/custom-fonts/#from-packages 54 | -------------------------------------------------------------------------------- /test/provider_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:convex_bottom_bar/convex_bottom_bar.dart'; 18 | import 'package:flutter/material.dart'; 19 | import 'package:flutter_test/flutter_test.dart'; 20 | 21 | import 'widget_test.dart'; 22 | 23 | void main() { 24 | testWidgets('Test StyleProvider', (WidgetTester tester) async { 25 | await tester.pumpWidget(material(StyleProvider( 26 | style: Style(), 27 | child: Directionality( 28 | textDirection: TextDirection.rtl, 29 | child: ConvexAppBar( 30 | items: [ 31 | TabItem(icon: Icons.gradient, title: ''), 32 | TabItem(icon: Icons.help_outline, title: ''), 33 | TabItem(icon: Icons.work, title: ''), 34 | ], 35 | ), 36 | )))); 37 | }); 38 | // no longer needed for null safety 39 | /*testWidgets('Test Provider assertion, style should not be null', 40 | (WidgetTester tester) async { 41 | expect(() async { 42 | await tester.pumpWidget(StyleProvider( 43 | style: null, 44 | child: null, 45 | )); 46 | }, throwsAssertionError); 47 | });*/ 48 | /*testWidgets('Test Provider assertion, child should not be null', 49 | (WidgetTester tester) async { 50 | expect(() async { 51 | await tester.pumpWidget(StyleProvider( 52 | style: Style(), 53 | child: null, 54 | )); 55 | }, throwsAssertionError); 56 | });*/ 57 | } 58 | 59 | /// testWidgets('MyWidget asserts invalid bounds', (WidgetTester tester) async { 60 | /// await tester.pumpWidget(MyWidget(-1)); 61 | /// expect(tester.takeException(), isAssertionError); // or isNull, as appropriate. 62 | /// }); 63 | class Style extends StyleHook { 64 | @override 65 | double get iconSize { 66 | return 10; 67 | } 68 | 69 | @override 70 | TextStyle textStyle(Color color, String? fontFamily) { 71 | return TextStyle(color: color, fontFamily: fontFamily); 72 | } 73 | 74 | @override 75 | double get activeIconSize { 76 | return 10; 77 | } 78 | 79 | @override 80 | double get activeIconMargin { 81 | return 1; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/utils_test.dart: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Chaobin Wu 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import 'package:convex_bottom_bar/src/reused_gradient.dart'; 18 | import 'package:flutter/material.dart'; 19 | import 'package:flutter_test/flutter_test.dart'; 20 | 21 | void main() { 22 | test('Test Gradient', () { 23 | var gradient = ReusedGradient(); 24 | gradient.gradient = LinearGradient( 25 | begin: Alignment.topLeft, 26 | end: Alignment.bottomRight, 27 | colors: [Colors.blue, Colors.redAccent, Colors.green, Colors.blue], 28 | tileMode: TileMode.repeated, 29 | ); 30 | gradient.updateWith(Paint(), size: Rect.fromLTRB(0, 0, 10, 10)); 31 | expect(gradient.valid, true); 32 | }); 33 | } 34 | --------------------------------------------------------------------------------