├── preview.gif ├── lib ├── fade_out_particle.dart └── src │ ├── particle.dart │ ├── render_object.dart │ ├── extensions.dart │ ├── render_box.dart │ └── fade_out_particle.dart ├── example ├── android │ ├── gradle.properties │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── 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 │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ └── example │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── settings.gradle │ └── build.gradle ├── pubspec.yaml ├── README.md ├── .gitignore ├── analysis_options.yaml ├── .metadata └── lib │ └── main.dart ├── analysis_options.yaml ├── CHANGELOG.md ├── .metadata ├── pubspec.yaml ├── README.md ├── .gitignore ├── test ├── fade_out_particle_wrapper.dart └── widget_test.dart └── LICENSE /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoomanmmd/fade_out_particle/HEAD/preview.gif -------------------------------------------------------------------------------- /lib/fade_out_particle.dart: -------------------------------------------------------------------------------- 1 | library fade_out_particle; 2 | 3 | export 'src/fade_out_particle.dart'; 4 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoomanmmd/fade_out_particle/HEAD/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/hoomanmmd/fade_out_particle/HEAD/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/hoomanmmd/fade_out_particle/HEAD/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/hoomanmmd/fade_out_particle/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoomanmmd/fade_out_particle/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.2.2 2 | * Fix deprecated api calls 3 | 4 | ## 1.2.1 5 | * Fix need paint 6 | 7 | ## 1.2.0 8 | * Improve Animation 9 | * fix leakage 10 | 11 | ## 1.1.1 12 | * Improve Performance 13 | 14 | ## 1.1.0 15 | * Add onAnimationEnd 16 | 17 | ## 1.0.0 18 | * Initial release 19 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 7 | -------------------------------------------------------------------------------- /.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: fb57da5f945d02ef4f98dfd9409a72b7cce74268 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /lib/src/particle.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | class Particle extends LinkedListEntry { 4 | Particle({ 5 | required this.cx, 6 | required this.cy, 7 | required this.radius, 8 | required this.rgbaColor, 9 | required this.pathType, 10 | }); 11 | 12 | final int cx; 13 | final int cy; 14 | final double radius; 15 | final int rgbaColor; 16 | final int pathType; 17 | } 18 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: example 2 | description: Example for fade out particle. 3 | publish_to: 'none' 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: ">=2.17.1 <4.0.0" 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | 13 | fade_out_particle: 14 | path: ../ 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | flutter_lints: ^2.0.0 20 | 21 | flutter: 22 | uses-material-design: true -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: fade_out_particle 2 | description: Fade out particle effect for disappearing views like Text and Icon 3 | version: 1.2.2 4 | homepage: https://github.com/hoomanmmd/fade_out_particle 5 | 6 | environment: 7 | sdk: ">=2.17.1 <4.0.0" 8 | flutter: ">=1.17.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | flutter_lints: ^2.0.0 18 | 19 | flutter: -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FadeOutParticle is an animation for disappearing views like Text and Icon 2 |

3 | FadeOutParticle 4 |

5 | 6 | # Installation 7 | Add this line to your pubspec: 8 | ```yaml 9 | dependencies: 10 | fade_out_particle: ^1.2.1 11 | ``` 12 | 13 | ## Usage 14 | ```dart 15 | FadeOutParticle( 16 | disappear: true, 17 | child: Text('Fade out Particle'), 18 | ) 19 | ``` 20 | 21 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | Example for fade out particle. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /lib/src/render_object.dart: -------------------------------------------------------------------------------- 1 | part of 'fade_out_particle.dart'; 2 | 3 | @immutable 4 | class _RenderObject extends SingleChildRenderObjectWidget { 5 | final double progress; 6 | final LinkedList? particles; 7 | 8 | const _RenderObject({ 9 | required this.progress, 10 | required this.particles, 11 | super.child, 12 | }); 13 | 14 | @override 15 | _RenderBox createRenderObject(BuildContext context) => 16 | _RenderBox(progress, particles); 17 | 18 | @override 19 | void updateRenderObject(BuildContext context, _RenderBox renderBox) { 20 | renderBox.progress = progress; 21 | renderBox.particles = particles; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | /pubspec.lock 27 | /preview.gif 28 | **/doc/api/ 29 | .dart_tool/ 30 | .packages 31 | build/ 32 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.6.21' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.1.2' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Web related 36 | lib/generated_plugin_registrant.dart 37 | 38 | # Symbolication related 39 | app.*.symbols 40 | 41 | # Obfuscation related 42 | app.*.map.json 43 | 44 | # Android Studio will place build artifacts here 45 | /android/app/debug 46 | /android/app/profile 47 | /android/app/release 48 | 49 | pubspec.lock -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /test/fade_out_particle_wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class FadeOutParticleWrapper extends StatefulWidget { 4 | const FadeOutParticleWrapper({ 5 | required this.widgetBuilder, 6 | super.key, 7 | }); 8 | 9 | final Widget Function(Widget, bool) widgetBuilder; 10 | 11 | @override 12 | State createState() => _FadeOutParticleWrapperState(); 13 | } 14 | 15 | class _FadeOutParticleWrapperState extends State { 16 | bool _showFirst = true; 17 | 18 | @override 19 | Widget build(BuildContext context) { 20 | return Column( 21 | children: [ 22 | widget.widgetBuilder( 23 | Container( 24 | key: const ValueKey('1'), 25 | color: Colors.black, 26 | width: 100, 27 | height: 100, 28 | ), 29 | _showFirst, 30 | ), 31 | ElevatedButton( 32 | onPressed: _toggle, 33 | child: const Text('Animate'), 34 | ), 35 | ], 36 | ); 37 | } 38 | 39 | void _toggle() { 40 | setState(() => _showFirst = !_showFirst); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/src/extensions.dart: -------------------------------------------------------------------------------- 1 | part of 'fade_out_particle.dart'; 2 | 3 | extension _IntExtensions on int { 4 | Color withOpacity(double opacity) { 5 | final resultOpacity = (opacity * (this & 0xFF) * 0.7).toInt(); 6 | return Color((this >> 8 & 0x00FFFFFF) | (resultOpacity << 24)); 7 | } 8 | 9 | bool get isTransparent => (this & 0xFF) == 0; 10 | } 11 | 12 | extension _DoubleExtensions on double { 13 | double interpolateYAxis(int type) { 14 | switch (type) { 15 | case 0: 16 | return this * this * ((1.7 + 1) * this - 1.7); 17 | case 1: 18 | return math.sqrt(1.1 - (this - 1) * (this - 1)); 19 | case 2: 20 | return math.pow(this, 2.0).toDouble(); 21 | case 3: 22 | return 1 - math.cos(this * math.pi / 2.0); 23 | case 4: 24 | return math.pow(this, 3.0).toDouble(); 25 | default: 26 | return math.pow(2.0, 10.0 * (this - 1)).toDouble(); 27 | } 28 | } 29 | 30 | double particleProgress(double width) => 31 | math.min(1, this / width.limitedAnimationWidth); 32 | 33 | double get limitedAnimationWidth => math.min(this, 128); 34 | } 35 | 36 | extension _ByteDataExtensions on ByteData { 37 | int maybeNonTransparentColor(int i, int j, int width, int space) { 38 | final offset = (i - space + j * width) * 4; 39 | if (space == 0 || getInt8(offset + 3) != 0) { 40 | return getInt32(offset); 41 | } 42 | return maybeNonTransparentColor(i, j, width, space - 1); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at 17 | # https://dart-lang.github.io/linter/lints/index.html. 18 | # 19 | # Instead of disabling a lint rule for the entire project in the 20 | # section below, it can also be suppressed for a single line of code 21 | # or a specific dart file by using the `// ignore: name_of_lint` and 22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 23 | # producing the lint. 24 | rules: 25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 27 | 28 | # Additional information about this file can be found at 29 | # https://dart.dev/guides/language/analysis-options 30 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | 15 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:fade_out_particle/fade_out_particle.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_test/flutter_test.dart'; 6 | 7 | import 'fade_out_particle_wrapper.dart'; 8 | 9 | void main() { 10 | testWidgets('Disappear widget', (WidgetTester tester) async { 11 | final widgetBuilder = (Widget child, bool disappear) => FadeOutParticle( 12 | duration: const Duration(microseconds: 10), 13 | disappear: disappear, 14 | child: child, 15 | ); 16 | 17 | await tester.pumpWidget( 18 | MaterialApp( 19 | home: FadeOutParticleWrapper(widgetBuilder: widgetBuilder), 20 | ), 21 | ); 22 | 23 | expect(find.byKey(const ValueKey('1')), findsOneWidget); 24 | 25 | await tester.tap(find.text('Animate')); 26 | await tester.pump(); 27 | 28 | expect(find.byKey(const ValueKey('1')), findsOneWidget); 29 | 30 | await tester.pumpAndSettle(); 31 | 32 | expect(find.byKey(const ValueKey('1')), findsNothing); 33 | }); 34 | 35 | testWidgets('onAnimationEnd callback is called after animation ends', 36 | (tester) async { 37 | final completer = Completer(); 38 | 39 | final widgetBuilder = (Widget child, bool disappear) => FadeOutParticle( 40 | duration: const Duration(microseconds: 10), 41 | disappear: disappear, 42 | onAnimationEnd: () => completer.complete(), 43 | child: child, 44 | ); 45 | 46 | await tester.pumpWidget( 47 | MaterialApp( 48 | home: FadeOutParticleWrapper(widgetBuilder: widgetBuilder), 49 | ), 50 | ); 51 | 52 | expect(completer.isCompleted, isFalse); 53 | 54 | await tester.pumpAndSettle(); 55 | 56 | expect(completer.isCompleted, isTrue); 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /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. 5 | 6 | version: 7 | revision: 676cefaaff197f27424942307668886253e1ec35 8 | channel: stable 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 676cefaaff197f27424942307668886253e1ec35 17 | base_revision: 676cefaaff197f27424942307668886253e1ec35 18 | - platform: android 19 | create_revision: 676cefaaff197f27424942307668886253e1ec35 20 | base_revision: 676cefaaff197f27424942307668886253e1ec35 21 | - platform: ios 22 | create_revision: 676cefaaff197f27424942307668886253e1ec35 23 | base_revision: 676cefaaff197f27424942307668886253e1ec35 24 | - platform: linux 25 | create_revision: 676cefaaff197f27424942307668886253e1ec35 26 | base_revision: 676cefaaff197f27424942307668886253e1ec35 27 | - platform: macos 28 | create_revision: 676cefaaff197f27424942307668886253e1ec35 29 | base_revision: 676cefaaff197f27424942307668886253e1ec35 30 | - platform: web 31 | create_revision: 676cefaaff197f27424942307668886253e1ec35 32 | base_revision: 676cefaaff197f27424942307668886253e1ec35 33 | - platform: windows 34 | create_revision: 676cefaaff197f27424942307668886253e1ec35 35 | base_revision: 676cefaaff197f27424942307668886253e1ec35 36 | 37 | # User provided section 38 | 39 | # List of Local paths (relative to this file) that should be 40 | # ignored by the migrate tool. 41 | # 42 | # Files that are not part of the templates will be ignored by default. 43 | unmanaged_files: 44 | - 'lib/main.dart' 45 | - 'ios/Runner.xcodeproj/project.pbxproj' 46 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:fade_out_particle/fade_out_particle.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | void main() => runApp(const MyApp()); 5 | 6 | class MyApp extends StatelessWidget { 7 | const MyApp({super.key}); 8 | 9 | @override 10 | Widget build(BuildContext context) { 11 | return MaterialApp( 12 | title: 'FadeOutParticle Demo', 13 | theme: ThemeData(primarySwatch: Colors.blue), 14 | home: const HomePage(), 15 | ); 16 | } 17 | } 18 | 19 | class HomePage extends StatefulWidget { 20 | const HomePage({super.key}); 21 | 22 | @override 23 | State createState() => _HomePageState(); 24 | } 25 | 26 | class _HomePageState extends State { 27 | bool _disappear = true; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return Scaffold( 32 | body: Center( 33 | child: Column( 34 | mainAxisSize: MainAxisSize.min, 35 | children: [ 36 | FadeOutParticle( 37 | disappear: _disappear, 38 | duration: const Duration(milliseconds: 3000), 39 | child: Row( 40 | mainAxisSize: MainAxisSize.min, 41 | children: [ 42 | Icon( 43 | Icons.flutter_dash, 44 | size: 52, 45 | color: Theme.of(context).primaryColorDark, 46 | ), 47 | const SizedBox(width: 8), 48 | Text( 49 | 'Fade out Particle', 50 | style: Theme.of(context).textTheme.headlineSmall?.copyWith( 51 | fontWeight: FontWeight.w900, 52 | ), 53 | ), 54 | ], 55 | ), 56 | onAnimationEnd: () => print('animation ended'), 57 | ), 58 | const SizedBox(height: 150), 59 | OutlinedButton( 60 | onPressed: () => setState(() => _disappear = !_disappear), 61 | child: Text(_disappear ? 'Reset' : 'Start'), 62 | ), 63 | ], 64 | ), 65 | ), 66 | ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | ndkVersion flutter.ndkVersion 31 | 32 | compileOptions { 33 | sourceCompatibility JavaVersion.VERSION_1_8 34 | targetCompatibility JavaVersion.VERSION_1_8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | 41 | sourceSets { 42 | main.java.srcDirs += 'src/main/kotlin' 43 | } 44 | 45 | defaultConfig { 46 | applicationId "com.example.example" 47 | // You can update the following values to match your application needs. 48 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. 49 | minSdkVersion flutter.minSdkVersion 50 | targetSdkVersion flutter.targetSdkVersion 51 | versionCode flutterVersionCode.toInteger() 52 | versionName flutterVersionName 53 | } 54 | 55 | buildTypes { 56 | release { 57 | // Signing with the debug keys for now, so `flutter run --release` works. 58 | signingConfig signingConfigs.debug 59 | } 60 | } 61 | } 62 | 63 | flutter { 64 | source '../..' 65 | } 66 | 67 | dependencies { 68 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 69 | } 70 | -------------------------------------------------------------------------------- /lib/src/render_box.dart: -------------------------------------------------------------------------------- 1 | part of 'fade_out_particle.dart'; 2 | 3 | class _RenderBox extends RenderProxyBox { 4 | double _progress; 5 | LinkedList? _particles; 6 | 7 | _RenderBox(this._progress, this._particles); 8 | 9 | @override 10 | bool get alwaysNeedsCompositing => child != null; 11 | 12 | set progress(double newValue) { 13 | if (newValue == _progress) { 14 | return; 15 | } 16 | _progress = newValue; 17 | markNeedsPaint(); 18 | } 19 | 20 | set particles(LinkedList? newValue) { 21 | if (newValue == _particles) { 22 | return; 23 | } 24 | _particles = newValue; 25 | markNeedsPaint(); 26 | } 27 | 28 | @override 29 | void paint(PaintingContext context, Offset offset) { 30 | final child = this.child; 31 | if (child == null || _progress == 0) { 32 | super.paint(context, offset); 33 | return; 34 | } 35 | final double width = child.size.width; 36 | final double height = child.size.height; 37 | final canvas = context.canvas; 38 | 39 | final bounds = Rect.fromLTRB(0, 0, width + 1, height + 1); 40 | final paint = Paint(); 41 | 42 | canvas.saveLayer(bounds, paint); 43 | 44 | super.paint(context, offset); 45 | 46 | paint.blendMode = BlendMode.dstOut; 47 | final limit = 48 | width - width * (1 + width.limitedAnimationWidth / width) * _progress; 49 | final fadingLimit = math.min(width / 3, 10); 50 | final clipRect = Rect.fromLTRB(limit, -1.0, width + 1, height + 1); 51 | 52 | paint.shader = ui.Gradient.linear( 53 | Offset(limit, height / 2), 54 | Offset(limit + fadingLimit, height / 2), 55 | [ 56 | const Color(0x00000000), 57 | const Color(-1), 58 | ], 59 | ); 60 | 61 | canvas.drawRect(clipRect, paint); 62 | 63 | canvas.restore(); 64 | 65 | paint.shader = null; 66 | paint.blendMode = BlendMode.srcOver; 67 | 68 | if (_particles != null) { 69 | _drawParticles(_particles!, limit, width, paint, canvas); 70 | } 71 | } 72 | 73 | void _drawParticles(LinkedList particles, double limit, 74 | double width, Paint paint, Canvas canvas) { 75 | for (final particle in particles) { 76 | if (particle.cx < limit - 1) { 77 | continue; 78 | } 79 | final particleProgress = (particle.cx - limit).particleProgress(width); 80 | if (particleProgress == 1) { 81 | continue; 82 | } 83 | paint.color = particle.rgbaColor.withOpacity(1 - particleProgress); 84 | if (paint.color.a == 0) { 85 | continue; 86 | } 87 | final cx = 88 | (1 + _particleMaxRadius - particle.radius) / _particleMaxRadius; 89 | final cy = (particle.cx + particle.cy * 3) % 2 - 1; 90 | final dx = particle.cx + particleProgress * 10 * cx; 91 | final dy = particle.cy + 92 | particleProgress.interpolateYAxis(particle.pathType) * 8 * cy; 93 | canvas.drawCircle( 94 | Offset(dx, dy), 95 | particle.radius, 96 | paint, 97 | ); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/src/fade_out_particle.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | import 'dart:math' as math; 3 | import 'dart:typed_data'; 4 | import 'dart:ui' as ui; 5 | 6 | import 'package:flutter/rendering.dart'; 7 | import 'package:flutter/widgets.dart'; 8 | 9 | import 'particle.dart'; 10 | 11 | part 'extensions.dart'; 12 | part 'render_box.dart'; 13 | part 'render_object.dart'; 14 | 15 | const _particleMaxRadius = 2.0; 16 | const _particleMinRadius = 1.0; 17 | const _spaceBetweenParticles = 3; 18 | 19 | /// Fade out Particle effect 20 | @immutable 21 | class FadeOutParticle extends StatefulWidget { 22 | /// widget which is going to be disappeared 23 | final Widget child; 24 | 25 | /// duration of animation 26 | final Duration duration; 27 | 28 | /// whether to start disappearing [child] or not 29 | final bool disappear; 30 | 31 | /// curve of animation 32 | final Curve curve; 33 | 34 | /// callback to get notified when animation is ended 35 | final VoidCallback? onAnimationEnd; 36 | 37 | /// Fade out Particle effect 38 | const FadeOutParticle({ 39 | required this.disappear, 40 | required this.child, 41 | this.duration = const Duration(milliseconds: 1500), 42 | this.curve = Curves.easeOut, 43 | this.onAnimationEnd, 44 | super.key, 45 | }); 46 | 47 | @override 48 | State createState() => _FadeOutParticleState(); 49 | } 50 | 51 | class _FadeOutParticleState extends State 52 | with SingleTickerProviderStateMixin { 53 | final GlobalKey _repaintKey = GlobalKey(); 54 | late final AnimationController _controller = AnimationController( 55 | vsync: this, 56 | duration: widget.duration, 57 | )..addStatusListener((status) { 58 | if (status == AnimationStatus.completed) { 59 | widget.onAnimationEnd?.call(); 60 | } 61 | }); 62 | late final CurvedAnimation _animation = CurvedAnimation( 63 | parent: _controller, 64 | curve: widget.curve, 65 | ); 66 | LinkedList? _particles; 67 | 68 | @override 69 | void initState() { 70 | super.initState(); 71 | if (widget.disappear) { 72 | WidgetsBinding.instance.addPostFrameCallback((_) => _prepareParticles()); 73 | } 74 | } 75 | 76 | @override 77 | void didUpdateWidget(FadeOutParticle oldWidget) { 78 | if (oldWidget.duration != widget.duration || 79 | oldWidget.curve != widget.curve) { 80 | _controller.duration = widget.duration; 81 | _animation.curve = widget.curve; 82 | } 83 | if (oldWidget.disappear != widget.disappear) { 84 | if (widget.disappear) { 85 | _prepareParticles(); 86 | } else { 87 | _controller.reset(); 88 | } 89 | } 90 | super.didUpdateWidget(oldWidget); 91 | } 92 | 93 | @override 94 | Widget build(BuildContext context) { 95 | return RepaintBoundary( 96 | key: _repaintKey, 97 | child: AnimatedBuilder( 98 | animation: _animation, 99 | builder: (BuildContext _, Widget? child) => _RenderObject( 100 | progress: _animation.value, 101 | particles: _particles, 102 | child: child, 103 | ), 104 | child: widget.child, 105 | ), 106 | ); 107 | } 108 | 109 | Future _prepareParticles() async { 110 | final repaintContext = _repaintKey.currentContext; 111 | if (repaintContext == null) { 112 | return; 113 | } 114 | RenderRepaintBoundary boundary = 115 | repaintContext.findRenderObject() as RenderRepaintBoundary; 116 | final ui.Image image; 117 | try { 118 | image = await boundary.toImage(); 119 | } catch (e) { 120 | WidgetsBinding.instance.addPostFrameCallback((_) => _prepareParticles()); 121 | 122 | return; 123 | } 124 | 125 | final bytes = await image.toByteData(); 126 | 127 | if (bytes != null && image.width >= 2 && image.height >= 2) { 128 | _generateParticles(bytes, image.width, image.height); 129 | } 130 | 131 | image.dispose(); 132 | } 133 | 134 | void _generateParticles(ByteData bytes, int width, int height) { 135 | final verticalCount = math.max(1, height ~/ _spaceBetweenParticles); 136 | final horizontalCount = math.max(1, width ~/ _spaceBetweenParticles); 137 | 138 | final particles = LinkedList(); 139 | int horizontalOffset; 140 | const halfSpace = _spaceBetweenParticles ~/ 2; 141 | int verticalOffset = halfSpace; 142 | final random = math.Random(); 143 | for (int i = 0; i < verticalCount; i++) { 144 | horizontalOffset = halfSpace; 145 | for (var j = 0; j < horizontalCount; j++) { 146 | final rgbaColor = bytes.maybeNonTransparentColor( 147 | horizontalOffset, 148 | verticalOffset, 149 | width, 150 | halfSpace, 151 | ); 152 | if (!rgbaColor.isTransparent) { 153 | particles.add( 154 | Particle( 155 | cx: horizontalOffset, 156 | cy: verticalOffset, 157 | rgbaColor: rgbaColor, 158 | radius: _generateRandomRadius(random), 159 | pathType: random.nextInt(6), 160 | ), 161 | ); 162 | } 163 | horizontalOffset += _spaceBetweenParticles; 164 | } 165 | verticalOffset += _spaceBetweenParticles; 166 | } 167 | if (mounted) { 168 | setState(() { 169 | _particles = particles; 170 | _controller.forward(); 171 | }); 172 | } 173 | } 174 | 175 | double _generateRandomRadius(math.Random random) => 176 | random.nextDouble() * (_particleMaxRadius - _particleMinRadius) + 177 | _particleMinRadius; 178 | 179 | @override 180 | void dispose() { 181 | _controller.dispose(); 182 | super.dispose(); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------