├── .github └── workflows │ ├── master.yml │ └── release.yml ├── .gitignore ├── .pubignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── art ├── screen1.png └── screen2.png ├── example ├── .gitignore ├── .metadata ├── .pubignore ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── percentindicatorexample │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ ├── 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 │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── 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 │ │ └── main.m ├── lib │ ├── main.dart │ ├── multi_segment_page.dart │ ├── sample_circular_page.dart │ ├── sample_linear_page.dart │ └── segment_page.dart ├── percent_indicator_example.iml ├── percent_indicator_example_android.iml ├── pubspec.lock └── pubspec.yaml ├── lib ├── circular_percent_indicator.dart ├── flutter_percent_indicator.dart ├── linear_percent_indicator.dart ├── multi_segment_linear_indicator.dart └── percent_indicator.dart ├── percent_indicator.iml ├── pubspec.lock ├── pubspec.yaml ├── screenshots ├── circular_percent_indicator.gif ├── circular_percent_indicator.png ├── linear_percent_indicator.gif └── linear_percent_indicator.png └── test └── percent_indicator_test.dart /.github/workflows/master.yml: -------------------------------------------------------------------------------- 1 | name: Package Publish dry-run 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | jobs: 8 | 9 | dry-run: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: 'Checkout' 13 | uses: actions/checkout@v1 14 | - name: 'Dry-run' 15 | uses: Omega365/actions-flutter-pub-publisher@master 16 | with: 17 | credential: ${{secrets.CREDENTIAL_JSON}} 18 | flutter_package: true 19 | skip_test: true 20 | dry_run: true 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Package Publish 2 | 3 | on: 4 | push: 5 | branches: [release ] 6 | 7 | jobs: 8 | check_version: 9 | name: "Check Version Tag" 10 | runs-on: ubuntu-20.04 11 | steps: 12 | - name: Checkout Repository 13 | uses: actions/checkout@v3 14 | 15 | - name: Get Latest Version Tag 16 | id: previoustag 17 | uses: WyriHaximus/github-action-get-previous-tag@master 18 | 19 | - name: Print Latest Version 20 | run: "echo Latest tag: ${{ steps.previoustag.outputs.tag }}" 21 | 22 | - name: Get Version from pubspec.yaml 23 | id: config 24 | uses: CumulusDS/get-yaml-paths-action@v0.1.0 25 | with: 26 | file: pubspec.yaml 27 | version_name: version 28 | 29 | - name: Print New Version 30 | run: "echo New version from pubspec.yaml: ${{ steps.config.outputs.version_name }}" 31 | 32 | - name: Compare Version 33 | if: ${{ steps.config.outputs.version_name == steps.previoustag.outputs.tag }} 34 | run: | 35 | echo "The version from pubspec.yaml is the same as the previous tag, please update the version" 36 | exit 1 37 | 38 | - name: Save New Version 39 | run: "echo ${{ steps.config.outputs.version_name }} > version.txt" 40 | 41 | - name: Upload New Version 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: home 45 | path: version.txt 46 | 47 | publish: 48 | needs: 49 | - check_version 50 | runs-on: ubuntu-20.04 51 | steps: 52 | - name: Checkout Repository 53 | uses: actions/checkout@v3 54 | 55 | - name: Publish Package 56 | uses: Omega365/actions-flutter-pub-publisher@master 57 | with: 58 | credential: "${{ secrets.CREDENTIAL_JSON }}" 59 | dry_run: false 60 | flutter_package: true 61 | skip_test: true 62 | 63 | tag: 64 | name: "Tag Version" 65 | needs: 66 | - publish 67 | runs-on: ubuntu-20.04 68 | steps: 69 | - name: Download New Version 70 | uses: actions/download-artifact@v4 71 | with: 72 | name: home 73 | 74 | - name: Set and Tag the New Version 75 | run: | 76 | echo "RELEASE_VERSION=$(cat home/version.txt)" >> $GITHUB_ENV 77 | shell: bash 78 | 79 | - name: Create New Tag 80 | uses: tvdias/github-tagger@v0.0.2 81 | with: 82 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 83 | tag: "${{ env.RELEASE_VERSION }}" 84 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | .idea/ 7 | 8 | build/ 9 | ios/.generated/ 10 | ios/Flutter/Generated.xcconfig 11 | ios/Runner/GeneratedPluginRegistrant.* 12 | example/ios/Flutter/flutter_export_environment.sh 13 | example/ios/Flutter/.last_build_id 14 | example/.idea/** 15 | 16 | example/.idea/libraries/Dart_SDK.xml 17 | example/.idea/libraries/Flutter_for_Android.xml 18 | example/.idea/modules.xml 19 | example/.idea/runConfigurations/main_dart.xml 20 | example/.idea/workspace.xml -------------------------------------------------------------------------------- /.pubignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | .idea/ 7 | 8 | build/ 9 | ios/.generated/ 10 | ios/Flutter/Generated.xcconfig 11 | ios/Runner/GeneratedPluginRegistrant.* 12 | example/ios/Flutter/flutter_export_environment.sh 13 | example/ios/Flutter/.last_build_id 14 | example/.idea/** 15 | 16 | example/.idea/libraries/Dart_SDK.xml 17 | example/.idea/libraries/Flutter_for_Android.xml 18 | example/.idea/modules.xml 19 | example/.idea/runConfigurations/main_dart.xml 20 | example/.idea/workspace.xml -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # [4.2.5] 2 | 3 | - Added clipRotatedLinearGradient param, thanks kamil-matula 4 | - Enabling multi-segment progress bar with animations and stripes, thanks naseerahmedaziz 5 | - A lot of fixes. 6 | 7 | # [4.2.4] 8 | 9 | - A lot of fixes. 10 | 11 | # [4.2.3] 12 | 13 | - Screenshots added. Thanks FMorschel. 14 | 15 | # [4.2.2] 16 | 17 | - This version is for flutter 3.0 >=. 18 | 19 | # [4.2.1] 20 | 21 | - Add compatibility with lower versions of flutter. 22 | 23 | # [4.2.0] 24 | 25 | - Flutter 3.0 updated. 26 | 27 | # [4.0.1] 28 | 29 | - Fixed a bug: 1% progress should be inside. Thanks Gábor. 30 | 31 | # [4.0.0] 32 | 33 | - [BREAKING CHANGE] now `radius` is a real radius and not diameter. Thanks to `Nipun Shah`. 34 | 35 | # [3.5.0] 36 | 37 | - `linearStrokeCap` was deprecated. Use `barRadius` instead. Thanks to `martinkong0806`. 38 | 39 | # [3.4.0] 40 | 41 | - Null safety migration completed. 42 | - Widget Indicator works when using Arc mode on `CircularPercentIndicator`. 43 | 44 | # [3.0.1] 45 | 46 | - `linearGradientBackgroundColor` was added for `LinearPercentIndicator`. Thanks Jeremiah Parrack. 47 | 48 | # [3.3.0-nullsafety.1 - 3.0.0] 49 | 50 | - Null safety migration. 51 | 52 | # 2.1.9 - 2.1.9+1 53 | 54 | - Users can stop `animation` after this was initialize. 55 | - Added Half Arc for `CircularPercentIndicator` , added by Vivek 56 | - Extra height was removed. Thanks Brayan Cantos. 57 | 58 | # 2.1.8 59 | 60 | - New field was added : `rotateLinearGradient` -> Enable rotation of linear gradient in circular percent indicator. Added by `ecokeco`. 61 | 62 | # 2.1.7 - 2.1.7+4 63 | 64 | - Added optional `widgetIndicator` for `CircularPercentIndicator` and `LinearPercentIndicator`, it's an indicator displayed at the end of the progress, it only works when the `animation` is `true`. Thanks to Brayan Cantos for the contribution 65 | 66 | # 2.1.6 67 | 68 | - Added optional `onAnimationEnd` for `CircularPercentIndicator` and `LinearPercentIndicator`, it's a callback when the animation ends. Thanks to Brayan Cantos for the contribution 69 | 70 | # 2.1.5 71 | 72 | - Added optional `backgroundWidth` for `CircularPercentIndicator`. Thanks to CircuitGuy for the contribution 73 | 74 | # 2.1.4 75 | 76 | - `restartAnimation` was added to restart the animation when reached the end. Thanks to superciccio for the contribution 77 | 78 | # 2.1.3 79 | 80 | - Added `StrokeCap` on background. Thanks @mifama 81 | 82 | # 2.1.2 83 | 84 | - `curve` was added in both indicators. 85 | 86 | # 2.1.1 87 | 88 | - `LinearPercentIndicator` now can display only part of linear gradient using `clipLinearGradient`. 89 | 90 | # 2.1.0 91 | 92 | - vertical padding removed from `LinearPercentIndicator`. 93 | 94 | # 2.0.1 95 | 96 | - `maskFilter` was added for `LinearPercentIndicator` and `CircularPercentIndicator`. Thanks to akdu12 for the contribution 97 | 98 | # 2.0.0 99 | 100 | - `linearGradient` was added for `LinearPercentIndicator` and `CircularPercentIndicator`. 101 | - `reverse`, `arcType` and `arcBackgroundColor` were added to the `CircularPercentIndicator`. 102 | 103 | # 1.0.16 104 | 105 | - `width` is optional for `LinearPercentIndicator` widget, 106 | 107 | # 1.0.15 108 | 109 | - Added `addAutomaticKeepAlive` property to preserve the state of the widget. 110 | - Added `isRTL` for `LinearPercentIndicator` widget. 111 | 112 | # 1.0.14 113 | 114 | - Fixed bug using animateFromLastPercent. Thanks joelbrostrom 115 | 116 | # 1.0.13 117 | 118 | - Padding removed from leading and trailing on LinearPercentIndicator, now you can use Expanded or Flexible. 119 | - Fixed animation when refresh the widget with different duration 120 | 121 | # 1.0.12 122 | 123 | - animateFromLastPercent property was added for LinearPercentIndicator and CircularPercentIndicator 124 | 125 | # 1.0.11 126 | 127 | - startAngle for CircularPercentIndicator was added 128 | 129 | # 1.0.10 130 | 131 | - animation update bug fixed. Thanks @Tiagosito 132 | 133 | # 1.0.9 134 | 135 | - padding property was added for LinearPercentIndicator 136 | 137 | # 1.0.7, 1.0.8 138 | 139 | - alignment property was added for LinearPercentIndicator 140 | 141 | # 1.0.5, 1.0.6 142 | 143 | - fillColor property was added to LinearPercentIndicator 144 | 145 | # 1.0.1 - 1.0.4 146 | 147 | - Readme updated 148 | 149 | # 1.0.0 150 | 151 | Initial release of the multi_segment_linear_indicator package. 152 | 153 | ### Features 154 | 155 | - Three independently animated segments 156 | - Customizable colors for each segment 157 | - Optional striped pattern for the middle segment 158 | - Smooth animations with customizable duration and curve 159 | - Configurable border radius 160 | - RTL support 161 | - Flexible sizing and padding options 162 | 163 | ### Documentation 164 | 165 | - Added comprehensive README with usage examples 166 | - Added API documentation 167 | - Added example application 168 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2018, diegoveloper@gmail.com 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Percent Indicator 2 | 3 | [![pub package](https://img.shields.io/pub/v/percent_indicator.svg)](https://pub.dev/packages/percent_indicator) 4 | 5 | Circular, Linear and Multi-segment linear percent indicators 6 | 7 |

8 | drawing drawing 9 |

10 | 11 | ## Features 12 | 13 | - Circle percent indicator 14 | - Linear percent indicator 15 | - Multi-segment linear indicator 16 | - Toggle animation 17 | - Custom duration of the animation 18 | - Progress based on a percentage value 19 | - Progress and background color 20 | - Custom size 21 | - Left , right or center child for Linear percent indicator 22 | - Top, bottom or center child for Circular percent indicator 23 | - Progress Color using gradients 24 | 25 | ## Getting started 26 | 27 | You should ensure that you add the router as a dependency in your flutter project. 28 | 29 | ```yaml 30 | dependencies: 31 | percent_indicator: ^4.2.5 32 | ``` 33 | 34 | You should then run `flutter packages upgrade` or update your packages in IntelliJ. 35 | 36 | ## Example Project 37 | 38 | There is a example project in the `example` folder. Check it out. Otherwise, keep reading to get up and running. 39 | 40 | ## Usage 41 | 42 | Need to include the import the package to the dart file where it will be used, use the below command, 43 | 44 | ```dart 45 | import 'package:percent_indicator/percent_indicator.dart'; 46 | ``` 47 | 48 | **Circular percent indicator** 49 | 50 | Basic Widget 51 | 52 | ```dart 53 | new CircularPercentIndicator( 54 | radius: 60.0, 55 | lineWidth: 5.0, 56 | percent: 1.0, 57 | center: new Text("100%"), 58 | progressColor: Colors.green, 59 | ) 60 | ``` 61 | 62 | Complete example 63 | 64 | ```dart 65 | @override 66 | Widget build(BuildContext context) { 67 | return Scaffold( 68 | appBar: new AppBar( 69 | title: new Text("Circular Percent Indicators"), 70 | ), 71 | body: Center( 72 | child: ListView( 73 | children: [ 74 | new CircularPercentIndicator( 75 | radius: 100.0, 76 | lineWidth: 10.0, 77 | percent: 0.8, 78 | header: new Text("Icon header"), 79 | center: new Icon( 80 | Icons.person_pin, 81 | size: 50.0, 82 | color: Colors.blue, 83 | ), 84 | backgroundColor: Colors.grey, 85 | progressColor: Colors.blue, 86 | ), 87 | new CircularPercentIndicator( 88 | radius: 130.0, 89 | animation: true, 90 | animationDuration: 1200, 91 | lineWidth: 15.0, 92 | percent: 0.4, 93 | center: new Text( 94 | "40 hours", 95 | style: 96 | new TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0), 97 | ), 98 | circularStrokeCap: CircularStrokeCap.butt, 99 | backgroundColor: Colors.yellow, 100 | progressColor: Colors.red, 101 | ), 102 | new CircularPercentIndicator( 103 | radius: 120.0, 104 | lineWidth: 13.0, 105 | animation: true, 106 | percent: 0.7, 107 | center: new Text( 108 | "70.0%", 109 | style: 110 | new TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0), 111 | ), 112 | footer: new Text( 113 | "Sales this week", 114 | style: 115 | new TextStyle(fontWeight: FontWeight.bold, fontSize: 17.0), 116 | ), 117 | circularStrokeCap: CircularStrokeCap.round, 118 | progressColor: Colors.purple, 119 | ), 120 | Padding( 121 | padding: EdgeInsets.all(15.0), 122 | child: new CircularPercentIndicator( 123 | radius: 60.0, 124 | lineWidth: 5.0, 125 | percent: 1.0, 126 | center: new Text("100%"), 127 | progressColor: Colors.green, 128 | ), 129 | ), 130 | Container( 131 | padding: EdgeInsets.all(15.0), 132 | child: new Row( 133 | mainAxisAlignment: MainAxisAlignment.center, 134 | children: [ 135 | new CircularPercentIndicator( 136 | radius: 45.0, 137 | lineWidth: 4.0, 138 | percent: 0.10, 139 | center: new Text("10%"), 140 | progressColor: Colors.red, 141 | ), 142 | new Padding( 143 | padding: EdgeInsets.symmetric(horizontal: 10.0), 144 | ), 145 | new CircularPercentIndicator( 146 | radius: 45.0, 147 | lineWidth: 4.0, 148 | percent: 0.30, 149 | center: new Text("30%"), 150 | progressColor: Colors.orange, 151 | ), 152 | new Padding( 153 | padding: EdgeInsets.symmetric(horizontal: 10.0), 154 | ), 155 | new CircularPercentIndicator( 156 | radius: 45.0, 157 | lineWidth: 4.0, 158 | percent: 0.60, 159 | center: new Text("60%"), 160 | progressColor: Colors.yellow, 161 | ), 162 | new Padding( 163 | padding: EdgeInsets.symmetric(horizontal: 10.0), 164 | ), 165 | new CircularPercentIndicator( 166 | radius: 45.0, 167 | lineWidth: 4.0, 168 | percent: 0.90, 169 | center: new Text("90%"), 170 | progressColor: Colors.green, 171 | ) 172 | ], 173 | ), 174 | ) 175 | ]), 176 | ), 177 | ); 178 | } 179 | ``` 180 | 181 |

182 | 183 |

184 | 185 | **Linear percent indicator** 186 | 187 | Basic Widget 188 | 189 | ```dart 190 | new LinearPercentIndicator( 191 | width: 140.0, 192 | lineHeight: 14.0, 193 | percent: 0.5, 194 | backgroundColor: Colors.grey, 195 | progressColor: Colors.blue, 196 | ), 197 | 198 | ``` 199 | 200 | Complete example 201 | 202 | ```dart 203 | @override 204 | Widget build(BuildContext context) { 205 | return Scaffold( 206 | appBar: new AppBar( 207 | title: new Text("Linear Percent Indicators"), 208 | ), 209 | body: Center( 210 | child: Column( 211 | mainAxisAlignment: MainAxisAlignment.center, 212 | children: [ 213 | Padding( 214 | padding: EdgeInsets.all(15.0), 215 | child: new LinearPercentIndicator( 216 | width: 140.0, 217 | lineHeight: 14.0, 218 | percent: 0.5, 219 | center: Text( 220 | "50.0%", 221 | style: new TextStyle(fontSize: 12.0), 222 | ), 223 | trailing: Icon(Icons.mood), 224 | linearStrokeCap: LinearStrokeCap.roundAll, 225 | backgroundColor: Colors.grey, 226 | progressColor: Colors.blue, 227 | ), 228 | ), 229 | Padding( 230 | padding: EdgeInsets.all(15.0), 231 | child: new LinearPercentIndicator( 232 | width: 170.0, 233 | animation: true, 234 | animationDuration: 1000, 235 | lineHeight: 20.0, 236 | leading: new Text("left content"), 237 | trailing: new Text("right content"), 238 | percent: 0.2, 239 | center: Text("20.0%"), 240 | linearStrokeCap: LinearStrokeCap.butt, 241 | progressColor: Colors.red, 242 | ), 243 | ), 244 | Padding( 245 | padding: EdgeInsets.all(15.0), 246 | child: new LinearPercentIndicator( 247 | width: MediaQuery.of(context).size.width - 50, 248 | animation: true, 249 | lineHeight: 20.0, 250 | animationDuration: 2000, 251 | percent: 0.9, 252 | center: Text("90.0%"), 253 | linearStrokeCap: LinearStrokeCap.roundAll, 254 | progressColor: Colors.greenAccent, 255 | ), 256 | ), 257 | Padding( 258 | padding: EdgeInsets.all(15.0), 259 | child: new LinearPercentIndicator( 260 | width: MediaQuery.of(context).size.width - 50, 261 | animation: true, 262 | lineHeight: 20.0, 263 | animationDuration: 2500, 264 | percent: 0.8, 265 | center: Text("80.0%"), 266 | linearStrokeCap: LinearStrokeCap.roundAll, 267 | progressColor: Colors.green, 268 | ), 269 | ), 270 | Padding( 271 | padding: EdgeInsets.all(15.0), 272 | child: Column( 273 | children: [ 274 | new LinearPercentIndicator( 275 | width: 100.0, 276 | lineHeight: 8.0, 277 | percent: 0.2, 278 | progressColor: Colors.red, 279 | ), 280 | new LinearPercentIndicator( 281 | width: 100.0, 282 | lineHeight: 8.0, 283 | percent: 0.5, 284 | progressColor: Colors.orange, 285 | ), 286 | new LinearPercentIndicator( 287 | width: 100.0, 288 | lineHeight: 8.0, 289 | percent: 0.9, 290 | progressColor: Colors.blue, 291 | ) 292 | ], 293 | ), 294 | ), 295 | ], 296 | ), 297 | ), 298 | ); 299 | } 300 | ``` 301 | 302 | **Multi-segment linear indicator** 303 | 304 | Basic Widget 305 | 306 | ```dart 307 | new MultiSegmentLinearIndicator( 308 | width: MediaQuery.of(context).size.width - 64, 309 | lineHeight: 30.0, 310 | firstSegmentPercent: 0.25, 311 | secondSegmentPercent: 0.4, 312 | thirdSegmentPercent: 0.35, 313 | firstSegmentColor: Color(0xFF4285F4), 314 | secondSegmentColor: Color(0xFF6DD5F6), 315 | thirdSegmentColor: Color(0xFFEFEFEF), 316 | enableStripes: [1], 317 | barRadius: Radius.circular(10.0), 318 | animation: true, 319 | animationDuration: 1000, 320 | curve: Curves.easeInOut, 321 | animateFromLastPercent: true, 322 | onAnimationEnd: () { 323 | ScaffoldMessenger.of(context).showSnackBar( 324 | const SnackBar( 325 | content: Text('Animation completed!'), 326 | duration: Duration(seconds: 1), 327 | ), 328 | ); 329 | }, 330 | ), 331 | 332 | ``` 333 | 334 | Complete example 335 | 336 | ```dart 337 | @override 338 | Widget build(BuildContext context) { 339 | return Scaffold( 340 | appBar: AppBar( 341 | title: Text('Multi Segment Progress'), 342 | ), 343 | body: Center( 344 | child: Padding( 345 | padding: const EdgeInsets.all(20.0), 346 | child: Column( 347 | mainAxisAlignment: MainAxisAlignment.center, 348 | children: [ 349 | MultiSegmentLinearIndicator( 350 | width: MediaQuery.of(context).size.width - 64, 351 | lineHeight: 30.0, 352 | firstSegmentPercent: 0.25, 353 | secondSegmentPercent: 0.4, 354 | thirdSegmentPercent: 0.35, 355 | firstSegmentColor: Color(0xFF4285F4), 356 | secondSegmentColor: Color(0xFF6DD5F6), 357 | thirdSegmentColor: Color(0xFFEFEFEF), 358 | enableStripes: [1], 359 | barRadius: Radius.circular(10.0), 360 | animation: true, 361 | animationDuration: 1000, 362 | curve: Curves.easeInOut, 363 | animateFromLastPercent: true, 364 | onAnimationEnd: () { 365 | ScaffoldMessenger.of(context).showSnackBar( 366 | const SnackBar( 367 | content: Text('Animation completed!'), 368 | duration: Duration(seconds: 1), 369 | ), 370 | ); 371 | }, 372 | ), 373 | SizedBox(height: 10), 374 | Text( 375 | 'Static with easeInOut: 25% - 40% - 35%', 376 | style: TextStyle(fontSize: 16), 377 | ), 378 | SizedBox(height: 30), 379 | MultiSegmentLinearIndicator( 380 | width: MediaQuery.of(context).size.width - 40, 381 | lineHeight: 20.0, 382 | firstSegmentPercent: 0.3, 383 | secondSegmentPercent: 0.4, 384 | thirdSegmentPercent: 0.3, 385 | firstSegmentColor: Color(0xFFBA0521), 386 | secondSegmentColor: Color(0xFF071437), 387 | thirdSegmentColor: Color(0xFFFF9205), 388 | enableStripes: [2], 389 | barRadius: Radius.circular(20), 390 | ), 391 | SizedBox(height: 10), 392 | Text( 393 | 'Static: 30% - 40% - 30%', 394 | style: TextStyle(fontSize: 16), 395 | ), 396 | SizedBox(height: 30), 397 | MultiSegmentLinearIndicator( 398 | width: MediaQuery.of(context).size.width - 40, 399 | lineHeight: 20.0, 400 | firstSegmentPercent: firstSegment, 401 | secondSegmentPercent: secondSegment, 402 | thirdSegmentPercent: thirdSegment, 403 | firstSegmentColor: Colors.green, 404 | secondSegmentColor: Colors.blue, 405 | thirdSegmentColor: Colors.orange, 406 | enableStripes: [1, 3], 407 | animation: true, 408 | animateFromLastPercent: true, 409 | animationDuration: 1000, 410 | curve: Curves.easeInOut, 411 | barRadius: Radius.circular(10), 412 | ), 413 | SizedBox(height: 10), 414 | Text( 415 | 'Progress: ${(firstSegment * 100).toInt()}% - ${(secondSegment * 100).toInt()}% - ${(thirdSegment * 100).toInt()}%', 416 | style: TextStyle(fontSize: 16), 417 | ), 418 | SizedBox(height: 30), 419 | MultiSegmentLinearIndicator( 420 | width: MediaQuery.of(context).size.width - 64, 421 | lineHeight: 30.0, 422 | firstSegmentPercent: 0.15, 423 | secondSegmentPercent: 0.4, 424 | thirdSegmentPercent: 0.45, 425 | firstSegmentColor: Color(0xFFBA0521), 426 | secondSegmentColor: Color(0xFFAEFAB00), 427 | thirdSegmentColor: Color(0xFFEFEFEF), 428 | enableStripes: [1], 429 | animation: true, 430 | animationDuration: 1000, 431 | curve: Curves.decelerate, 432 | animateFromLastPercent: true, 433 | ), 434 | SizedBox(height: 10), 435 | Text( 436 | 'Static with decelerate: 25% - 40% - 35%', 437 | style: TextStyle(fontSize: 16), 438 | ), 439 | SizedBox(height: 30), 440 | ], 441 | ), 442 | ), 443 | ), 444 | ); 445 | } 446 | 447 | ``` 448 | 449 |

450 | 451 |

452 | 453 | You can follow me on twitter [@diegoveloper](https://www.twitter.com/diegoveloper) 454 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # https://www.dartlang.org/guides/language/analysis-options 2 | analyzer: 3 | 4 | language: 5 | 6 | 7 | # Source of linter options: 8 | # http://dart-lang.github.io/linter/lints/options/options.html 9 | linter: 10 | rules: 11 | - camel_case_types 12 | - hash_and_equals 13 | - unrelated_type_equality_checks 14 | - valid_regexps 15 | 16 | -------------------------------------------------------------------------------- /art/screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/art/screen1.png -------------------------------------------------------------------------------- /art/screen2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/art/screen2.png -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .flutter-plugins 10 | 11 | .idea/ 12 | -------------------------------------------------------------------------------- /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: c7ea3ca377e909469c68f2ab878a5bc53d3cf66b 8 | channel: beta 9 | -------------------------------------------------------------------------------- /example/.pubignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .flutter-plugins 10 | 11 | .idea/ 12 | 13 | .idea/**/*.xml 14 | .idea/sonarlint 15 | !.idea/libraries/Dart_SDK.xml -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | GeneratedPluginRegistrant.java 11 | -------------------------------------------------------------------------------- /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 | apply plugin: 'com.android.application' 15 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 16 | 17 | android { 18 | compileSdkVersion 31 19 | 20 | lintOptions { 21 | disable 'InvalidPackage' 22 | } 23 | 24 | defaultConfig { 25 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 26 | applicationId "com.example.percentindicatorexample" 27 | minSdkVersion 16 28 | targetSdkVersion 31 29 | versionCode 1 30 | versionName "1.0" 31 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 32 | } 33 | 34 | buildTypes { 35 | release { 36 | // TODO: Add your own signing config for the release build. 37 | // Signing with the debug keys for now, so `flutter run --release` works. 38 | signingConfig signingConfigs.debug 39 | } 40 | } 41 | } 42 | 43 | flutter { 44 | source '../..' 45 | } 46 | 47 | dependencies { 48 | testImplementation 'junit:junit:4.13.1' 49 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 50 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 51 | } 52 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 15 | 18 | 26 | 30 | 33 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/percentindicatorexample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.percentindicatorexample; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends FlutterActivity { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:7.0.4' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | mavenCentral() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | android.enableJetifier=true 2 | android.useAndroidX=true 3 | org.gradle.jvmargs=-Xmx1536M 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Oct 04 01:29:47 IST 2019 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.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/app.flx 37 | /Flutter/app.zip 38 | /Flutter/flutter_assets/ 39 | /Flutter/App.framework 40 | /Flutter/Flutter.framework 41 | /Flutter/Generated.xcconfig 42 | /ServiceDefinitions.json 43 | 44 | Pods/ 45 | .symlinks/ 46 | -------------------------------------------------------------------------------- /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 | 12.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.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 13 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; 14 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 15 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 16 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 17 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 18 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXCopyFilesBuildPhase section */ 22 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 23 | isa = PBXCopyFilesBuildPhase; 24 | buildActionMask = 2147483647; 25 | dstPath = ""; 26 | dstSubfolderSpec = 10; 27 | files = ( 28 | ); 29 | name = "Embed Frameworks"; 30 | runOnlyForDeploymentPostprocessing = 0; 31 | }; 32 | /* End PBXCopyFilesBuildPhase section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 36 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 37 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 38 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 39 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 40 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 41 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 42 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 43 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 45 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 46 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 47 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 48 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | /* End PBXFileReference section */ 50 | 51 | /* Begin PBXFrameworksBuildPhase section */ 52 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | /* End PBXFrameworksBuildPhase section */ 60 | 61 | /* Begin PBXGroup section */ 62 | 9740EEB11CF90186004384FC /* Flutter */ = { 63 | isa = PBXGroup; 64 | children = ( 65 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 66 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 67 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 68 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 69 | ); 70 | name = Flutter; 71 | sourceTree = ""; 72 | }; 73 | 97C146E51CF9000F007C117D = { 74 | isa = PBXGroup; 75 | children = ( 76 | 9740EEB11CF90186004384FC /* Flutter */, 77 | 97C146F01CF9000F007C117D /* Runner */, 78 | 97C146EF1CF9000F007C117D /* Products */, 79 | CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, 80 | ); 81 | sourceTree = ""; 82 | }; 83 | 97C146EF1CF9000F007C117D /* Products */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 97C146EE1CF9000F007C117D /* Runner.app */, 87 | ); 88 | name = Products; 89 | sourceTree = ""; 90 | }; 91 | 97C146F01CF9000F007C117D /* Runner */ = { 92 | isa = PBXGroup; 93 | children = ( 94 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 95 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 96 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 97 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 98 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 99 | 97C147021CF9000F007C117D /* Info.plist */, 100 | 97C146F11CF9000F007C117D /* Supporting Files */, 101 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 102 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 103 | ); 104 | path = Runner; 105 | sourceTree = ""; 106 | }; 107 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 97C146F21CF9000F007C117D /* main.m */, 111 | ); 112 | name = "Supporting Files"; 113 | sourceTree = ""; 114 | }; 115 | /* End PBXGroup section */ 116 | 117 | /* Begin PBXNativeTarget section */ 118 | 97C146ED1CF9000F007C117D /* Runner */ = { 119 | isa = PBXNativeTarget; 120 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 121 | buildPhases = ( 122 | 9740EEB61CF901F6004384FC /* Run Script */, 123 | 97C146EA1CF9000F007C117D /* Sources */, 124 | 97C146EB1CF9000F007C117D /* Frameworks */, 125 | 97C146EC1CF9000F007C117D /* Resources */, 126 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 127 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 128 | ); 129 | buildRules = ( 130 | ); 131 | dependencies = ( 132 | ); 133 | name = Runner; 134 | productName = Runner; 135 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 136 | productType = "com.apple.product-type.application"; 137 | }; 138 | /* End PBXNativeTarget section */ 139 | 140 | /* Begin PBXProject section */ 141 | 97C146E61CF9000F007C117D /* Project object */ = { 142 | isa = PBXProject; 143 | attributes = { 144 | LastUpgradeCheck = 1510; 145 | ORGANIZATIONNAME = "The Chromium Authors"; 146 | TargetAttributes = { 147 | 97C146ED1CF9000F007C117D = { 148 | CreatedOnToolsVersion = 7.3.1; 149 | }; 150 | }; 151 | }; 152 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 153 | compatibilityVersion = "Xcode 3.2"; 154 | developmentRegion = English; 155 | hasScannedForEncodings = 0; 156 | knownRegions = ( 157 | en, 158 | Base, 159 | ); 160 | mainGroup = 97C146E51CF9000F007C117D; 161 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 162 | projectDirPath = ""; 163 | projectRoot = ""; 164 | targets = ( 165 | 97C146ED1CF9000F007C117D /* Runner */, 166 | ); 167 | }; 168 | /* End PBXProject section */ 169 | 170 | /* Begin PBXResourcesBuildPhase section */ 171 | 97C146EC1CF9000F007C117D /* Resources */ = { 172 | isa = PBXResourcesBuildPhase; 173 | buildActionMask = 2147483647; 174 | files = ( 175 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 176 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 177 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 178 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 179 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 180 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 181 | ); 182 | runOnlyForDeploymentPostprocessing = 0; 183 | }; 184 | /* End PBXResourcesBuildPhase section */ 185 | 186 | /* Begin PBXShellScriptBuildPhase section */ 187 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 188 | isa = PBXShellScriptBuildPhase; 189 | alwaysOutOfDate = 1; 190 | buildActionMask = 2147483647; 191 | files = ( 192 | ); 193 | inputPaths = ( 194 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 195 | ); 196 | name = "Thin Binary"; 197 | outputPaths = ( 198 | ); 199 | runOnlyForDeploymentPostprocessing = 0; 200 | shellPath = /bin/sh; 201 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 202 | }; 203 | 9740EEB61CF901F6004384FC /* Run Script */ = { 204 | isa = PBXShellScriptBuildPhase; 205 | alwaysOutOfDate = 1; 206 | buildActionMask = 2147483647; 207 | files = ( 208 | ); 209 | inputPaths = ( 210 | ); 211 | name = "Run Script"; 212 | outputPaths = ( 213 | ); 214 | runOnlyForDeploymentPostprocessing = 0; 215 | shellPath = /bin/sh; 216 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 217 | }; 218 | /* End PBXShellScriptBuildPhase section */ 219 | 220 | /* Begin PBXSourcesBuildPhase section */ 221 | 97C146EA1CF9000F007C117D /* Sources */ = { 222 | isa = PBXSourcesBuildPhase; 223 | buildActionMask = 2147483647; 224 | files = ( 225 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 226 | 97C146F31CF9000F007C117D /* main.m in Sources */, 227 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 228 | ); 229 | runOnlyForDeploymentPostprocessing = 0; 230 | }; 231 | /* End PBXSourcesBuildPhase section */ 232 | 233 | /* Begin PBXVariantGroup section */ 234 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 235 | isa = PBXVariantGroup; 236 | children = ( 237 | 97C146FB1CF9000F007C117D /* Base */, 238 | ); 239 | name = Main.storyboard; 240 | sourceTree = ""; 241 | }; 242 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 243 | isa = PBXVariantGroup; 244 | children = ( 245 | 97C147001CF9000F007C117D /* Base */, 246 | ); 247 | name = LaunchScreen.storyboard; 248 | sourceTree = ""; 249 | }; 250 | /* End PBXVariantGroup section */ 251 | 252 | /* Begin XCBuildConfiguration section */ 253 | 97C147031CF9000F007C117D /* Debug */ = { 254 | isa = XCBuildConfiguration; 255 | buildSettings = { 256 | ALWAYS_SEARCH_USER_PATHS = NO; 257 | CLANG_ANALYZER_NONNULL = YES; 258 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 259 | CLANG_CXX_LIBRARY = "libc++"; 260 | CLANG_ENABLE_MODULES = YES; 261 | CLANG_ENABLE_OBJC_ARC = YES; 262 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 263 | CLANG_WARN_BOOL_CONVERSION = YES; 264 | CLANG_WARN_COMMA = YES; 265 | CLANG_WARN_CONSTANT_CONVERSION = YES; 266 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 267 | CLANG_WARN_EMPTY_BODY = YES; 268 | CLANG_WARN_ENUM_CONVERSION = YES; 269 | CLANG_WARN_INFINITE_RECURSION = YES; 270 | CLANG_WARN_INT_CONVERSION = YES; 271 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 272 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 273 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 274 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 275 | CLANG_WARN_STRICT_PROTOTYPES = YES; 276 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 277 | CLANG_WARN_UNREACHABLE_CODE = YES; 278 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 279 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 280 | COPY_PHASE_STRIP = NO; 281 | DEBUG_INFORMATION_FORMAT = dwarf; 282 | ENABLE_STRICT_OBJC_MSGSEND = YES; 283 | ENABLE_TESTABILITY = YES; 284 | GCC_C_LANGUAGE_STANDARD = gnu99; 285 | GCC_DYNAMIC_NO_PIC = NO; 286 | GCC_NO_COMMON_BLOCKS = YES; 287 | GCC_OPTIMIZATION_LEVEL = 0; 288 | GCC_PREPROCESSOR_DEFINITIONS = ( 289 | "DEBUG=1", 290 | "$(inherited)", 291 | ); 292 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 293 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 294 | GCC_WARN_UNDECLARED_SELECTOR = YES; 295 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 296 | GCC_WARN_UNUSED_FUNCTION = YES; 297 | GCC_WARN_UNUSED_VARIABLE = YES; 298 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 299 | MTL_ENABLE_DEBUG_INFO = YES; 300 | ONLY_ACTIVE_ARCH = YES; 301 | SDKROOT = iphoneos; 302 | TARGETED_DEVICE_FAMILY = "1,2"; 303 | }; 304 | name = Debug; 305 | }; 306 | 97C147041CF9000F007C117D /* Release */ = { 307 | isa = XCBuildConfiguration; 308 | buildSettings = { 309 | ALWAYS_SEARCH_USER_PATHS = NO; 310 | CLANG_ANALYZER_NONNULL = YES; 311 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 312 | CLANG_CXX_LIBRARY = "libc++"; 313 | CLANG_ENABLE_MODULES = YES; 314 | CLANG_ENABLE_OBJC_ARC = YES; 315 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 316 | CLANG_WARN_BOOL_CONVERSION = YES; 317 | CLANG_WARN_COMMA = YES; 318 | CLANG_WARN_CONSTANT_CONVERSION = YES; 319 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 320 | CLANG_WARN_EMPTY_BODY = YES; 321 | CLANG_WARN_ENUM_CONVERSION = YES; 322 | CLANG_WARN_INFINITE_RECURSION = YES; 323 | CLANG_WARN_INT_CONVERSION = YES; 324 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 325 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 326 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 327 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 328 | CLANG_WARN_STRICT_PROTOTYPES = YES; 329 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 330 | CLANG_WARN_UNREACHABLE_CODE = YES; 331 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 332 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 333 | COPY_PHASE_STRIP = NO; 334 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 335 | ENABLE_NS_ASSERTIONS = NO; 336 | ENABLE_STRICT_OBJC_MSGSEND = YES; 337 | GCC_C_LANGUAGE_STANDARD = gnu99; 338 | GCC_NO_COMMON_BLOCKS = YES; 339 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 340 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 341 | GCC_WARN_UNDECLARED_SELECTOR = YES; 342 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 343 | GCC_WARN_UNUSED_FUNCTION = YES; 344 | GCC_WARN_UNUSED_VARIABLE = YES; 345 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 346 | MTL_ENABLE_DEBUG_INFO = NO; 347 | SDKROOT = iphoneos; 348 | TARGETED_DEVICE_FAMILY = "1,2"; 349 | VALIDATE_PRODUCT = YES; 350 | }; 351 | name = Release; 352 | }; 353 | 97C147061CF9000F007C117D /* Debug */ = { 354 | isa = XCBuildConfiguration; 355 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 356 | buildSettings = { 357 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 358 | CURRENT_PROJECT_VERSION = 1; 359 | ENABLE_BITCODE = NO; 360 | FRAMEWORK_SEARCH_PATHS = ( 361 | "$(inherited)", 362 | "$(PROJECT_DIR)/Flutter", 363 | ); 364 | INFOPLIST_FILE = Runner/Info.plist; 365 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 366 | LIBRARY_SEARCH_PATHS = ( 367 | "$(inherited)", 368 | "$(PROJECT_DIR)/Flutter", 369 | ); 370 | PRODUCT_BUNDLE_IDENTIFIER = com.example.percentIndicatorExample; 371 | PRODUCT_NAME = "$(TARGET_NAME)"; 372 | VERSIONING_SYSTEM = "apple-generic"; 373 | }; 374 | name = Debug; 375 | }; 376 | 97C147071CF9000F007C117D /* Release */ = { 377 | isa = XCBuildConfiguration; 378 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 379 | buildSettings = { 380 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 381 | CURRENT_PROJECT_VERSION = 1; 382 | ENABLE_BITCODE = NO; 383 | FRAMEWORK_SEARCH_PATHS = ( 384 | "$(inherited)", 385 | "$(PROJECT_DIR)/Flutter", 386 | ); 387 | INFOPLIST_FILE = Runner/Info.plist; 388 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 389 | LIBRARY_SEARCH_PATHS = ( 390 | "$(inherited)", 391 | "$(PROJECT_DIR)/Flutter", 392 | ); 393 | PRODUCT_BUNDLE_IDENTIFIER = com.example.percentIndicatorExample; 394 | PRODUCT_NAME = "$(TARGET_NAME)"; 395 | VERSIONING_SYSTEM = "apple-generic"; 396 | }; 397 | name = Release; 398 | }; 399 | /* End XCBuildConfiguration section */ 400 | 401 | /* Begin XCConfigurationList section */ 402 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 403 | isa = XCConfigurationList; 404 | buildConfigurations = ( 405 | 97C147031CF9000F007C117D /* Debug */, 406 | 97C147041CF9000F007C117D /* Release */, 407 | ); 408 | defaultConfigurationIsVisible = 0; 409 | defaultConfigurationName = Release; 410 | }; 411 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 412 | isa = XCConfigurationList; 413 | buildConfigurations = ( 414 | 97C147061CF9000F007C117D /* Debug */, 415 | 97C147071CF9000F007C117D /* Release */, 416 | ); 417 | defaultConfigurationIsVisible = 0; 418 | defaultConfigurationName = Release; 419 | }; 420 | /* End XCConfigurationList section */ 421 | }; 422 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 423 | } 424 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/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 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | percent_indicator_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 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 | CADisableMinimumFrameDurationOnPhone 45 | 46 | UIApplicationSupportsIndirectInputEvents 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:percent_indicator_example/sample_circular_page.dart'; 3 | import 'package:percent_indicator_example/sample_linear_page.dart'; 4 | import 'package:percent_indicator_example/multi_segment_page.dart'; 5 | 6 | void main() { 7 | runApp(MaterialApp(home: Scaffold(body: SamplePage()))); 8 | } 9 | 10 | class SamplePage extends StatefulWidget { 11 | @override 12 | _SamplePageState createState() => _SamplePageState(); 13 | } 14 | 15 | class _SamplePageState extends State { 16 | void _openPage(Widget page) { 17 | Navigator.push( 18 | context, 19 | MaterialPageRoute( 20 | builder: (BuildContext context) => page, 21 | ), 22 | ); 23 | } 24 | 25 | @override 26 | Widget build(BuildContext context) { 27 | return Container( 28 | child: Center( 29 | child: Column( 30 | crossAxisAlignment: CrossAxisAlignment.center, 31 | mainAxisAlignment: MainAxisAlignment.center, 32 | children: [ 33 | MaterialButton( 34 | color: Colors.blueAccent, 35 | child: Text("Circular Library"), 36 | onPressed: () => _openPage(SampleCircularPage()), 37 | ), 38 | Padding( 39 | padding: EdgeInsets.all(20.0), 40 | ), 41 | MaterialButton( 42 | color: Colors.blueAccent, 43 | child: Text("Linear Library"), 44 | onPressed: () => _openPage(SampleLinearPage()), 45 | ), 46 | Padding( 47 | padding: EdgeInsets.all(20.0), 48 | ), 49 | MaterialButton( 50 | color: Colors.blueAccent, 51 | child: Text("Multi Segment Linear Library"), 52 | onPressed: () => _openPage(MultiSegmentPage()), 53 | ), 54 | ], 55 | ), 56 | ), 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /example/lib/multi_segment_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:percent_indicator/flutter_percent_indicator.dart'; 3 | 4 | class MultiSegmentPage extends StatefulWidget { 5 | @override 6 | _MultiSegmentPageState createState() => _MultiSegmentPageState(); 7 | } 8 | 9 | class _MultiSegmentPageState extends State { 10 | double firstSegment = 0.3; 11 | double secondSegment = 0.4; 12 | double thirdSegment = 0.3; 13 | 14 | @override 15 | void initState() { 16 | super.initState(); 17 | 18 | Future.delayed(Duration(seconds: 2), () { 19 | setState(() { 20 | secondSegment = 0.5; 21 | thirdSegment = 0.2; 22 | }); 23 | }); 24 | } 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return Scaffold( 29 | appBar: AppBar( 30 | title: Text('Multi Segment Progress'), 31 | ), 32 | body: Center( 33 | child: Padding( 34 | padding: const EdgeInsets.all(20.0), 35 | child: Column( 36 | mainAxisAlignment: MainAxisAlignment.center, 37 | children: [ 38 | MultiSegmentLinearIndicator( 39 | width: MediaQuery.of(context).size.width - 64, 40 | lineHeight: 30.0, 41 | segments: [ 42 | SegmentLinearIndicator( 43 | percent: 0.25, 44 | color: Color(0xFF4285F4), 45 | enableStripes: true, 46 | ), 47 | SegmentLinearIndicator( 48 | percent: 0.4, 49 | color: Color(0xFF6DD5F6), 50 | ), 51 | SegmentLinearIndicator( 52 | percent: 0.35, 53 | color: Color(0xFFEFEFEF), 54 | ), 55 | ], 56 | barRadius: Radius.circular(10.0), 57 | animation: true, 58 | animationDuration: 1000, 59 | curve: Curves.easeInOut, 60 | animateFromLastPercent: true, 61 | onAnimationEnd: () { 62 | ScaffoldMessenger.of(context).showSnackBar( 63 | const SnackBar( 64 | content: Text('Animation completed!'), 65 | duration: Duration(seconds: 1), 66 | ), 67 | ); 68 | }, 69 | ), 70 | SizedBox(height: 10), 71 | Text( 72 | 'Static with easeInOut: 25% - 40% - 35%', 73 | style: TextStyle(fontSize: 16), 74 | ), 75 | SizedBox(height: 30), 76 | MultiSegmentLinearIndicator( 77 | width: MediaQuery.of(context).size.width - 40, 78 | lineHeight: 20.0, 79 | segments: [ 80 | SegmentLinearIndicator( 81 | percent: 0.3, 82 | color: Color(0xFFBA0521), 83 | ), 84 | SegmentLinearIndicator( 85 | percent: 0.4, 86 | color: Color(0xFF071437), 87 | enableStripes: true, 88 | ), 89 | SegmentLinearIndicator( 90 | percent: 0.3, 91 | color: Color(0xFFFF9205), 92 | ), 93 | ], 94 | barRadius: Radius.circular(20), 95 | ), 96 | SizedBox(height: 10), 97 | Text( 98 | 'Static: 30% - 40% - 30%', 99 | style: TextStyle(fontSize: 16), 100 | ), 101 | SizedBox(height: 30), 102 | MultiSegmentLinearIndicator( 103 | width: MediaQuery.of(context).size.width - 40, 104 | lineHeight: 20.0, 105 | segments: [ 106 | SegmentLinearIndicator( 107 | percent: firstSegment, 108 | color: Colors.green, 109 | enableStripes: true, 110 | ), 111 | SegmentLinearIndicator( 112 | percent: secondSegment, 113 | color: Colors.blue, 114 | enableStripes: true, 115 | ), 116 | SegmentLinearIndicator( 117 | percent: thirdSegment, 118 | color: Colors.orange, 119 | ), 120 | ], 121 | animation: true, 122 | animateFromLastPercent: true, 123 | animationDuration: 1000, 124 | curve: Curves.easeInOut, 125 | barRadius: Radius.circular(10), 126 | ), 127 | SizedBox(height: 10), 128 | Text( 129 | 'Progress: ${(firstSegment * 100).toInt()}% - ${(secondSegment * 100).toInt()}% - ${(thirdSegment * 100).toInt()}%', 130 | style: TextStyle(fontSize: 16), 131 | ), 132 | SizedBox(height: 30), 133 | MultiSegmentLinearIndicator( 134 | width: MediaQuery.of(context).size.width - 64, 135 | lineHeight: 30.0, 136 | segments: [ 137 | SegmentLinearIndicator( 138 | percent: 0.15, 139 | color: Color(0xFFBA0521), 140 | enableStripes: true, 141 | ), 142 | SegmentLinearIndicator( 143 | percent: 0.4, color: Color(0xFFAEFAB00)), 144 | SegmentLinearIndicator( 145 | percent: 0.45, color: Color(0xFFEFEFEF)), 146 | ], 147 | animation: true, 148 | animationDuration: 1000, 149 | curve: Curves.decelerate, 150 | animateFromLastPercent: true, 151 | ), 152 | SizedBox(height: 10), 153 | Text( 154 | 'Static with decelerate: 25% - 40% - 35%', 155 | style: TextStyle(fontSize: 16), 156 | ), 157 | SizedBox(height: 30), 158 | ], 159 | ), 160 | ), 161 | ), 162 | ); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /example/lib/sample_circular_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:percent_indicator/percent_indicator.dart'; 3 | 4 | class SampleCircularPage extends StatefulWidget { 5 | @override 6 | _SampleCircularPageState createState() => _SampleCircularPageState(); 7 | } 8 | 9 | class _SampleCircularPageState extends State { 10 | String state = 'Animation start'; 11 | @override 12 | Widget build(BuildContext context) { 13 | return Scaffold( 14 | appBar: AppBar( 15 | title: Text("Circular Percent Indicators"), 16 | ), 17 | body: Center( 18 | child: ListView( 19 | children: [ 20 | CircularPercentIndicator( 21 | radius: 60.0, 22 | animation: true, 23 | animationDuration: 1000, 24 | lineWidth: 10.0, 25 | percent: 1.0, 26 | reverse: false, 27 | arcType: ArcType.FULL_REVERSED, 28 | startAngle: 0.0, 29 | animateFromLastPercent: true, 30 | circularStrokeCap: CircularStrokeCap.round, 31 | backgroundColor: Colors.green, 32 | linearGradient: const LinearGradient( 33 | begin: Alignment.centerLeft, 34 | end: Alignment.centerRight, 35 | tileMode: TileMode.clamp, 36 | stops: [0.0, 1.0], 37 | colors: [ 38 | Colors.yellow, 39 | Colors.red, 40 | ], 41 | ), 42 | widgetIndicator: Center( 43 | child: Container( 44 | height: 20, 45 | width: 20, 46 | decoration: BoxDecoration( 47 | color: Colors.yellow, 48 | borderRadius: BorderRadius.circular(50), 49 | ), 50 | padding: const EdgeInsets.all(5), 51 | child: Container( 52 | decoration: BoxDecoration( 53 | color: Colors.yellow, 54 | borderRadius: BorderRadius.circular(50), 55 | ), 56 | ), 57 | ), 58 | ), 59 | arcBackgroundColor: Colors.grey, 60 | ), 61 | CircularPercentIndicator( 62 | radius: 60.0, 63 | animation: true, 64 | animationDuration: 1000, 65 | lineWidth: 10.0, 66 | percent: 0.9, 67 | reverse: false, 68 | arcType: ArcType.FULL, 69 | startAngle: 0.0, 70 | animateFromLastPercent: true, 71 | circularStrokeCap: CircularStrokeCap.round, 72 | backgroundColor: Colors.green, 73 | linearGradient: const LinearGradient( 74 | begin: Alignment.centerLeft, 75 | end: Alignment.centerRight, 76 | tileMode: TileMode.clamp, 77 | stops: [0.0, 1.0], 78 | colors: [ 79 | Colors.yellow, 80 | Colors.red, 81 | ], 82 | ), 83 | widgetIndicator: Center( 84 | child: Container( 85 | height: 30, 86 | width: 30, 87 | decoration: BoxDecoration( 88 | color: Colors.yellow, 89 | borderRadius: BorderRadius.circular(50), 90 | ), 91 | padding: const EdgeInsets.all(5), 92 | child: Container( 93 | decoration: BoxDecoration( 94 | color: Colors.yellow, 95 | borderRadius: BorderRadius.circular(50), 96 | ), 97 | ), 98 | ), 99 | ), 100 | arcBackgroundColor: Colors.grey, 101 | ), 102 | CircularPercentIndicator( 103 | radius: 60.0, 104 | lineWidth: 13.0, 105 | animation: true, 106 | animationDuration: 3000, 107 | percent: 0.7, 108 | animateFromLastPercent: true, 109 | center: Text( 110 | "70.0%", 111 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0), 112 | ), 113 | footer: Text( 114 | "Sales this week", 115 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 17.0), 116 | ), 117 | circularStrokeCap: CircularStrokeCap.round, 118 | progressColor: Colors.purple, 119 | widgetIndicator: RotatedBox( 120 | quarterTurns: 1, 121 | child: Icon(Icons.airplanemode_active, size: 30), 122 | ), 123 | ), 124 | CircularPercentIndicator( 125 | radius: 50.0, 126 | lineWidth: 10.0, 127 | percent: 0.5, 128 | center: Text("50%"), 129 | circularStrokeCap: CircularStrokeCap.round, 130 | backgroundColor: Colors.grey, 131 | maskFilter: MaskFilter.blur(BlurStyle.solid, 3), 132 | linearGradient: LinearGradient( 133 | begin: Alignment.topCenter, 134 | end: Alignment.bottomCenter, 135 | colors: [Colors.orange, Colors.yellow], 136 | ), 137 | ), 138 | CircularPercentIndicator( 139 | radius: 50.0, 140 | lineWidth: 10.0, 141 | percent: 0.8, 142 | header: Text("Icon header"), 143 | center: Icon( 144 | Icons.person_pin, 145 | size: 50.0, 146 | color: Colors.blue, 147 | ), 148 | reverse: true, 149 | backgroundColor: Colors.grey, 150 | progressColor: Colors.blue, 151 | ), 152 | Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ 153 | CircularPercentIndicator( 154 | radius: 50.0, 155 | animation: true, 156 | animationDuration: 2000, 157 | lineWidth: 10.0, 158 | percent: 0.5, 159 | arcBackgroundColor: Colors.orangeAccent, 160 | arcType: ArcType.HALF, 161 | center: Text( 162 | "40 hours", 163 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.0), 164 | ), 165 | circularStrokeCap: CircularStrokeCap.butt, 166 | backgroundColor: Colors.transparent, 167 | progressColor: Colors.red, 168 | ), 169 | CircularPercentIndicator( 170 | radius: 60.0, 171 | animation: true, 172 | animationDuration: 2000, 173 | lineWidth: 10.0, 174 | percent: 0.5, 175 | reverse: true, 176 | arcBackgroundColor: Colors.teal, 177 | arcType: ArcType.FULL, 178 | center: Text( 179 | "20 hours", 180 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.0), 181 | ), 182 | circularStrokeCap: CircularStrokeCap.butt, 183 | backgroundColor: Colors.yellow, 184 | progressColor: Colors.red, 185 | ), 186 | ]), 187 | CircularPercentIndicator( 188 | radius: 50.0, 189 | animation: true, 190 | animationDuration: 2000, 191 | lineWidth: 10.0, 192 | percent: 0.5, 193 | startAngle: 90, 194 | center: Text( 195 | "Start angle 250", 196 | textAlign: TextAlign.center, 197 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14.0), 198 | ), 199 | circularStrokeCap: CircularStrokeCap.butt, 200 | backgroundColor: Colors.grey, 201 | progressColor: Colors.red, 202 | ), 203 | CircularPercentIndicator( 204 | radius: 60.0, 205 | lineWidth: 13.0, 206 | animation: true, 207 | animationDuration: 3000, 208 | percent: 0.7, 209 | animateFromLastPercent: true, 210 | center: Text( 211 | "70.0%", 212 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0), 213 | ), 214 | footer: Text( 215 | "Sales this week", 216 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 17.0), 217 | ), 218 | circularStrokeCap: CircularStrokeCap.round, 219 | progressColor: Colors.purple, 220 | ), 221 | Padding( 222 | padding: EdgeInsets.all(15.0), 223 | child: CircularPercentIndicator( 224 | radius: 30.0, 225 | lineWidth: 5.0, 226 | percent: 1.0, 227 | center: Text("100%"), 228 | progressColor: Colors.green, 229 | ), 230 | ), 231 | Container( 232 | padding: EdgeInsets.all(15.0), 233 | child: SingleChildScrollView( 234 | scrollDirection: Axis.horizontal, 235 | child: Row( 236 | mainAxisAlignment: MainAxisAlignment.center, 237 | children: [ 238 | CircularPercentIndicator( 239 | radius: 22.0, 240 | lineWidth: 4.0, 241 | percent: 0.10, 242 | center: Text("10%"), 243 | progressColor: Colors.red, 244 | ), 245 | Padding( 246 | padding: EdgeInsets.symmetric(horizontal: 10.0), 247 | ), 248 | CircularPercentIndicator( 249 | radius: 22.0, 250 | lineWidth: 4.0, 251 | backgroundWidth: 1.0, 252 | percent: 0.2, 253 | animation: true, 254 | center: Text("20%"), 255 | progressColor: Colors.orangeAccent, 256 | ), 257 | Padding( 258 | padding: EdgeInsets.symmetric(horizontal: 10.0), 259 | ), 260 | CircularPercentIndicator( 261 | radius: 22.0, 262 | lineWidth: 4.0, 263 | percent: 0.30, 264 | center: Text("30%"), 265 | progressColor: Colors.orange, 266 | ), 267 | Padding( 268 | padding: EdgeInsets.symmetric(horizontal: 10.0), 269 | ), 270 | CircularPercentIndicator( 271 | radius: 22.0, 272 | lineWidth: 4.0, 273 | backgroundWidth: 8, 274 | animation: true, 275 | animationDuration: 200, 276 | percent: 0.60, 277 | center: Text("60%"), 278 | progressColor: Colors.yellow, 279 | ), 280 | Padding( 281 | padding: EdgeInsets.symmetric(horizontal: 10.0), 282 | ), 283 | CircularPercentIndicator( 284 | radius: 22.0, 285 | lineWidth: 4.0, 286 | percent: 0.90, 287 | center: Text("90%"), 288 | progressColor: Colors.green, 289 | ), 290 | Padding( 291 | padding: EdgeInsets.symmetric(horizontal: 10.0), 292 | ), 293 | CircularPercentIndicator( 294 | radius: 22.0, 295 | lineWidth: 4.0, 296 | percent: 1.0, 297 | animation: true, 298 | restartAnimation: true, 299 | center: CircleAvatar( 300 | child: Icon(Icons.person), 301 | ), 302 | progressColor: Colors.redAccent, 303 | ), 304 | ], 305 | ), 306 | ), 307 | ), 308 | CircularPercentIndicator( 309 | radius: 40.0, 310 | lineWidth: 5.0, 311 | animation: true, 312 | percent: .5, 313 | animationDuration: 2500, 314 | animateFromLastPercent: true, 315 | center: Text( 316 | "50.0%", 317 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0), 318 | ), 319 | footer: Text( 320 | state, 321 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 17.0), 322 | ), 323 | circularStrokeCap: CircularStrokeCap.round, 324 | progressColor: Colors.blueAccent, 325 | onAnimationEnd: () => 326 | setState(() => state = 'End Animation at 50%'), 327 | ), 328 | const SizedBox(height: 20), 329 | CircularPercentIndicator( 330 | radius: 40.0, 331 | backgroundColor: Colors.white, 332 | percent: .7, 333 | lineWidth: 10, 334 | backgroundWidth: 15, 335 | fillColor: Colors.transparent, 336 | circularStrokeCap: CircularStrokeCap.round, 337 | arcBackgroundColor: Colors.transparent, 338 | arcType: ArcType.HALF, 339 | ), 340 | const SizedBox(height: 20), 341 | Padding( 342 | padding: const EdgeInsets.only(bottom: 18.0), 343 | child: Center( 344 | child: Text( 345 | "With different border color", 346 | style: TextStyle(fontWeight: FontWeight.bold, fontSize: 17.0), 347 | ), 348 | ), 349 | ), 350 | Row( 351 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 352 | children: [ 353 | CircularPercentIndicator( 354 | radius: 40.0, 355 | progressColor: Colors.yellow, 356 | progressBorderColor: Colors.green, 357 | percent: .2, 358 | lineWidth: 15, 359 | center: Text( 360 | "20.0%", 361 | style: 362 | TextStyle(fontWeight: FontWeight.bold, fontSize: 12.0), 363 | ), 364 | backgroundWidth: 15, 365 | fillColor: Colors.transparent, 366 | circularStrokeCap: CircularStrokeCap.round, 367 | arcBackgroundColor: Colors.yellow[200], 368 | arcType: ArcType.FULL, 369 | ), 370 | CircularPercentIndicator( 371 | radius: 40.0, 372 | progressColor: Colors.white, 373 | progressBorderColor: Colors.blue, 374 | percent: .9, 375 | lineWidth: 10, 376 | center: Text( 377 | "90.0%", 378 | style: 379 | TextStyle(fontWeight: FontWeight.bold, fontSize: 12.0), 380 | ), 381 | backgroundWidth: 15, 382 | fillColor: Colors.transparent, 383 | circularStrokeCap: CircularStrokeCap.round, 384 | arcBackgroundColor: Colors.transparent, 385 | arcType: ArcType.FULL, 386 | ), 387 | CircularPercentIndicator( 388 | radius: 40.0, 389 | progressColor: Colors.red, 390 | progressBorderColor: Colors.black, 391 | percent: .4, 392 | lineWidth: 10, 393 | backgroundWidth: 15, 394 | fillColor: Colors.transparent, 395 | circularStrokeCap: CircularStrokeCap.round, 396 | arcBackgroundColor: Colors.grey, 397 | arcType: ArcType.FULL, 398 | center: Text( 399 | "40.0%", 400 | style: 401 | TextStyle(fontWeight: FontWeight.bold, fontSize: 12.0), 402 | ), 403 | ), 404 | ], 405 | ), 406 | ], 407 | ), 408 | ), 409 | ); 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /example/lib/sample_linear_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:percent_indicator/percent_indicator.dart'; 3 | 4 | class SampleLinearPage extends StatefulWidget { 5 | @override 6 | _SampleLinearPageState createState() => _SampleLinearPageState(); 7 | } 8 | 9 | class _SampleLinearPageState extends State { 10 | String state = 'Animation start'; 11 | bool isRunning = true; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Scaffold( 16 | appBar: AppBar( 17 | title: Text("Linear Percent Indicators"), 18 | actions: [ 19 | IconButton( 20 | icon: Icon(Icons.stop), 21 | onPressed: () { 22 | setState(() { 23 | isRunning = false; 24 | }); 25 | }) 26 | ], 27 | ), 28 | body: Center( 29 | child: SingleChildScrollView( 30 | child: Column( 31 | mainAxisAlignment: MainAxisAlignment.center, 32 | children: [ 33 | Padding( 34 | padding: EdgeInsets.all(15.0), 35 | child: LinearPercentIndicator( 36 | width: MediaQuery.of(context).size.width - 50, 37 | animation: isRunning, 38 | lineHeight: 20.0, 39 | animationDuration: 3000, 40 | percent: 0.5, 41 | animateFromLastPercent: true, 42 | center: Text("50.0%"), 43 | progressColor: Colors.red, 44 | widgetIndicator: RotatedBox( 45 | quarterTurns: 1, 46 | child: Icon(Icons.airplanemode_active, size: 50)), 47 | ), 48 | ), 49 | Padding( 50 | padding: EdgeInsets.all(15.0), 51 | child: LinearPercentIndicator( 52 | width: MediaQuery.of(context).size.width - 50, 53 | lineHeight: 20.0, 54 | animationDuration: 3000, 55 | percent: 0.5, 56 | animateFromLastPercent: true, 57 | center: Text("50.0%"), 58 | linearGradient: LinearGradient( 59 | colors: [Color(0xffB07BE6), Color(0xff5BA2E0)], 60 | ), 61 | linearGradientBackgroundColor: LinearGradient( 62 | colors: [Color(0xffe5d6fa), Color(0xffc8dff8)], 63 | ), 64 | ), 65 | ), 66 | Padding( 67 | padding: EdgeInsets.all(15.0), 68 | child: FittedBox( 69 | child: LinearPercentIndicator( 70 | width: 140.0, 71 | fillColor: Colors.green, 72 | linearGradient: LinearGradient( 73 | colors: [Colors.red, Colors.blue], 74 | ), 75 | lineHeight: 14.0, 76 | percent: 0.7, 77 | center: Text( 78 | "70.0%", 79 | style: TextStyle(fontSize: 12.0), 80 | ), 81 | trailing: Icon(Icons.mood), 82 | barRadius: Radius.circular(7), 83 | backgroundColor: Colors.grey, 84 | ), 85 | ), 86 | ), 87 | Padding( 88 | padding: EdgeInsets.all(15.0), 89 | child: FittedBox( 90 | child: LinearPercentIndicator( 91 | width: 140.0, 92 | fillColor: Colors.green, 93 | lineHeight: 14.0, 94 | percent: 0.5, 95 | center: Text( 96 | "50.0%", 97 | style: TextStyle(fontSize: 12.0), 98 | ), 99 | trailing: Icon(Icons.mood), 100 | barRadius: Radius.circular(7), 101 | backgroundColor: Colors.grey, 102 | progressColor: Colors.blue, 103 | ), 104 | ), 105 | ), 106 | Padding( 107 | padding: EdgeInsets.all(15.0), 108 | child: FittedBox( 109 | child: LinearPercentIndicator( 110 | width: 140.0, 111 | lineHeight: 40.0, 112 | percent: 0.05, 113 | center: Text( 114 | "5.0%", 115 | style: TextStyle(fontSize: 12.0), 116 | ), 117 | barRadius: Radius.circular(20), 118 | backgroundColor: Colors.grey, 119 | progressColor: Colors.red, 120 | ), 121 | ), 122 | ), 123 | Padding( 124 | padding: EdgeInsets.all(15.0), 125 | child: LinearPercentIndicator( 126 | animation: true, 127 | animationDuration: 500, 128 | lineHeight: 20.0, 129 | leading: Expanded( 130 | child: Text("left content"), 131 | ), 132 | trailing: Expanded( 133 | child: Text( 134 | "right content", 135 | textAlign: TextAlign.end, 136 | )), 137 | percent: 0.2, 138 | center: Text("20.0%"), 139 | progressColor: Colors.red, 140 | ), 141 | ), 142 | Padding( 143 | padding: EdgeInsets.all(15.0), 144 | child: LinearPercentIndicator( 145 | width: MediaQuery.of(context).size.width - 50, 146 | animation: true, 147 | lineHeight: 20.0, 148 | animationDuration: 2000, 149 | percent: 0.9, 150 | animateFromLastPercent: true, 151 | center: Text("90.0%"), 152 | isRTL: true, 153 | barRadius: Radius.elliptical(5, 15), 154 | progressColor: Colors.greenAccent, 155 | maskFilter: MaskFilter.blur(BlurStyle.solid, 3), 156 | ), 157 | ), 158 | Padding( 159 | padding: EdgeInsets.all(15.0), 160 | child: LinearPercentIndicator( 161 | width: MediaQuery.of(context).size.width - 50, 162 | animation: true, 163 | lineHeight: 20.0, 164 | animationDuration: 2500, 165 | percent: 0.8, 166 | center: Text("80.0%"), 167 | barRadius: Radius.circular(5), 168 | progressColor: Colors.green, 169 | ), 170 | ), 171 | Padding( 172 | padding: EdgeInsets.all(15.0), 173 | child: LinearPercentIndicator( 174 | animation: true, 175 | lineHeight: 20.0, 176 | animationDuration: 2500, 177 | percent: 0.55, 178 | center: Text("55.0%"), 179 | barRadius: Radius.circular(3), 180 | progressColor: Colors.green, 181 | ), 182 | ), 183 | Padding( 184 | padding: EdgeInsets.all(15.0), 185 | child: Column( 186 | children: [ 187 | LinearPercentIndicator( 188 | width: 100.0, 189 | lineHeight: 8.0, 190 | percent: 0.2, 191 | progressColor: Colors.red, 192 | ), 193 | SizedBox( 194 | height: 10, 195 | ), 196 | LinearPercentIndicator( 197 | width: 100.0, 198 | lineHeight: 8.0, 199 | percent: 0.5, 200 | progressColor: Colors.orange, 201 | ), 202 | SizedBox( 203 | height: 10, 204 | ), 205 | LinearPercentIndicator( 206 | width: 100.0, 207 | lineHeight: 8.0, 208 | percent: 0.9, 209 | progressColor: Colors.blue, 210 | ), 211 | SizedBox( 212 | height: 10, 213 | ), 214 | LinearPercentIndicator( 215 | width: 100.0, 216 | lineHeight: 8.0, 217 | percent: 1.0, 218 | progressColor: Colors.lightBlueAccent, 219 | restartAnimation: true, 220 | animation: true, 221 | ) 222 | ], 223 | ), 224 | ), 225 | Padding( 226 | padding: EdgeInsets.all(15), 227 | child: LinearPercentIndicator( 228 | lineHeight: 20, 229 | center: Text('50%'), 230 | progressColor: Colors.blueAccent, 231 | percent: .5, 232 | animation: true, 233 | animationDuration: 5000, 234 | onAnimationEnd: () => 235 | setState(() => state = 'End Animation at 50%'), 236 | ), 237 | ), 238 | Text(state), 239 | Padding( 240 | padding: EdgeInsets.all(15), 241 | child: LinearPercentIndicator( 242 | lineHeight: 20, 243 | center: Text('50%'), 244 | progressColor: Colors.pinkAccent, 245 | barRadius: Radius.circular(10), 246 | percent: .5, 247 | animation: true, 248 | animationDuration: 1000, 249 | ), 250 | ), 251 | Text('Rounded Edges'), 252 | Padding( 253 | padding: EdgeInsets.all(15), 254 | child: LinearPercentIndicator( 255 | lineHeight: 20, 256 | center: Text('60%'), 257 | progressColor: Colors.purple, 258 | barRadius: Radius.circular(5), 259 | percent: .6, 260 | animation: true, 261 | animationDuration: 1200, 262 | ), 263 | ), 264 | Text('Soft Corner Edges'), 265 | Padding( 266 | padding: EdgeInsets.all(15), 267 | child: LinearPercentIndicator( 268 | lineHeight: 20, 269 | center: Text('70%'), 270 | progressColor: Colors.deepPurple, 271 | barRadius: Radius.elliptical(5, 10), 272 | percent: .7, 273 | animation: true, 274 | animationDuration: 1400, 275 | ), 276 | ), 277 | Text('Custom Edges'), 278 | Padding( 279 | padding: EdgeInsets.all(15), 280 | child: LinearPercentIndicator( 281 | lineHeight: 20, 282 | center: Text('90%'), 283 | progressColor: Colors.white, 284 | progressBorderColor: Colors.green, 285 | barRadius: Radius.elliptical(5, 10), 286 | percent: .9, 287 | animation: true, 288 | animationDuration: 1400, 289 | ), 290 | ), 291 | Text('Custom Border Color'), 292 | ], 293 | ), 294 | ), 295 | ), 296 | ); 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /example/lib/segment_page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:percent_indicator/flutter_percent_indicator.dart'; 3 | 4 | void main() { 5 | runApp(const MyApp()); 6 | } 7 | 8 | class MyApp extends StatelessWidget { 9 | const MyApp({Key? key}) : super(key: key); 10 | 11 | @override 12 | Widget build(BuildContext context) { 13 | return MaterialApp( 14 | title: 'Multi-Segment Indicator Demo', 15 | debugShowCheckedModeBanner: false, 16 | theme: ThemeData(), 17 | home: const MultiSegmentDemo(), 18 | ); 19 | } 20 | } 21 | 22 | class MultiSegmentDemo extends StatefulWidget { 23 | const MultiSegmentDemo({Key? key}) : super(key: key); 24 | 25 | @override 26 | State createState() => _MultiSegmentDemoState(); 27 | } 28 | 29 | class _MultiSegmentDemoState extends State { 30 | double firstSegment = 0.3; 31 | double secondSegment = 0.4; 32 | double thirdSegment = 0.3; 33 | double lineHeight = 30.0; 34 | bool hasStripes = true; 35 | double borderRadius = 10.0; 36 | Color firstColor = const Color(0xFF4285F4); 37 | Color secondColor = const Color(0xFF6DD5F6); 38 | Color thirdColor = const Color(0xFFEFEFEF); 39 | bool animate = true; 40 | int animationDuration = 1000; 41 | bool animateFromLastPercent = true; 42 | Curve animationCurve = Curves.easeInOut; 43 | 44 | final List curves = [ 45 | Curves.linear, 46 | Curves.easeIn, 47 | Curves.easeOut, 48 | Curves.easeInOut, 49 | Curves.elasticIn, 50 | Curves.elasticOut, 51 | Curves.bounceIn, 52 | Curves.bounceOut, 53 | ]; 54 | 55 | String _getCurveName(Curve curve) { 56 | return curve.toString().split('.').last; 57 | } 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | return Scaffold( 62 | appBar: AppBar( 63 | title: const Text('Multi-Segment Indicator Demo'), 64 | ), 65 | body: SingleChildScrollView( 66 | padding: const EdgeInsets.all(16.0), 67 | child: Column( 68 | crossAxisAlignment: CrossAxisAlignment.start, 69 | children: [ 70 | Card( 71 | color: Colors.white, 72 | child: Padding( 73 | padding: const EdgeInsets.all(16.0), 74 | child: Column( 75 | crossAxisAlignment: CrossAxisAlignment.start, 76 | children: [ 77 | MultiSegmentLinearIndicator( 78 | width: MediaQuery.of(context).size.width - 64, 79 | lineHeight: lineHeight, 80 | segments: [ 81 | SegmentLinearIndicator( 82 | percent: firstSegment, color: firstColor), 83 | SegmentLinearIndicator( 84 | percent: secondSegment, 85 | color: secondColor, 86 | enableStripes: hasStripes), 87 | SegmentLinearIndicator( 88 | percent: thirdSegment, 89 | color: thirdColor, 90 | ), 91 | ], 92 | barRadius: Radius.circular(borderRadius), 93 | animation: animate, 94 | animationDuration: animationDuration, 95 | curve: animationCurve, 96 | animateFromLastPercent: animateFromLastPercent, 97 | onAnimationEnd: () { 98 | ScaffoldMessenger.of(context).showSnackBar( 99 | const SnackBar( 100 | content: Text('Animation completed!'), 101 | duration: Duration(seconds: 1), 102 | ), 103 | ); 104 | }, 105 | ), 106 | ], 107 | ), 108 | ), 109 | ), 110 | const SizedBox(height: 16), 111 | ], 112 | ), 113 | ), 114 | ); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /example/percent_indicator_example.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /example/percent_indicator_example_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | characters: 5 | dependency: transitive 6 | description: 7 | name: characters 8 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "1.3.0" 12 | collection: 13 | dependency: transitive 14 | description: 15 | name: collection 16 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "1.18.0" 20 | flutter: 21 | dependency: "direct main" 22 | description: flutter 23 | source: sdk 24 | version: "0.0.0" 25 | material_color_utilities: 26 | dependency: transitive 27 | description: 28 | name: material_color_utilities 29 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 30 | url: "https://pub.dev" 31 | source: hosted 32 | version: "0.11.1" 33 | meta: 34 | dependency: transitive 35 | description: 36 | name: meta 37 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 38 | url: "https://pub.dev" 39 | source: hosted 40 | version: "1.15.0" 41 | percent_indicator: 42 | dependency: "direct main" 43 | description: 44 | path: ".." 45 | relative: true 46 | source: path 47 | version: "4.2.5" 48 | sky_engine: 49 | dependency: transitive 50 | description: flutter 51 | source: sdk 52 | version: "0.0.99" 53 | vector_math: 54 | dependency: transitive 55 | description: 56 | name: vector_math 57 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 58 | url: "https://pub.dev" 59 | source: hosted 60 | version: "2.1.4" 61 | sdks: 62 | dart: ">=3.3.0-0 <4.0.0" 63 | flutter: ">=2.12.0" 64 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: percent_indicator_example 2 | description: A new Flutter project. 3 | 4 | version: 1.0.0+1 5 | publish_to: "none" 6 | 7 | dependencies: 8 | flutter: 9 | sdk: flutter 10 | 11 | percent_indicator: 12 | path: ../ 13 | environment: 14 | sdk: ">=2.12.0-0 <3.0.0" 15 | # For information on the generic Dart part of this file, see the 16 | # following page: https://www.dartlang.org/tools/pub/pubspec 17 | 18 | # The following section is specific to Flutter. 19 | flutter: 20 | # The following line ensures that the Material Icons font is 21 | # included with your application, so that you can use the icons in 22 | # the material Icons class. 23 | uses-material-design: true 24 | -------------------------------------------------------------------------------- /lib/circular_percent_indicator.dart: -------------------------------------------------------------------------------- 1 | //import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'dart:math' as math; 5 | 6 | enum ArcType { HALF, FULL, FULL_REVERSED } 7 | 8 | enum CircularStrokeCap { butt, round, square } 9 | 10 | extension CircularStrokeCapExtension on CircularStrokeCap { 11 | StrokeCap get strokeCap { 12 | switch (this) { 13 | case CircularStrokeCap.butt: 14 | return StrokeCap.butt; 15 | case CircularStrokeCap.round: 16 | return StrokeCap.round; 17 | case CircularStrokeCap.square: 18 | return StrokeCap.square; 19 | } 20 | } 21 | } 22 | 23 | num radians(num deg) => deg * (math.pi / 180.0); 24 | 25 | // ignore: must_be_immutable 26 | class CircularPercentIndicator extends StatefulWidget { 27 | ///Percent value between 0.0 and 1.0 28 | final double percent; 29 | final double radius; 30 | 31 | ///Width of the progress bar of the circle 32 | final double lineWidth; 33 | 34 | ///Width of the unfilled background of the progress bar 35 | final double backgroundWidth; 36 | 37 | ///First color applied to the complete circle 38 | final Color fillColor; 39 | 40 | ///Color of the border of the progress bar , default = null 41 | final Color? progressBorderColor; 42 | 43 | ///Color of the background of the circle , default = transparent 44 | final Color backgroundColor; 45 | 46 | Color get progressColor => _progressColor; 47 | 48 | late Color _progressColor; 49 | 50 | ///true if you want the circle to have animation 51 | final bool animation; 52 | 53 | ///duration of the animation in milliseconds, It only applies if animation attribute is true 54 | final int animationDuration; 55 | 56 | ///widget at the top of the circle 57 | final Widget? header; 58 | 59 | ///widget at the bottom of the circle 60 | final Widget? footer; 61 | 62 | ///widget inside the circle 63 | final Widget? center; 64 | 65 | final LinearGradient? linearGradient; 66 | 67 | ///The kind of finish to place on the end of lines drawn, values supported: butt, round, square 68 | final CircularStrokeCap circularStrokeCap; 69 | 70 | ///the angle which the circle will start the progress (in degrees, eg: 0.0, 45.0, 90.0) 71 | final double startAngle; 72 | 73 | /// set true if you want to animate the linear from the last percent value you set 74 | final bool animateFromLastPercent; 75 | 76 | /// set to false if you do not want the default behavior of initially animating up from 0% 77 | final bool animateToInitialPercent; 78 | 79 | /// set false if you don't want to preserve the state of the widget 80 | final bool addAutomaticKeepAlive; 81 | 82 | /// set the arc type 83 | final ArcType? arcType; 84 | 85 | /// set a circular background color when use the arcType property 86 | final Color? arcBackgroundColor; 87 | 88 | /// set true when you want to display the progress in reverse mode 89 | final bool reverse; 90 | 91 | /// Creates a mask filter that takes the progress shape being drawn and blurs it. 92 | final MaskFilter? maskFilter; 93 | 94 | /// set a circular curve animation type 95 | final Curve curve; 96 | 97 | /// set true when you want to restart the animation, it restarts only when reaches 1.0 as a value 98 | /// defaults to false 99 | final bool restartAnimation; 100 | 101 | /// Callback called when the animation ends (only if `animation` is true) 102 | final VoidCallback? onAnimationEnd; 103 | 104 | /// Display a widget indicator at the end of the progress. It only works when `animation` is true 105 | final Widget? widgetIndicator; 106 | 107 | /// Set to true if you want to rotate linear gradient in accordance to the [startAngle]. 108 | final bool rotateLinearGradient; 109 | 110 | /// Set true if you want to display only part of [linearGradient] based on percent value. 111 | /// Works only if [rotateLinearGradient] is true. 112 | final bool clipRotatedLinearGradient; 113 | 114 | /// Return current percent value if animation is true. 115 | final Function(double value)? onPercentValue; 116 | 117 | CircularPercentIndicator({ 118 | Key? key, 119 | this.percent = 0.0, 120 | this.lineWidth = 5.0, 121 | this.startAngle = 0.0, 122 | required this.radius, 123 | this.fillColor = Colors.transparent, 124 | this.backgroundColor = const Color(0xFFB8C7CB), 125 | Color? progressColor, 126 | //negative values ignored, replaced with lineWidth 127 | this.backgroundWidth = -1, 128 | this.linearGradient, 129 | this.animation = false, 130 | this.animationDuration = 500, 131 | this.header, 132 | this.footer, 133 | this.center, 134 | this.addAutomaticKeepAlive = true, 135 | this.circularStrokeCap = CircularStrokeCap.butt, 136 | this.arcBackgroundColor, 137 | this.arcType, 138 | this.animateFromLastPercent = false, 139 | this.animateToInitialPercent = true, 140 | this.reverse = false, 141 | this.curve = Curves.linear, 142 | this.maskFilter, 143 | this.restartAnimation = false, 144 | this.onAnimationEnd, 145 | this.widgetIndicator, 146 | this.rotateLinearGradient = false, 147 | this.clipRotatedLinearGradient = false, 148 | this.progressBorderColor, 149 | this.onPercentValue, 150 | }) : super(key: key) { 151 | if (linearGradient != null && progressColor != null) { 152 | throw ArgumentError( 153 | 'Cannot provide both linearGradient and progressColor'); 154 | } 155 | _progressColor = progressColor ?? Colors.red; 156 | 157 | assert(startAngle >= 0.0); 158 | if (percent < 0.0 || percent > 1.0) { 159 | throw Exception( 160 | "Percent value must be a double between 0.0 and 1.0, but it's $percent"); 161 | } 162 | 163 | if (arcType == null && arcBackgroundColor != null) { 164 | throw ArgumentError('arcType is required when you arcBackgroundColor'); 165 | } 166 | } 167 | 168 | @override 169 | _CircularPercentIndicatorState createState() => 170 | _CircularPercentIndicatorState(); 171 | } 172 | 173 | class _CircularPercentIndicatorState extends State 174 | with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { 175 | AnimationController? _animationController; 176 | Animation? _animation; 177 | double _percent = 0.0; 178 | double _diameter = 0.0; 179 | 180 | @override 181 | void dispose() { 182 | if (_animationController != null) { 183 | _animationController!.dispose(); 184 | } 185 | super.dispose(); 186 | } 187 | 188 | @override 189 | void initState() { 190 | if (widget.animation) { 191 | if (!widget.animateToInitialPercent) _percent = widget.percent; 192 | _animationController = AnimationController( 193 | vsync: this, 194 | duration: Duration(milliseconds: widget.animationDuration), 195 | ); 196 | _animation = Tween(begin: _percent, end: widget.percent).animate( 197 | CurvedAnimation(parent: _animationController!, curve: widget.curve), 198 | )..addListener(() { 199 | setState(() { 200 | _percent = _animation!.value; 201 | widget.onPercentValue?.call(_percent); 202 | }); 203 | if (widget.restartAnimation && _percent == 1.0) { 204 | _animationController!.repeat(min: 0, max: 1.0); 205 | } 206 | }); 207 | _animationController!.addStatusListener((status) { 208 | if (widget.onAnimationEnd != null && 209 | status == AnimationStatus.completed) { 210 | widget.onAnimationEnd!(); 211 | } 212 | }); 213 | _animationController!.forward(); 214 | } else { 215 | _updateProgress(); 216 | } 217 | _diameter = widget.radius * 2; 218 | super.initState(); 219 | } 220 | 221 | void _checkIfNeedCancelAnimation(CircularPercentIndicator oldWidget) { 222 | if (oldWidget.animation && 223 | !widget.animation && 224 | _animationController != null) { 225 | _animationController!.stop(); 226 | } 227 | } 228 | 229 | @override 230 | void didUpdateWidget(CircularPercentIndicator oldWidget) { 231 | super.didUpdateWidget(oldWidget); 232 | if (oldWidget.percent != widget.percent || 233 | oldWidget.startAngle != widget.startAngle) { 234 | if (_animationController != null) { 235 | _animationController!.duration = 236 | Duration(milliseconds: widget.animationDuration); 237 | _animation = Tween( 238 | begin: widget.animateFromLastPercent ? oldWidget.percent : 0.0, 239 | end: widget.percent, 240 | ).animate( 241 | CurvedAnimation(parent: _animationController!, curve: widget.curve), 242 | ); 243 | _animationController!.forward(from: 0.0); 244 | } else { 245 | _updateProgress(); 246 | } 247 | } 248 | _checkIfNeedCancelAnimation(oldWidget); 249 | } 250 | 251 | _updateProgress() { 252 | setState(() => _percent = widget.percent); 253 | } 254 | 255 | @override 256 | Widget build(BuildContext context) { 257 | super.build(context); 258 | var items = List.empty(growable: true); 259 | if (widget.header != null) { 260 | items.add(widget.header!); 261 | } 262 | items.add( 263 | Container( 264 | height: _diameter, 265 | width: _diameter, 266 | child: Stack( 267 | children: [ 268 | CustomPaint( 269 | painter: _CirclePainter( 270 | progress: _percent * 360, 271 | progressColor: widget.progressColor, 272 | progressBorderColor: widget.progressBorderColor, 273 | backgroundColor: widget.backgroundColor, 274 | startAngle: widget.startAngle, 275 | circularStrokeCap: widget.circularStrokeCap, 276 | radius: widget.radius - widget.lineWidth / 2, 277 | lineWidth: widget.lineWidth, 278 | //negative values ignored, replaced with lineWidth 279 | backgroundWidth: widget.backgroundWidth >= 0.0 280 | ? (widget.backgroundWidth) 281 | : widget.lineWidth, 282 | arcBackgroundColor: widget.arcBackgroundColor, 283 | arcType: widget.arcType, 284 | reverse: widget.reverse, 285 | linearGradient: widget.linearGradient, 286 | maskFilter: widget.maskFilter, 287 | rotateLinearGradient: widget.rotateLinearGradient, 288 | clipRotatedLinearGradient: widget.clipRotatedLinearGradient, 289 | ), 290 | child: (widget.center != null) 291 | ? Center(child: widget.center) 292 | : SizedBox.expand(), 293 | ), 294 | if (widget.widgetIndicator != null && widget.animation) 295 | Positioned.fill( 296 | child: Transform.rotate( 297 | angle: radians( 298 | (widget.circularStrokeCap != CircularStrokeCap.butt && 299 | widget.reverse) 300 | ? -15 301 | : 0) 302 | .toDouble(), 303 | child: Transform.rotate( 304 | angle: getCurrentPercent(_percent), 305 | child: Transform.translate( 306 | offset: Offset( 307 | (widget.circularStrokeCap != CircularStrokeCap.butt) 308 | ? widget.lineWidth / 2 309 | : 0, 310 | (-widget.radius + widget.lineWidth / 2), 311 | ), 312 | child: widget.widgetIndicator, 313 | ), 314 | ), 315 | ), 316 | ), 317 | ], 318 | ), 319 | ), 320 | ); 321 | 322 | if (widget.footer != null) { 323 | items.add(widget.footer!); 324 | } 325 | 326 | return Material( 327 | color: widget.fillColor, 328 | child: Container( 329 | child: Column( 330 | mainAxisAlignment: MainAxisAlignment.center, 331 | mainAxisSize: MainAxisSize.min, 332 | children: items, 333 | ), 334 | ), 335 | ); 336 | } 337 | 338 | double getCurrentPercent(double percent) { 339 | if (widget.arcType != null) { 340 | final angle = _getStartAngleFixedMargin(widget.arcType!).fixedStartAngle; 341 | final fixedPercent = 342 | widget.percent > 0 ? 1.0 / widget.percent * _percent : 0; 343 | late double margin; 344 | if (widget.arcType == ArcType.HALF) { 345 | margin = 180 * widget.percent; 346 | } else { 347 | margin = 280 * widget.percent; 348 | } 349 | return radians(angle + margin * fixedPercent).toDouble(); 350 | } else { 351 | final angle = 360; 352 | return radians((widget.reverse ? -angle : angle) * _percent).toDouble(); 353 | } 354 | } 355 | 356 | @override 357 | bool get wantKeepAlive => widget.addAutomaticKeepAlive; 358 | } 359 | 360 | _ArcAngles _getStartAngleFixedMargin(ArcType arcType) { 361 | double fixedStartAngle, startAngleFixedMargin; 362 | if (arcType == ArcType.FULL_REVERSED) { 363 | fixedStartAngle = 399; 364 | startAngleFixedMargin = 312 / fixedStartAngle; 365 | } else if (arcType == ArcType.FULL) { 366 | fixedStartAngle = 220; 367 | startAngleFixedMargin = 172 / fixedStartAngle; 368 | } else { 369 | fixedStartAngle = 270; 370 | startAngleFixedMargin = 135 / fixedStartAngle; 371 | } 372 | return _ArcAngles( 373 | fixedStartAngle: fixedStartAngle, 374 | startAngleFixedMargin: startAngleFixedMargin, 375 | ); 376 | } 377 | 378 | class _ArcAngles { 379 | const _ArcAngles( 380 | {required this.fixedStartAngle, required this.startAngleFixedMargin}); 381 | final double fixedStartAngle; 382 | final double startAngleFixedMargin; 383 | } 384 | 385 | class _CirclePainter extends CustomPainter { 386 | final Paint _paintBackground = Paint(); 387 | final Paint _paintLine = Paint(); 388 | final Paint _paintLineBorder = Paint(); 389 | final Paint _paintBackgroundStartAngle = Paint(); 390 | final double lineWidth; 391 | final double backgroundWidth; 392 | final double progress; 393 | final double radius; 394 | final Color progressColor; 395 | final Color? progressBorderColor; 396 | final Color backgroundColor; 397 | final CircularStrokeCap circularStrokeCap; 398 | final double startAngle; 399 | final LinearGradient? linearGradient; 400 | final Color? arcBackgroundColor; 401 | final ArcType? arcType; 402 | final bool reverse; 403 | final MaskFilter? maskFilter; 404 | final bool rotateLinearGradient; 405 | final bool clipRotatedLinearGradient; 406 | 407 | _CirclePainter({ 408 | required this.lineWidth, 409 | required this.backgroundWidth, 410 | required this.progress, 411 | required this.radius, 412 | required this.progressColor, 413 | required this.backgroundColor, 414 | this.progressBorderColor, 415 | this.startAngle = 0.0, 416 | this.circularStrokeCap = CircularStrokeCap.butt, 417 | this.linearGradient, 418 | required this.reverse, 419 | this.arcBackgroundColor, 420 | this.arcType, 421 | this.maskFilter, 422 | required this.rotateLinearGradient, 423 | required this.clipRotatedLinearGradient, 424 | }) { 425 | _paintBackground.color = backgroundColor; 426 | _paintBackground.style = PaintingStyle.stroke; 427 | _paintBackground.strokeWidth = backgroundWidth; 428 | _paintBackground.strokeCap = circularStrokeCap.strokeCap; 429 | 430 | if (arcBackgroundColor != null) { 431 | _paintBackgroundStartAngle.color = arcBackgroundColor!; 432 | _paintBackgroundStartAngle.style = PaintingStyle.stroke; 433 | _paintBackgroundStartAngle.strokeWidth = lineWidth; 434 | _paintBackgroundStartAngle.strokeCap = circularStrokeCap.strokeCap; 435 | } 436 | 437 | _paintLine.color = progressColor; 438 | _paintLine.style = PaintingStyle.stroke; 439 | _paintLine.strokeWidth = 440 | progressBorderColor != null ? lineWidth - 2 : lineWidth; 441 | _paintLine.strokeCap = circularStrokeCap.strokeCap; 442 | 443 | if (progressBorderColor != null) { 444 | _paintLineBorder.color = progressBorderColor!; 445 | _paintLineBorder.style = PaintingStyle.stroke; 446 | _paintLineBorder.strokeWidth = lineWidth; 447 | _paintLineBorder.strokeCap = circularStrokeCap.strokeCap; 448 | } 449 | } 450 | 451 | @override 452 | void paint(Canvas canvas, Size size) { 453 | final center = Offset(size.width / 2, size.height / 2); 454 | double fixedStartAngle = startAngle; 455 | double startAngleFixedMargin = 1.0; 456 | if (arcType != null) { 457 | final arcAngles = _getStartAngleFixedMargin(arcType!); 458 | fixedStartAngle = arcAngles.fixedStartAngle; 459 | startAngleFixedMargin = arcAngles.startAngleFixedMargin; 460 | } 461 | if (arcType == null) { 462 | canvas.drawCircle(center, radius, _paintBackground); 463 | } 464 | 465 | if (maskFilter != null) { 466 | _paintLineBorder.maskFilter = _paintLine.maskFilter = maskFilter; 467 | } 468 | if (linearGradient != null) { 469 | if (rotateLinearGradient && progress > 0) { 470 | double correction = 0; 471 | if (_paintLine.strokeCap != StrokeCap.butt) { 472 | correction = math.atan(_paintLine.strokeWidth / 2 / radius); 473 | } 474 | _paintLineBorder.shader = _paintLine.shader = SweepGradient( 475 | transform: reverse 476 | ? GradientRotation( 477 | radians(-90 - progress + startAngle) - correction) 478 | : GradientRotation(radians(-90.0 + startAngle) - correction), 479 | startAngle: radians(0).toDouble(), 480 | endAngle: clipRotatedLinearGradient 481 | ? radians(360).toDouble() 482 | : radians(progress).toDouble(), 483 | tileMode: TileMode.clamp, 484 | colors: reverse 485 | ? linearGradient!.colors.reversed.toList() 486 | : linearGradient!.colors, 487 | ).createShader( 488 | Rect.fromCircle(center: center, radius: radius), 489 | ); 490 | } else if (!rotateLinearGradient) { 491 | _paintLineBorder.shader = 492 | _paintLine.shader = linearGradient!.createShader( 493 | Rect.fromCircle(center: center, radius: radius), 494 | ); 495 | } 496 | } 497 | 498 | if (arcBackgroundColor != null) { 499 | canvas.drawArc( 500 | Rect.fromCircle(center: center, radius: radius), 501 | radians(-90.0 + fixedStartAngle).toDouble(), 502 | radians(360 * startAngleFixedMargin).toDouble(), 503 | false, 504 | _paintBackgroundStartAngle, 505 | ); 506 | } 507 | 508 | if (reverse) { 509 | final start = 510 | radians(360 * startAngleFixedMargin - 90.0 + fixedStartAngle) 511 | .toDouble(); 512 | final end = radians(-progress * startAngleFixedMargin).toDouble(); 513 | if (progressBorderColor != null) { 514 | canvas.drawArc( 515 | Rect.fromCircle( 516 | center: center, 517 | radius: radius, 518 | ), 519 | start, 520 | end, 521 | false, 522 | _paintLineBorder, 523 | ); 524 | } 525 | canvas.drawArc( 526 | Rect.fromCircle( 527 | center: center, 528 | radius: radius, 529 | ), 530 | start, 531 | end, 532 | false, 533 | _paintLine, 534 | ); 535 | } else { 536 | final start = radians(-90.0 + fixedStartAngle).toDouble(); 537 | final end = radians(progress * startAngleFixedMargin).toDouble(); 538 | if (progressBorderColor != null) { 539 | canvas.drawArc( 540 | Rect.fromCircle( 541 | center: center, 542 | radius: radius, 543 | ), 544 | start, 545 | end, 546 | false, 547 | _paintLineBorder, 548 | ); 549 | } 550 | canvas.drawArc( 551 | Rect.fromCircle( 552 | center: center, 553 | radius: radius, 554 | ), 555 | start, 556 | end, 557 | false, 558 | _paintLine, 559 | ); 560 | } 561 | } 562 | 563 | @override 564 | bool shouldRepaint(CustomPainter oldDelegate) => true; 565 | } 566 | -------------------------------------------------------------------------------- /lib/flutter_percent_indicator.dart: -------------------------------------------------------------------------------- 1 | library flutter_percent_indicator; 2 | 3 | export 'circular_percent_indicator.dart'; 4 | export 'linear_percent_indicator.dart'; 5 | export 'multi_segment_linear_indicator.dart'; 6 | -------------------------------------------------------------------------------- /lib/linear_percent_indicator.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: unused_element 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | @Deprecated('This property is no longer used, please use barRadius instead.') 6 | enum LinearStrokeCap { butt, round, roundAll } 7 | 8 | extension ExtDouble on double { 9 | bool get isZero => this.toString() == '0.0'; 10 | } 11 | 12 | // ignore: must_be_immutable 13 | class LinearPercentIndicator extends StatefulWidget { 14 | ///Percent value between 0.0 and 1.0 15 | final double percent; 16 | final double? width; 17 | 18 | ///Height of the line 19 | final double lineHeight; 20 | 21 | ///Color of the background of the Line , default = transparent 22 | final Color fillColor; 23 | 24 | ///Color of the border of the progress bar , default = null 25 | final Color? progressBorderColor; 26 | 27 | ///First color applied to the complete line 28 | Color get backgroundColor => _backgroundColor; 29 | late Color _backgroundColor; 30 | 31 | ///First color applied to the complete line 32 | final LinearGradient? linearGradientBackgroundColor; 33 | 34 | Color get progressColor => _progressColor; 35 | 36 | late Color _progressColor; 37 | 38 | ///true if you want the Line to have animation 39 | final bool animation; 40 | 41 | ///duration of the animation in milliseconds, It only applies if animation attribute is true 42 | final int animationDuration; 43 | 44 | ///widget at the left of the Line 45 | final Widget? leading; 46 | 47 | ///widget at the right of the Line 48 | final Widget? trailing; 49 | 50 | ///widget inside the Line 51 | final Widget? center; 52 | 53 | ///The kind of finish to place on the end of lines drawn, values supported: butt, round, roundAll 54 | @Deprecated('This property is no longer used, please use barRadius instead.') 55 | final LinearStrokeCap? linearStrokeCap; 56 | 57 | /// The border radius of the progress bar (Will replace linearStrokeCap) 58 | final Radius? barRadius; 59 | 60 | ///alignment of the Row (leading-widget-center-trailing) 61 | final MainAxisAlignment alignment; 62 | 63 | ///padding to the LinearPercentIndicator 64 | final EdgeInsets padding; 65 | 66 | /// set true if you want to animate the linear from the last percent value you set 67 | final bool animateFromLastPercent; 68 | 69 | /// set to false if you do not want the default behavior of initially animating up from 0% 70 | final bool animateToInitialPercent; 71 | 72 | /// If present, this will make the progress bar colored by this gradient. 73 | /// 74 | /// This will override [progressColor]. It is an error to provide both. 75 | final LinearGradient? linearGradient; 76 | 77 | /// set false if you don't want to preserve the state of the widget 78 | final bool addAutomaticKeepAlive; 79 | 80 | /// set true if you want to animate the linear from the right to left (RTL) 81 | final bool isRTL; 82 | 83 | /// Creates a mask filter that takes the progress shape being drawn and blurs it. 84 | final MaskFilter? maskFilter; 85 | 86 | /// Set true if you want to display only part of [linearGradient] based on percent value 87 | /// (ie. create 'VU effect'). If no [linearGradient] is specified this option is ignored. 88 | final bool clipLinearGradient; 89 | 90 | /// set a linear curve animation type 91 | final Curve curve; 92 | 93 | /// set true when you want to restart the animation, it restarts only when reaches 1.0 as a value 94 | /// defaults to false 95 | final bool restartAnimation; 96 | 97 | /// Callback called when the animation ends (only if `animation` is true) 98 | final VoidCallback? onAnimationEnd; 99 | 100 | /// Display a widget indicator at the end of the progress. It only works when `animation` is true 101 | final Widget? widgetIndicator; 102 | 103 | /// Return current percent value if animation is true. 104 | final Function(double value)? onPercentValue; 105 | 106 | LinearPercentIndicator({ 107 | Key? key, 108 | this.fillColor = Colors.transparent, 109 | this.percent = 0.0, 110 | this.lineHeight = 5.0, 111 | this.width, 112 | Color? backgroundColor, 113 | this.linearGradientBackgroundColor, 114 | this.linearGradient, 115 | Color? progressColor, 116 | this.animation = false, 117 | this.animationDuration = 500, 118 | this.animateFromLastPercent = false, 119 | this.animateToInitialPercent = true, 120 | this.isRTL = false, 121 | this.leading, 122 | this.trailing, 123 | this.center, 124 | this.addAutomaticKeepAlive = true, 125 | this.linearStrokeCap, 126 | this.barRadius, 127 | this.padding = const EdgeInsets.symmetric(horizontal: 10.0), 128 | this.alignment = MainAxisAlignment.start, 129 | this.maskFilter, 130 | this.clipLinearGradient = false, 131 | this.curve = Curves.linear, 132 | this.restartAnimation = false, 133 | this.onAnimationEnd, 134 | this.widgetIndicator, 135 | this.progressBorderColor, 136 | this.onPercentValue, 137 | }) : super(key: key) { 138 | if (linearGradient != null && progressColor != null) { 139 | throw ArgumentError( 140 | 'Cannot provide both linearGradient and progressColor'); 141 | } 142 | _progressColor = progressColor ?? Colors.red; 143 | 144 | if (linearGradientBackgroundColor != null && backgroundColor != null) { 145 | throw ArgumentError( 146 | 'Cannot provide both linearGradientBackgroundColor and backgroundColor'); 147 | } 148 | _backgroundColor = backgroundColor ?? Color(0xFFB8C7CB); 149 | 150 | if (percent < 0.0 || percent > 1.0) { 151 | throw new Exception( 152 | "Percent value must be a double between 0.0 and 1.0, but it's $percent"); 153 | } 154 | } 155 | 156 | @override 157 | _LinearPercentIndicatorState createState() => _LinearPercentIndicatorState(); 158 | } 159 | 160 | class _LinearPercentIndicatorState extends State 161 | with SingleTickerProviderStateMixin, AutomaticKeepAliveClientMixin { 162 | AnimationController? _animationController; 163 | Animation? _animation; 164 | double _percent = 0.0; 165 | final _containerKey = GlobalKey(); 166 | final _keyIndicator = GlobalKey(); 167 | double _containerWidth = 0.0; 168 | double _containerHeight = 0.0; 169 | double _indicatorWidth = 0.0; 170 | double _indicatorHeight = 0.0; 171 | 172 | @override 173 | void dispose() { 174 | _animationController?.dispose(); 175 | super.dispose(); 176 | } 177 | 178 | @override 179 | void initState() { 180 | WidgetsBinding.instance.addPostFrameCallback((_) { 181 | if (mounted) { 182 | setState(() { 183 | _containerWidth = _containerKey.currentContext?.size?.width ?? 0.0; 184 | _containerHeight = _containerKey.currentContext?.size?.height ?? 0.0; 185 | if (_keyIndicator.currentContext != null) { 186 | _indicatorWidth = _keyIndicator.currentContext?.size?.width ?? 0.0; 187 | _indicatorHeight = 188 | _keyIndicator.currentContext?.size?.height ?? 0.0; 189 | } 190 | }); 191 | } 192 | }); 193 | if (widget.animation) { 194 | if (!widget.animateToInitialPercent) _percent = widget.percent; 195 | _animationController = AnimationController( 196 | vsync: this, 197 | duration: Duration(milliseconds: widget.animationDuration)); 198 | _animation = Tween(begin: _percent, end: widget.percent).animate( 199 | CurvedAnimation(parent: _animationController!, curve: widget.curve), 200 | )..addListener(() { 201 | setState(() { 202 | _percent = _animation!.value; 203 | widget.onPercentValue?.call(_percent); 204 | }); 205 | if (widget.restartAnimation && _percent == 1.0) { 206 | _animationController!.repeat(min: 0, max: 1.0); 207 | } 208 | }); 209 | _animationController!.addStatusListener((status) { 210 | if (widget.onAnimationEnd != null && 211 | status == AnimationStatus.completed) { 212 | widget.onAnimationEnd!(); 213 | } 214 | }); 215 | _animationController!.forward(); 216 | } else { 217 | _updateProgress(); 218 | } 219 | super.initState(); 220 | } 221 | 222 | void _checkIfNeedCancelAnimation(LinearPercentIndicator oldWidget) { 223 | if (oldWidget.animation && 224 | !widget.animation && 225 | _animationController != null) { 226 | _animationController!.stop(); 227 | } 228 | } 229 | 230 | @override 231 | void didUpdateWidget(LinearPercentIndicator oldWidget) { 232 | super.didUpdateWidget(oldWidget); 233 | if (oldWidget.percent != widget.percent) { 234 | if (_animationController != null) { 235 | _animationController!.duration = 236 | Duration(milliseconds: widget.animationDuration); 237 | _animation = Tween( 238 | begin: widget.animateFromLastPercent ? oldWidget.percent : 0.0, 239 | end: widget.percent) 240 | .animate( 241 | CurvedAnimation(parent: _animationController!, curve: widget.curve), 242 | ); 243 | _animationController!.forward(from: 0.0); 244 | } else { 245 | _updateProgress(); 246 | } 247 | } 248 | _checkIfNeedCancelAnimation(oldWidget); 249 | } 250 | 251 | _updateProgress() { 252 | setState(() { 253 | _percent = widget.percent; 254 | }); 255 | } 256 | 257 | @override 258 | Widget build(BuildContext context) { 259 | super.build(context); 260 | var items = List.empty(growable: true); 261 | if (widget.leading != null) { 262 | items.add(widget.leading!); 263 | } 264 | final hasSetWidth = widget.width != null; 265 | final percentPositionedHorizontal = 266 | _containerWidth * _percent - _indicatorWidth / 3; 267 | //LayoutBuilder is used to get the size of the container where the widget is rendered 268 | var containerWidget = LayoutBuilder(builder: (context, constraints) { 269 | _containerWidth = constraints.maxWidth; 270 | _containerHeight = constraints.maxHeight; 271 | return Container( 272 | width: hasSetWidth ? widget.width : double.infinity, 273 | height: widget.lineHeight, 274 | padding: widget.padding, 275 | child: Stack( 276 | clipBehavior: Clip.none, 277 | children: [ 278 | CustomPaint( 279 | key: _containerKey, 280 | painter: _LinearPainter( 281 | isRTL: widget.isRTL, 282 | progress: _percent, 283 | progressColor: widget.progressColor, 284 | linearGradient: widget.linearGradient, 285 | backgroundColor: widget.backgroundColor, 286 | barRadius: widget.barRadius ?? 287 | Radius.zero, // If radius is not defined, set it to zero 288 | linearGradientBackgroundColor: 289 | widget.linearGradientBackgroundColor, 290 | maskFilter: widget.maskFilter, 291 | clipLinearGradient: widget.clipLinearGradient, 292 | ), 293 | child: (widget.center != null) 294 | ? Center(child: widget.center) 295 | : Container(), 296 | ), 297 | if (widget.widgetIndicator != null && _indicatorWidth == 0) 298 | Opacity( 299 | opacity: 0.0, 300 | key: _keyIndicator, 301 | child: widget.widgetIndicator, 302 | ), 303 | if (widget.widgetIndicator != null && 304 | _containerWidth > 0 && 305 | _indicatorWidth > 0) 306 | Positioned( 307 | right: widget.isRTL ? percentPositionedHorizontal : null, 308 | left: !widget.isRTL ? percentPositionedHorizontal : null, 309 | top: _containerHeight / 2 - _indicatorHeight, 310 | child: widget.widgetIndicator!, 311 | ), 312 | ], 313 | ), 314 | ); 315 | }); 316 | 317 | if (hasSetWidth) { 318 | items.add(containerWidget); 319 | } else { 320 | items.add(Expanded( 321 | child: containerWidget, 322 | )); 323 | } 324 | if (widget.trailing != null) { 325 | items.add(widget.trailing!); 326 | } 327 | 328 | return Material( 329 | color: Colors.transparent, 330 | child: Container( 331 | color: widget.fillColor, 332 | child: Row( 333 | mainAxisAlignment: widget.alignment, 334 | crossAxisAlignment: CrossAxisAlignment.center, 335 | children: items, 336 | ), 337 | ), 338 | ); 339 | } 340 | 341 | @override 342 | bool get wantKeepAlive => widget.addAutomaticKeepAlive; 343 | } 344 | 345 | class _LinearPainter extends CustomPainter { 346 | final Paint _paintBackground = new Paint(); 347 | final Paint _paintLine = new Paint(); 348 | final Paint _paintLineBorder = new Paint(); 349 | final double progress; 350 | final bool isRTL; 351 | final Color progressColor; 352 | final Color? progressBorderColor; 353 | final Color backgroundColor; 354 | final Radius barRadius; 355 | final LinearGradient? linearGradient; 356 | final LinearGradient? linearGradientBackgroundColor; 357 | final MaskFilter? maskFilter; 358 | final bool clipLinearGradient; 359 | 360 | _LinearPainter({ 361 | required this.progress, 362 | required this.isRTL, 363 | required this.progressColor, 364 | required this.backgroundColor, 365 | required this.barRadius, 366 | this.progressBorderColor, 367 | this.linearGradient, 368 | this.maskFilter, 369 | required this.clipLinearGradient, 370 | this.linearGradientBackgroundColor, 371 | }) { 372 | _paintBackground.color = backgroundColor; 373 | 374 | _paintLine.color = 375 | progress == 0 ? progressColor.withOpacity(0.0) : progressColor; 376 | 377 | if (progressBorderColor != null) { 378 | _paintLineBorder.color = progress == 0 379 | ? progressBorderColor!.withOpacity(0.0) 380 | : progressBorderColor!; 381 | } 382 | } 383 | 384 | @override 385 | void paint(Canvas canvas, Size size) { 386 | // Draw background first 387 | Path backgroundPath = Path(); 388 | backgroundPath.addRRect(RRect.fromRectAndRadius( 389 | Rect.fromLTWH(0, 0, size.width, size.height), barRadius)); 390 | canvas.drawPath(backgroundPath, _paintBackground); 391 | canvas.clipPath(backgroundPath); 392 | 393 | if (maskFilter != null) { 394 | _paintLineBorder.maskFilter = maskFilter; 395 | _paintLine.maskFilter = maskFilter; 396 | } 397 | 398 | if (linearGradientBackgroundColor != null) { 399 | Offset shaderEndPoint = 400 | clipLinearGradient ? Offset.zero : Offset(size.width, size.height); 401 | _paintBackground.shader = linearGradientBackgroundColor 402 | ?.createShader(Rect.fromPoints(Offset.zero, shaderEndPoint)); 403 | } 404 | 405 | // Then draw progress line 406 | final xProgress = size.width * progress; 407 | Path linePath = Path(); 408 | Path linePathBorder = Path(); 409 | double factor = progressBorderColor != null ? 2 : 0; 410 | double correction = factor * 2; //Left and right or top an down 411 | if (isRTL) { 412 | if (linearGradient != null) { 413 | _paintLineBorder.shader = 414 | _createGradientShaderRightToLeft(size, xProgress); 415 | _paintLine.shader = _createGradientShaderRightToLeft(size, xProgress); 416 | } 417 | linePath.addRRect(RRect.fromRectAndRadius( 418 | Rect.fromLTWH( 419 | size.width - size.width * progress, 0, xProgress, size.height), 420 | barRadius)); 421 | } else { 422 | if (linearGradient != null) { 423 | _paintLineBorder.shader = 424 | _createGradientShaderLeftToRight(size, xProgress); 425 | _paintLine.shader = _createGradientShaderLeftToRight(size, xProgress); 426 | } 427 | if (progressBorderColor != null) { 428 | linePathBorder.addRRect(RRect.fromRectAndRadius( 429 | Rect.fromLTWH(0, 0, xProgress, size.height), barRadius)); 430 | } 431 | linePath.addRRect(RRect.fromRectAndRadius( 432 | Rect.fromLTWH( 433 | factor, factor, xProgress - correction, size.height - correction), 434 | barRadius)); 435 | } 436 | if (progressBorderColor != null) { 437 | canvas.drawPath(linePathBorder, _paintLineBorder); 438 | } 439 | canvas.drawPath(linePath, _paintLine); 440 | } 441 | 442 | Shader _createGradientShaderRightToLeft(Size size, double xProgress) { 443 | Offset shaderEndPoint = 444 | clipLinearGradient ? Offset.zero : Offset(xProgress, size.height); 445 | return linearGradient!.createShader( 446 | Rect.fromPoints( 447 | Offset(size.width, size.height), 448 | shaderEndPoint, 449 | ), 450 | ); 451 | } 452 | 453 | Shader _createGradientShaderLeftToRight(Size size, double xProgress) { 454 | Offset shaderEndPoint = clipLinearGradient 455 | ? Offset(size.width, size.height) 456 | : Offset(xProgress, size.height); 457 | return linearGradient!.createShader( 458 | Rect.fromPoints( 459 | Offset.zero, 460 | shaderEndPoint, 461 | ), 462 | ); 463 | } 464 | 465 | @override 466 | bool shouldRepaint(CustomPainter oldDelegate) => true; 467 | } 468 | -------------------------------------------------------------------------------- /lib/multi_segment_linear_indicator.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | /// A multi-segment linear progress indicator that displays three segments with different colors and styles. 4 | /// One segment can optionally display stripes. 5 | /// 6 | /// The total of all segments must be less than or equal to 1.0 (100%). 7 | /// Each segment percentage must be between 0.0 and 1.0. 8 | /// 9 | /// Example: 10 | /// ```dart 11 | /// MultiSegmentLinearIndicator( 12 | /// segments: [ 13 | /// SegmentLinearIndicator(percent: 0.3, color: Colors.red, enableStripes: true), 14 | /// SegmentLinearIndicator(percent: 0.4, color: Colors.blue), 15 | /// SegmentLinearIndicator(percent: 0.3, color: Colors.green), 16 | /// ], 17 | /// lineHeight: 20, 18 | /// barRadius: Radius.circular(10), 19 | /// animation: true, 20 | /// enableStripes: [1, 2], // Enable stripes for first and second segments 21 | /// ) 22 | /// ``` 23 | 24 | /// Represents a segment in the linear indicator with a percentage and color. 25 | class SegmentLinearIndicator { 26 | final double percent; 27 | final Color color; 28 | final bool enableStripes; 29 | 30 | SegmentLinearIndicator({ 31 | required this.percent, 32 | required this.color, 33 | this.enableStripes = false, 34 | }); 35 | } 36 | 37 | class MultiSegmentLinearIndicator extends StatefulWidget { 38 | /// List of segments to display in the indicator. 39 | final List segments; 40 | 41 | /// Height of the progress bar 42 | final double lineHeight; 43 | 44 | /// Optional width of the progress bar. If null, fills the parent width 45 | final double? width; 46 | 47 | /// Border radius of the progress bar 48 | final Radius? barRadius; 49 | 50 | /// Padding around the progress bar 51 | final EdgeInsets padding; 52 | 53 | /// Whether to animate changes in the progress values 54 | final bool animation; 55 | 56 | /// Duration of the animation in milliseconds 57 | final int animationDuration; 58 | 59 | /// Animation curve to use 60 | final Curve curve; 61 | 62 | /// Whether to animate from the last percentage value or from 0 63 | final bool animateFromLastPercent; 64 | 65 | /// Callback when animation completes 66 | final VoidCallback? onAnimationEnd; 67 | 68 | /// Creates a multi-segment linear progress indicator. 69 | /// 70 | /// The sum of all segment percentages must be less than or equal to 1.0. 71 | MultiSegmentLinearIndicator({ 72 | Key? key, 73 | required this.segments, 74 | this.lineHeight = 5.0, 75 | this.width, 76 | this.barRadius, 77 | this.padding = const EdgeInsets.symmetric(horizontal: 10.0), 78 | this.animation = false, 79 | this.animationDuration = 500, 80 | this.curve = Curves.linear, 81 | this.animateFromLastPercent = false, 82 | this.onAnimationEnd, 83 | }) : super(key: key) { 84 | final sum = segments.fold( 85 | 0.0, 86 | (sum, segment) => sum + segment.percent, 87 | ); 88 | if (sum > 1.0) { 89 | throw Exception( 90 | 'The sum of all segment percentages must be less than or equal to 1.0, but got $sum', 91 | ); 92 | } 93 | } 94 | 95 | @override 96 | State createState() => 97 | _MultiSegmentLinearIndicatorState(); 98 | } 99 | 100 | class _MultiSegmentLinearIndicatorState 101 | extends State 102 | with SingleTickerProviderStateMixin { 103 | AnimationController? _animationController; 104 | late List> _segmentAnimations; 105 | late List _segmentPercents; 106 | 107 | @override 108 | void dispose() { 109 | _animationController?.dispose(); 110 | super.dispose(); 111 | } 112 | 113 | @override 114 | void initState() { 115 | super.initState(); 116 | _segmentPercents = List.filled(widget.segments.length, 0.0); 117 | 118 | if (!widget.animation) { 119 | for (int i = 0; i < widget.segments.length; i++) { 120 | _segmentPercents[i] = widget.segments[i].percent; 121 | } 122 | return; 123 | } 124 | 125 | _animationController = AnimationController( 126 | vsync: this, 127 | duration: Duration(milliseconds: widget.animationDuration), 128 | ); 129 | 130 | _setupAnimations(); 131 | 132 | _animationController!.addStatusListener((status) { 133 | if (widget.onAnimationEnd != null && 134 | status == AnimationStatus.completed) { 135 | widget.onAnimationEnd!(); 136 | } 137 | }); 138 | 139 | _animationController!.forward(); 140 | } 141 | 142 | void _setupAnimations() { 143 | _segmentAnimations = List.generate(widget.segments.length, (index) { 144 | final double start = 145 | widget.animateFromLastPercent ? _segmentPercents[index] : 0.0; 146 | 147 | return Tween( 148 | begin: start, 149 | end: widget.segments[index].percent, 150 | ).animate(CurvedAnimation( 151 | parent: _animationController!, 152 | curve: widget.curve, 153 | )) 154 | ..addListener(() { 155 | setState(() { 156 | _segmentPercents[index] = _segmentAnimations[index].value; 157 | }); 158 | }); 159 | }); 160 | } 161 | 162 | void _checkIfNeedCancelAnimation(MultiSegmentLinearIndicator oldWidget) { 163 | if (oldWidget.animation && 164 | !widget.animation && 165 | _animationController != null) { 166 | _animationController!.stop(); 167 | } 168 | } 169 | 170 | @override 171 | void didUpdateWidget(MultiSegmentLinearIndicator oldWidget) { 172 | super.didUpdateWidget(oldWidget); 173 | 174 | if (widget.animation && 175 | !_areSegmentsEqual(oldWidget.segments, widget.segments)) { 176 | _animationController!.duration = 177 | Duration(milliseconds: widget.animationDuration); 178 | _setupAnimations(); 179 | _animationController!.forward(from: 0.0); 180 | } else if (!widget.animation) { 181 | for (int i = 0; i < widget.segments.length; i++) { 182 | _segmentPercents[i] = widget.segments[i].percent; 183 | } 184 | } 185 | 186 | _checkIfNeedCancelAnimation(oldWidget); 187 | } 188 | 189 | bool _areSegmentsEqual( 190 | List a, List b) { 191 | if (a.length != b.length) return false; 192 | 193 | for (int i = 0; i < a.length; i++) { 194 | if (a[i].percent != b[i].percent) return false; 195 | } 196 | 197 | return true; 198 | } 199 | 200 | @override 201 | Widget build(BuildContext context) { 202 | return Container( 203 | width: widget.width, 204 | padding: widget.padding, 205 | child: CustomPaint( 206 | painter: _MultiSegmentPainter( 207 | segments: widget.segments, 208 | segmentPercents: _segmentPercents, 209 | barRadius: widget.barRadius ?? Radius.zero, 210 | ), 211 | child: SizedBox( 212 | height: widget.lineHeight, 213 | ), 214 | ), 215 | ); 216 | } 217 | } 218 | 219 | /// Custom painter for drawing the multi-segment progress bar 220 | class _MultiSegmentPainter extends CustomPainter { 221 | final List segments; 222 | final List segmentPercents; 223 | final Radius barRadius; 224 | 225 | _MultiSegmentPainter({ 226 | required this.segments, 227 | required this.segmentPercents, 228 | required this.barRadius, 229 | }); 230 | 231 | @override 232 | void paint(Canvas canvas, Size size) { 233 | double startX = 0.0; 234 | 235 | for (int i = 0; i < segments.length; i++) { 236 | final segmentWidth = size.width * segmentPercents[i]; 237 | final segmentPaint = Paint() 238 | ..color = segments[i].color 239 | ..style = PaintingStyle.fill; 240 | 241 | final segmentPath = Path(); 242 | if (i == 0 && segmentPercents[i] == 1.0) { 243 | // Full width with full border radius 244 | segmentPath.addRRect(RRect.fromRectAndRadius( 245 | Rect.fromLTWH(startX, 0, segmentWidth, size.height), 246 | barRadius, 247 | )); 248 | } else if (i == 0) { 249 | // Round only the left side 250 | segmentPath.addRRect(RRect.fromRectAndCorners( 251 | Rect.fromLTWH(startX, 0, segmentWidth, size.height), 252 | topLeft: barRadius, 253 | bottomLeft: barRadius, 254 | )); 255 | } else if (i == segments.length - 1) { 256 | // Round only the right side for the last segment 257 | segmentPath.addRRect(RRect.fromRectAndCorners( 258 | Rect.fromLTWH(startX, 0, segmentWidth, size.height), 259 | topRight: barRadius, 260 | bottomRight: barRadius, 261 | )); 262 | } else { 263 | // No rounding for middle segments 264 | segmentPath.addRect( 265 | Rect.fromLTWH(startX, 0, segmentWidth, size.height), 266 | ); 267 | } 268 | canvas.drawPath(segmentPath, segmentPaint); 269 | 270 | // Draw stripes if enabled for this segment 271 | if (segments[i].enableStripes) { 272 | _drawStripes(canvas, startX, segmentWidth, size.height); 273 | } 274 | 275 | startX += segmentWidth; 276 | } 277 | } 278 | 279 | void _drawStripes(Canvas canvas, double startX, double width, double height) { 280 | final stripePaint = Paint() 281 | ..color = Colors.white.withOpacity(0.3) 282 | ..style = PaintingStyle.stroke 283 | ..strokeWidth = 2; 284 | 285 | const stripeSpacing = 8.0; 286 | 287 | canvas.save(); 288 | canvas.clipRect(Rect.fromLTWH(startX, 0, width, height)); 289 | 290 | for (double x = startX - height; 291 | x < startX + width + height; 292 | x += stripeSpacing) { 293 | canvas.drawLine( 294 | Offset(x, height), 295 | Offset(x + height, 0), 296 | stripePaint, 297 | ); 298 | } 299 | canvas.restore(); 300 | } 301 | 302 | @override 303 | bool shouldRepaint(covariant CustomPainter oldDelegate) => true; 304 | } 305 | -------------------------------------------------------------------------------- /lib/percent_indicator.dart: -------------------------------------------------------------------------------- 1 | library percent_indicator; 2 | 3 | export 'circular_percent_indicator.dart'; 4 | export 'linear_percent_indicator.dart'; 5 | -------------------------------------------------------------------------------- /percent_indicator.iml: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.18.0" 44 | fake_async: 45 | dependency: transitive 46 | description: 47 | name: fake_async 48 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.3.1" 52 | flutter: 53 | dependency: "direct main" 54 | description: flutter 55 | source: sdk 56 | version: "0.0.0" 57 | flutter_test: 58 | dependency: "direct dev" 59 | description: flutter 60 | source: sdk 61 | version: "0.0.0" 62 | leak_tracker: 63 | dependency: transitive 64 | description: 65 | name: leak_tracker 66 | sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" 67 | url: "https://pub.dev" 68 | source: hosted 69 | version: "10.0.5" 70 | leak_tracker_flutter_testing: 71 | dependency: transitive 72 | description: 73 | name: leak_tracker_flutter_testing 74 | sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" 75 | url: "https://pub.dev" 76 | source: hosted 77 | version: "3.0.5" 78 | leak_tracker_testing: 79 | dependency: transitive 80 | description: 81 | name: leak_tracker_testing 82 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 83 | url: "https://pub.dev" 84 | source: hosted 85 | version: "3.0.1" 86 | matcher: 87 | dependency: transitive 88 | description: 89 | name: matcher 90 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 91 | url: "https://pub.dev" 92 | source: hosted 93 | version: "0.12.16+1" 94 | material_color_utilities: 95 | dependency: transitive 96 | description: 97 | name: material_color_utilities 98 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 99 | url: "https://pub.dev" 100 | source: hosted 101 | version: "0.11.1" 102 | meta: 103 | dependency: transitive 104 | description: 105 | name: meta 106 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 107 | url: "https://pub.dev" 108 | source: hosted 109 | version: "1.15.0" 110 | path: 111 | dependency: transitive 112 | description: 113 | name: path 114 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 115 | url: "https://pub.dev" 116 | source: hosted 117 | version: "1.9.0" 118 | sky_engine: 119 | dependency: transitive 120 | description: flutter 121 | source: sdk 122 | version: "0.0.99" 123 | source_span: 124 | dependency: transitive 125 | description: 126 | name: source_span 127 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 128 | url: "https://pub.dev" 129 | source: hosted 130 | version: "1.10.0" 131 | stack_trace: 132 | dependency: transitive 133 | description: 134 | name: stack_trace 135 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 136 | url: "https://pub.dev" 137 | source: hosted 138 | version: "1.11.1" 139 | stream_channel: 140 | dependency: transitive 141 | description: 142 | name: stream_channel 143 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 144 | url: "https://pub.dev" 145 | source: hosted 146 | version: "2.1.2" 147 | string_scanner: 148 | dependency: transitive 149 | description: 150 | name: string_scanner 151 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 152 | url: "https://pub.dev" 153 | source: hosted 154 | version: "1.2.0" 155 | term_glyph: 156 | dependency: transitive 157 | description: 158 | name: term_glyph 159 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 160 | url: "https://pub.dev" 161 | source: hosted 162 | version: "1.2.1" 163 | test_api: 164 | dependency: transitive 165 | description: 166 | name: test_api 167 | sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" 168 | url: "https://pub.dev" 169 | source: hosted 170 | version: "0.7.2" 171 | vector_math: 172 | dependency: transitive 173 | description: 174 | name: vector_math 175 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 176 | url: "https://pub.dev" 177 | source: hosted 178 | version: "2.1.4" 179 | vm_service: 180 | dependency: transitive 181 | description: 182 | name: vm_service 183 | sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" 184 | url: "https://pub.dev" 185 | source: hosted 186 | version: "14.2.5" 187 | sdks: 188 | dart: ">=3.3.0 <4.0.0" 189 | flutter: ">=3.18.0-18.0.pre.54" 190 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: percent_indicator 2 | description: Library that allows you to display progress widgets based on percentage, can be Circular or Linear, you can also customize it to your needs. 3 | version: 4.2.5 4 | homepage: https://www.diegoveloper.com 5 | repository: https://github.com/diegoveloper/flutter_percent_indicator/ 6 | 7 | dependencies: 8 | flutter: 9 | sdk: flutter 10 | 11 | environment: 12 | sdk: ">=2.12.0 <4.0.0" 13 | flutter: ">=2.12.0" 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | 19 | screenshots: 20 | - description: Circular Percent Indicator 21 | path: screenshots/circular_percent_indicator.png 22 | - description: Linear Percent Indicator 23 | path: screenshots/linear_percent_indicator.png 24 | - description: Circular Percent Indicator Gif 25 | path: screenshots/circular_percent_indicator.gif 26 | - description: Linear Percent Indicator Gif 27 | path: screenshots/linear_percent_indicator.gif 28 | 29 | flutter: 30 | -------------------------------------------------------------------------------- /screenshots/circular_percent_indicator.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/screenshots/circular_percent_indicator.gif -------------------------------------------------------------------------------- /screenshots/circular_percent_indicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/screenshots/circular_percent_indicator.png -------------------------------------------------------------------------------- /screenshots/linear_percent_indicator.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/screenshots/linear_percent_indicator.gif -------------------------------------------------------------------------------- /screenshots/linear_percent_indicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diegoveloper/flutter_percent_indicator/f390f4c08ad150a56104a3250d94875919b7d94f/screenshots/linear_percent_indicator.png -------------------------------------------------------------------------------- /test/percent_indicator_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:percent_indicator/percent_indicator.dart'; 3 | import 'package:flutter_test/flutter_test.dart'; 4 | 5 | void main() { 6 | testWidgets('testing circular percent indicator widget', 7 | (WidgetTester tester) async { 8 | CircularPercentIndicator localWidget = 9 | CircularPercentIndicator(radius: 20.0); 10 | await tester.pumpWidget(MaterialApp(home: localWidget)); 11 | expect(localWidget.percent, 0.0); 12 | }); 13 | 14 | testWidgets('testing linear percent indicator widget', 15 | (WidgetTester tester) async { 16 | LinearPercentIndicator localWidget = LinearPercentIndicator( 17 | width: 100.0, 18 | ); 19 | await tester.pumpWidget(MaterialApp(home: localWidget)); 20 | expect(localWidget.percent, 0.0); 21 | }); 22 | 23 | testWidgets('show center widget in linear percent indicator widget', 24 | (WidgetTester tester) async { 25 | LinearPercentIndicator localWidget = LinearPercentIndicator( 26 | width: 100.0, 27 | center: Text('linear'), 28 | ); 29 | 30 | await tester.pumpWidget(MaterialApp(home: localWidget)); 31 | 32 | expect(find.byType(Center), findsOne); 33 | }); 34 | 35 | testWidgets('show center widget in circular percent indicator widget', 36 | (WidgetTester tester) async { 37 | CircularPercentIndicator localWidget = CircularPercentIndicator( 38 | radius: 20.0, 39 | center: Text('circular'), 40 | ); 41 | 42 | await tester.pumpWidget(MaterialApp(home: localWidget)); 43 | 44 | expect(find.byType(Center), findsOne); 45 | }); 46 | 47 | testWidgets('show header widget in circular percent indicator widget', 48 | (WidgetTester tester) async { 49 | CircularPercentIndicator localWidget = CircularPercentIndicator( 50 | radius: 20.0, 51 | header: Text('header'), 52 | ); 53 | 54 | await tester.pumpWidget(MaterialApp(home: localWidget)); 55 | 56 | final header = find.descendant( 57 | of: find.byType(Column), 58 | matching: find.byType(Text), 59 | ); 60 | 61 | final container = find.descendant( 62 | of: find.byType(Column), 63 | matching: find.byType(Container), 64 | ); 65 | expect(header, findsExactly(1)); 66 | expect(container, findsExactly(1)); 67 | 68 | expect(tester.getTopLeft(header).dy, 69 | lessThan(tester.getTopLeft(container).dy)); 70 | }); 71 | 72 | testWidgets('show footer widget in circular percent indicator widget', 73 | (WidgetTester tester) async { 74 | CircularPercentIndicator localWidget = CircularPercentIndicator( 75 | radius: 20.0, 76 | footer: Text('footer'), 77 | ); 78 | 79 | await tester.pumpWidget(MaterialApp(home: localWidget)); 80 | 81 | final header = find.descendant( 82 | of: find.byType(Column), 83 | matching: find.byType(Text), 84 | ); 85 | 86 | final container = find.descendant( 87 | of: find.byType(Column), 88 | matching: find.byType(Container), 89 | ); 90 | expect(header, findsExactly(1)); 91 | expect(container, findsExactly(1)); 92 | 93 | expect(tester.getTopLeft(header).dy, 94 | greaterThan(tester.getTopLeft(container).dy)); 95 | }); 96 | 97 | testWidgets('apply fillColor in circular percent indicator widget', 98 | (WidgetTester tester) async { 99 | CircularPercentIndicator localWidget = CircularPercentIndicator( 100 | radius: 20.0, 101 | fillColor: Colors.red, 102 | ); 103 | 104 | await tester.pumpWidget(MaterialApp(home: localWidget)); 105 | 106 | final material = find.byType(Material); 107 | 108 | expect(material, findsOne); 109 | 110 | expect((tester.firstWidget(material) as Material).color, Colors.red); 111 | }); 112 | 113 | testWidgets('apply fillColor in linear percent indicator widget', 114 | (WidgetTester tester) async { 115 | LinearPercentIndicator localWidget = LinearPercentIndicator( 116 | width: 100.0, 117 | fillColor: Colors.red, 118 | ); 119 | 120 | await tester.pumpWidget(MaterialApp(home: localWidget)); 121 | 122 | final container = find.ancestor( 123 | of: find.byType(Row), 124 | matching: find.byType(Container), 125 | ); 126 | 127 | expect((tester.firstWidget(container) as Container).color, Colors.red); 128 | }); 129 | 130 | testWidgets('testing percent animation in circular percent indicator widget', 131 | (WidgetTester tester) async { 132 | final animationController = AnimationController( 133 | vsync: tester, 134 | duration: const Duration(milliseconds: 200), 135 | ); 136 | 137 | await tester.pumpWidget( 138 | MaterialApp( 139 | home: AnimatedBuilder( 140 | animation: animationController, 141 | builder: (context, child) { 142 | return CircularPercentIndicator( 143 | radius: 100.0, 144 | fillColor: Colors.red, 145 | percent: animationController.value, 146 | ); 147 | }, 148 | ), 149 | ), 150 | ); 151 | 152 | animationController.forward(); 153 | 154 | await tester.pump(const Duration(milliseconds: 200)); 155 | 156 | await tester.pumpAndSettle(); 157 | 158 | final linear = find.byType(CircularPercentIndicator); 159 | 160 | expect((tester.firstWidget(linear) as CircularPercentIndicator).percent, 1.0); 161 | 162 | animationController.dispose(); 163 | }); 164 | testWidgets('testing percent animation in linear percent indicator widget', 165 | (WidgetTester tester) async { 166 | final animationController = AnimationController( 167 | vsync: tester, 168 | duration: const Duration(milliseconds: 200), 169 | ); 170 | 171 | await tester.pumpWidget( 172 | MaterialApp( 173 | home: AnimatedBuilder( 174 | animation: animationController, 175 | builder: (context, child) { 176 | return LinearPercentIndicator( 177 | width: 100.0, 178 | fillColor: Colors.red, 179 | percent: animationController.value, 180 | ); 181 | }, 182 | ), 183 | ), 184 | ); 185 | 186 | animationController.forward(); 187 | 188 | await tester.pump(const Duration(milliseconds: 200)); 189 | 190 | await tester.pumpAndSettle(); 191 | 192 | final linear = find.byType(LinearPercentIndicator); 193 | 194 | expect((tester.firstWidget(linear) as LinearPercentIndicator).percent, 1.0); 195 | 196 | animationController.dispose(); 197 | }); 198 | } 199 | --------------------------------------------------------------------------------