├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── README.md ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── dev │ │ │ │ └── roszkowski │ │ │ │ └── animations │ │ │ │ └── MainActivity.kt │ │ └── res │ │ │ ├── drawable-v21 │ │ │ └── launch_background.xml │ │ │ ├── drawable │ │ │ └── launch_background.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── values-night │ │ │ └── styles.xml │ │ │ └── values │ │ │ └── styles.xml │ │ └── profile │ │ └── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── assets └── placeholder_image.png ├── docs ├── gif.gif └── presentation.pdf ├── ios ├── .gitignore ├── Flutter │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── Icon-App-1024x1024@1x.png │ │ ├── Icon-App-20x20@1x.png │ │ ├── Icon-App-20x20@2x.png │ │ ├── Icon-App-20x20@3x.png │ │ ├── Icon-App-29x29@1x.png │ │ ├── Icon-App-29x29@2x.png │ │ ├── Icon-App-29x29@3x.png │ │ ├── Icon-App-40x40@1x.png │ │ ├── Icon-App-40x40@2x.png │ │ ├── Icon-App-40x40@3x.png │ │ ├── Icon-App-60x60@2x.png │ │ ├── Icon-App-60x60@3x.png │ │ ├── Icon-App-76x76@1x.png │ │ ├── Icon-App-76x76@2x.png │ │ └── Icon-App-83.5x83.5@2x.png │ └── LaunchImage.imageset │ │ ├── Contents.json │ │ ├── LaunchImage.png │ │ ├── LaunchImage@2x.png │ │ ├── LaunchImage@3x.png │ │ └── README.md │ ├── Base.lproj │ ├── LaunchScreen.storyboard │ └── Main.storyboard │ ├── Info.plist │ └── Runner-Bridging-Header.h ├── lib ├── fps_widget.dart ├── main.dart └── pages │ ├── about.dart │ ├── custom_controller.dart │ ├── explicit │ ├── avatar.dart │ ├── dots.dart │ ├── explicit.dart │ ├── flutter.dart │ ├── icon.dart │ └── slack.dart │ ├── explicit_animations.dart │ ├── funvas.dart │ ├── gravity_simulation.dart │ ├── helpers │ └── staggered_animations_widgets.dart │ ├── implicit │ ├── implicit.dart │ ├── implicit_counter.dart │ └── implicit_loader.dart │ ├── implicit_animations.dart │ ├── material.dart │ ├── material │ └── example.dart │ ├── page.dart │ ├── pages.dart │ ├── spring_simulation.dart │ ├── staggered_animations.dart │ ├── tween_animation_builder.dart │ └── tweens.dart ├── linux ├── .gitignore ├── CMakeLists.txt ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake ├── main.cc ├── my_application.cc └── my_application.h ├── macos ├── .gitignore ├── Flutter │ ├── Flutter-Debug.xcconfig │ ├── Flutter-Release.xcconfig │ └── GeneratedPluginRegistrant.swift ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── Runner │ ├── AppDelegate.swift │ ├── Assets.xcassets │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── app_icon_1024.png │ │ ├── app_icon_128.png │ │ ├── app_icon_16.png │ │ ├── app_icon_256.png │ │ ├── app_icon_32.png │ │ ├── app_icon_512.png │ │ └── app_icon_64.png │ ├── Base.lproj │ └── MainMenu.xib │ ├── Configs │ ├── AppInfo.xcconfig │ ├── Debug.xcconfig │ ├── Release.xcconfig │ └── Warnings.xcconfig │ ├── DebugProfile.entitlements │ ├── Info.plist │ ├── MainFlutterWindow.swift │ └── Release.entitlements ├── pubspec.lock ├── pubspec.yaml ├── test └── widget_test.dart ├── web ├── favicon.png ├── icons │ ├── Icon-192.png │ └── Icon-512.png ├── index.html └── manifest.json └── windows ├── .gitignore ├── CMakeLists.txt ├── flutter ├── CMakeLists.txt ├── generated_plugin_registrant.cc ├── generated_plugin_registrant.h └── generated_plugins.cmake └── runner ├── CMakeLists.txt ├── Runner.rc ├── flutter_window.cpp ├── flutter_window.h ├── main.cpp ├── resource.h ├── resources └── app_icon.ico ├── run_loop.cpp ├── run_loop.h ├── runner.exe.manifest ├── utils.cpp ├── utils.h ├── win32_window.cpp └── win32_window.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | 43 | # Android Studio will place build artifacts here 44 | /android/app/debug 45 | /android/app/profile 46 | /android/app/release 47 | -------------------------------------------------------------------------------- /.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: c5a4b4029c0798f37c4a39b479d7cb75daa7b05c 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "animations_samples", 9 | "request": "launch", 10 | "type": "dart" 11 | }, 12 | { 13 | "name": "animations_samples (profile mode)", 14 | "request": "launch", 15 | "type": "dart", 16 | "flutterMode": "profile" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # animations 2 | 3 | This is a showcase of several animation approaches in Flutter: 4 | 5 | - implicit animations 6 | - explicit animations 7 | - TweenAnimationBuilder 8 | - funvas 9 | - animations package 10 | 11 | Feel free to have fun with the app and let me know what you think :) 12 | 13 | [Web Demo](https://roszkowski.dev/animations/) 14 | 15 | [Presentation PDF](docs/presentation.pdf) 16 | 17 | ![animation gif](docs/gif.gif) 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 30 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | defaultConfig { 36 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 37 | applicationId "dev.roszkowski.animations" 38 | minSdkVersion 16 39 | targetSdkVersion 30 40 | versionCode flutterVersionCode.toInteger() 41 | versionName flutterVersionName 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 59 | } 60 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 13 | 17 | 21 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/dev/roszkowski/animations/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.roszkowski.animations 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.1.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 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 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /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-6.7-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /assets/placeholder_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/assets/placeholder_image.png -------------------------------------------------------------------------------- /docs/gif.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/docs/gif.gif -------------------------------------------------------------------------------- /docs/presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/docs/presentation.pdf -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | animations 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | LSApplicationQueriesSchemes 45 | 46 | https 47 | http 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/fps_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:fps_widget/fps_widget.dart'; 3 | import 'package:provider/provider.dart'; 4 | 5 | class MyFpsWidget extends StatelessWidget { 6 | const MyFpsWidget({Key? key, required this.child}) : super(key: key); 7 | 8 | final Widget child; 9 | 10 | @override 11 | Widget build(BuildContext context) { 12 | return ChangeNotifierProvider( 13 | create: (_) => FpsState(), 14 | child: FpsWrapper(child: child), 15 | ); 16 | } 17 | } 18 | 19 | class FpsWrapper extends StatelessWidget { 20 | const FpsWrapper({ 21 | Key? key, 22 | required this.child, 23 | }) : super(key: key); 24 | 25 | final Widget child; 26 | 27 | @override 28 | Widget build(BuildContext context) { 29 | final value = context.watch().show; 30 | if (value) { 31 | return Scaffold( 32 | body: FPSWidget( 33 | show: true, 34 | child: child, 35 | ), 36 | ); 37 | } else { 38 | return child; 39 | } 40 | } 41 | } 42 | 43 | class FpsState extends ChangeNotifier { 44 | bool show = false; 45 | 46 | void toggle() { 47 | show = !show; 48 | notifyListeners(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:animations_sample/fps_widget.dart'; 2 | import 'package:animations_sample/pages/gravity_simulation.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:provider/provider.dart'; 5 | import 'package:url_launcher/url_launcher.dart'; 6 | 7 | import 'pages/pages.dart'; 8 | 9 | void main() { 10 | runApp(MyApp()); 11 | } 12 | 13 | class MyApp extends StatelessWidget { 14 | @override 15 | Widget build(BuildContext context) { 16 | return MaterialApp( 17 | title: 'Animations Demo', 18 | theme: ThemeData( 19 | primarySwatch: Colors.blue, 20 | scrollbarTheme: ScrollbarThemeData(isAlwaysShown: true), 21 | ), 22 | debugShowCheckedModeBanner: false, 23 | home: MyFpsWidget( 24 | child: Dashboard(), 25 | ), 26 | ); 27 | } 28 | } 29 | 30 | class Dashboard extends StatefulWidget { 31 | @override 32 | _DashboardState createState() => _DashboardState(); 33 | } 34 | 35 | class _DashboardState extends State { 36 | int selectedIndex = 0; 37 | 38 | Widget getPage(int index) { 39 | if (index < pages.length) { 40 | return pages[index].widget; 41 | } else { 42 | return const SizedBox(); 43 | } 44 | } 45 | 46 | final pages = [ 47 | AppPage('Implicit animations', ImplicitAnimations()), 48 | AppPage('Explicit animations', ExplicitAnimations()), 49 | AppPage('Tweens', Tweens()), 50 | AppPage('TweenAnimationBuilder', TweenPageController()), 51 | AppPage('Staggered animations', StaggeredAnimations()), 52 | AppPage('Custom controller', CustomController()), 53 | AppPage('Spring simulation', PhysicsAnimation()), 54 | AppPage('Gravity simulation', GravitySimulationWidget()), 55 | AppPage('Material animations', MaterialAnimationsDemo()), 56 | AppPage('Funvas', FunvasDemo()), 57 | ]; 58 | 59 | @override 60 | Widget build(BuildContext context) { 61 | final drawer = AppDrawer( 62 | pages: pages, 63 | selectedIndex: selectedIndex, 64 | onTap: (index) { 65 | setState(() { 66 | selectedIndex = index; 67 | }); 68 | }, 69 | ); 70 | 71 | return Scaffold( 72 | drawer: drawer, 73 | body: LayoutBuilder( 74 | builder: (context, constraints) { 75 | return Row( 76 | children: [ 77 | if (constraints.maxWidth < 800) 78 | Align( 79 | alignment: Alignment.topLeft, 80 | child: IconButton( 81 | icon: Icon(Icons.menu), 82 | onPressed: () { 83 | Scaffold.of(context).openDrawer(); 84 | }, 85 | ), 86 | ) 87 | else 88 | drawer, 89 | Expanded( 90 | child: getPage(selectedIndex), 91 | ) 92 | ], 93 | ); 94 | }, 95 | ), 96 | ); 97 | } 98 | } 99 | 100 | class AppDrawer extends StatefulWidget { 101 | const AppDrawer({ 102 | Key? key, 103 | required this.onTap, 104 | required this.pages, 105 | this.selectedIndex = 0, 106 | }) : super(key: key); 107 | 108 | final Function(int) onTap; 109 | final List pages; 110 | final int selectedIndex; 111 | 112 | @override 113 | State createState() => _AppDrawerState(); 114 | } 115 | 116 | class _AppDrawerState extends State { 117 | final scrollController = ScrollController(); 118 | 119 | @override 120 | void dispose() { 121 | scrollController.dispose(); 122 | super.dispose(); 123 | } 124 | 125 | @override 126 | Widget build(BuildContext context) { 127 | final theme = Theme.of(context); 128 | return Drawer( 129 | child: ListView( 130 | padding: EdgeInsets.zero, 131 | controller: scrollController, 132 | children: [ 133 | DrawerHeader( 134 | child: Padding( 135 | padding: const EdgeInsets.all(16.0), 136 | child: Text( 137 | 'Animations examples', 138 | style: theme.textTheme.headline5, 139 | ), 140 | ), 141 | ), 142 | for (final page in widget.pages) 143 | ListTile( 144 | leading: Icon(Icons.chevron_right), 145 | title: Text(page.title), 146 | selected: widget.pages.indexOf(page) == widget.selectedIndex, 147 | onTap: () { 148 | widget.onTap(widget.pages.indexOf(page)); 149 | if (Scaffold.of(context).isDrawerOpen) { 150 | Navigator.of(context).pop(); 151 | } 152 | }, 153 | ), 154 | ThinDivider(), 155 | ListTile( 156 | leading: Icon(Icons.info_outline), 157 | title: Text('About'), 158 | onTap: () { 159 | if (Scaffold.of(context).isDrawerOpen) { 160 | Navigator.of(context).pop(); 161 | } 162 | showAboutDialog( 163 | context: context, 164 | children: [ 165 | AboutPage(), 166 | ], 167 | ); 168 | if (Scaffold.of(context).isDrawerOpen) { 169 | Navigator.of(context).pop(); 170 | } 171 | }, 172 | ), 173 | ListTile( 174 | leading: Icon(Icons.code), 175 | title: Text('Source Code'), 176 | onTap: () async { 177 | if (Scaffold.of(context).isDrawerOpen) { 178 | Navigator.of(context).pop(); 179 | } 180 | final url = 'https://github.com/orestesgaolin/animations_samples'; 181 | if (await canLaunch(url)) { 182 | launch(url); 183 | } 184 | }, 185 | ), 186 | ThinDivider(), 187 | SwitchListTile( 188 | value: context.watch().show, 189 | title: Text('Show FPS'), 190 | onChanged: (_) { 191 | context.read().toggle(); 192 | }, 193 | ), 194 | ], 195 | ), 196 | ); 197 | } 198 | } 199 | 200 | class ThinDivider extends StatelessWidget { 201 | const ThinDivider({ 202 | Key? key, 203 | }) : super(key: key); 204 | 205 | @override 206 | Widget build(BuildContext context) { 207 | return Divider( 208 | height: 1, 209 | thickness: 1, 210 | ); 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /lib/pages/about.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:gap/gap.dart'; 3 | import 'package:url_launcher/url_launcher.dart'; 4 | 5 | class AboutPage extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return Column( 9 | mainAxisAlignment: MainAxisAlignment.center, 10 | children: [ 11 | Text('Application made by Dominik Roszkowski'), 12 | const Gap(16), 13 | InkWell( 14 | onTap: () async { 15 | final url = 'https://roszkowski.dev'; 16 | if (await canLaunch(url)) { 17 | launch(url); 18 | } 19 | }, 20 | child: Text('dominik@roszkowski.dev'), 21 | ), 22 | const Gap(16), 23 | InkWell( 24 | onTap: () async { 25 | final url = 'https://twitter.com/OrestesGaolin'; 26 | if (await canLaunch(url)) { 27 | launch(url); 28 | } 29 | }, 30 | child: Text('@OrestesGaolin'), 31 | ), 32 | ], 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/pages/custom_controller.dart: -------------------------------------------------------------------------------- 1 | /// Boids algorithm ported from JS to Dart by Dominik Roszkowski 2 | /// Original version by Ben Eater licensesd under MIT 3 | /// Available https://github.com/beneater/boids 4 | /// 5 | /// Check out the Smarter Every Day video about 6 | /// this simulation https://www.youtube.com/watch?v=4LWmRuB-uNU 7 | /// 8 | /// Find me on Twitter https://twitter.com/OrestesGaolin 9 | /// And my website https://roszkowski.dev/ 10 | import 'dart:math' as math; 11 | import 'package:flutter/material.dart'; 12 | import 'package:flutter/scheduler.dart'; 13 | 14 | const double kSize = 150.0; 15 | 16 | class CustomController extends StatelessWidget { 17 | @override 18 | Widget build(BuildContext context) { 19 | return BoidsAnimation(); 20 | } 21 | } 22 | 23 | class BoidsAnimation extends StatefulWidget { 24 | BoidsAnimation({Key? key}) : super(key: key); 25 | 26 | @override 27 | _BoidsAnimationState createState() => _BoidsAnimationState(); 28 | } 29 | 30 | class _BoidsAnimationState extends State 31 | with TickerProviderStateMixin { 32 | late BoidSimulation simulation; 33 | 34 | @override 35 | void initState() { 36 | super.initState(); 37 | simulation = BoidSimulation(this); 38 | } 39 | 40 | @override 41 | void dispose() { 42 | simulation.dispose(); 43 | super.dispose(); 44 | } 45 | 46 | @override 47 | Widget build(BuildContext context) { 48 | return Column( 49 | children: [ 50 | Expanded( 51 | child: LayoutBuilder(builder: (context, size) { 52 | return ColoredBox( 53 | color: Colors.blue[800]!, 54 | child: AnimatedBuilder( 55 | animation: simulation, 56 | builder: (context, child) => Stack( 57 | children: [ 58 | Center( 59 | child: CustomPaint( 60 | painter: BoidPainter(simulation.boids), 61 | child: Container( 62 | height: size.maxHeight, 63 | width: size.maxWidth, 64 | color: Colors.white.withOpacity(0.05), 65 | ), 66 | ), 67 | ), 68 | ], 69 | ), 70 | ), 71 | ); 72 | }), 73 | ), 74 | Controls(simulation: simulation), 75 | ], 76 | ); 77 | } 78 | } 79 | 80 | class Controls extends StatelessWidget { 81 | const Controls({ 82 | Key? key, 83 | required this.simulation, 84 | }) : super(key: key); 85 | 86 | final BoidSimulation simulation; 87 | 88 | @override 89 | Widget build(BuildContext context) { 90 | return AnimatedBuilder( 91 | animation: simulation, 92 | builder: (context, anim) { 93 | return Padding( 94 | padding: const EdgeInsets.all(8.0), 95 | child: Wrap( 96 | alignment: WrapAlignment.center, 97 | children: [ 98 | Container( 99 | width: 200, 100 | child: Column( 101 | children: [ 102 | Text('Speed'), 103 | Slider( 104 | value: simulation.speedLimit, 105 | min: 0.0, 106 | max: 20.0, 107 | onChanged: (value) { 108 | simulation.speedLimit = value; 109 | }, 110 | ), 111 | ], 112 | ), 113 | ), 114 | Container( 115 | width: 200, 116 | child: Column( 117 | children: [ 118 | Text('Visual Range'), 119 | Slider( 120 | value: simulation.visualRange, 121 | min: 0.0, 122 | max: 100.0, 123 | onChanged: (value) { 124 | simulation.visualRange = value; 125 | }, 126 | ), 127 | ], 128 | ), 129 | ), 130 | Container( 131 | width: 200, 132 | child: Column( 133 | children: [ 134 | Text('Boids number'), 135 | Slider( 136 | value: simulation.numBoids.toDouble(), 137 | min: 0, 138 | max: 200, 139 | onChanged: (value) { 140 | simulation.setBoidsNumber(value.toInt()); 141 | }, 142 | ), 143 | ], 144 | ), 145 | ), 146 | Container( 147 | width: 200, 148 | child: Column( 149 | children: [ 150 | Text('Turn factor'), 151 | Slider( 152 | value: simulation.turnFactor, 153 | min: 0.0, 154 | max: 1.0, 155 | onChanged: (value) { 156 | simulation.turnFactor = value; 157 | }, 158 | ), 159 | ], 160 | ), 161 | ), 162 | ], 163 | ), 164 | ); 165 | }); 166 | } 167 | } 168 | 169 | class BoidPainter extends CustomPainter { 170 | final List boids; 171 | BoidPainter(this.boids); 172 | 173 | @override 174 | void paint(Canvas canvas, Size size) { 175 | final scale = size.shortestSide / kSize / 2; 176 | canvas.translate(size.width / 4, size.height / 4); 177 | canvas.scale(scale); 178 | for (var boid in boids) { 179 | canvas.save(); 180 | // canvas.drawCircle(boid.position, 1.0, Paint()..color = Colors.white); 181 | DashPainter(boid.position).paint(canvas, size); 182 | canvas.restore(); 183 | } 184 | } 185 | 186 | @override 187 | bool shouldRepaint(CustomPainter oldDelegate) { 188 | return true; 189 | } 190 | } 191 | 192 | class Boid { 193 | double x; 194 | double y; 195 | double dx; 196 | double dy; 197 | 198 | Offset get position => Offset(x, y); 199 | Offset get velocity => Offset(dx, dy); 200 | 201 | List history = []; 202 | 203 | Boid(this.x, this.y, this.dx, this.dy); 204 | 205 | bool operator ==(dynamic other) { 206 | if (other is Boid) { 207 | return other.x == this.x && 208 | other.y == this.y && 209 | other.dx == this.dx && 210 | other.dy == this.dy; 211 | } else 212 | return false; 213 | } 214 | 215 | @override 216 | int get hashCode => (x * y * dx * dy).toInt(); 217 | } 218 | 219 | class BoidSimulation extends ChangeNotifier { 220 | BoidSimulation(this.vsync) { 221 | initBoids(); 222 | _ticker = vsync.createTicker(_onEachTick)..start(); 223 | } 224 | final TickerProvider vsync; 225 | late Ticker _ticker; 226 | double time = 0.0; 227 | 228 | double speedLimit = 5.0; 229 | double visualRange = 40.0; 230 | int numBoids = 100; 231 | double turnFactor = 0.2; 232 | 233 | final double width = kSize; 234 | final double height = kSize; 235 | 236 | final List boids = []; 237 | 238 | void _onEachTick(Duration deltaTime) { 239 | final lastFrameTime = deltaTime.inMilliseconds.toDouble() / 1000.0; 240 | time += lastFrameTime; 241 | 242 | for (var boid in boids) { 243 | // Update the velocities according to each rule 244 | _flyTowardsCenter(boid); 245 | _avoidOthers(boid); 246 | _matchVelocity(boid); 247 | limitSpeed(boid); 248 | _keepWithinBounds(boid); 249 | 250 | // Update the position based on the current velocity 251 | boid.x += boid.dx; 252 | boid.y += boid.dy; 253 | boid.history.add(boid.position); 254 | boid.history = boid.history.take(50).toList(); 255 | } 256 | 257 | notifyListeners(); 258 | } 259 | 260 | @override 261 | void dispose() { 262 | _ticker.dispose(); 263 | super.dispose(); 264 | } 265 | 266 | void initBoids([int init = 0]) { 267 | for (var i = init; i < numBoids; i++) { 268 | final x = math.Random().nextDouble() * width; 269 | final y = math.Random().nextDouble() * height; 270 | final dx = math.Random().nextDouble() * 10 - 5; 271 | final dy = math.Random().nextDouble() * 10 - 5; 272 | boids.add(Boid(x, y, dx, dy)); 273 | } 274 | } 275 | 276 | void setBoidsNumber(int value) { 277 | numBoids = value; 278 | if (numBoids < boids.length) { 279 | boids.removeRange(numBoids, boids.length); 280 | } else { 281 | initBoids(boids.length); 282 | } 283 | } 284 | 285 | double _distance(Boid boid1, Boid boid2) { 286 | return math.sqrt( 287 | (boid1.x - boid2.x) * (boid1.x - boid2.x) + 288 | (boid1.y - boid2.y) * (boid1.y - boid2.y), 289 | ); 290 | } 291 | 292 | /// Constrain a boid to within the window. If it gets too close to an edge, 293 | /// nudge it back in and reverse its direction. 294 | void _keepWithinBounds(Boid boid) { 295 | const margin = 50; 296 | 297 | if (boid.x < width + margin) { 298 | boid.dx += turnFactor; 299 | } 300 | if (boid.x > -margin) { 301 | boid.dx -= turnFactor; 302 | } 303 | if (boid.y < height + margin) { 304 | boid.dy += turnFactor; 305 | } 306 | if (boid.y > -margin) { 307 | boid.dy -= turnFactor; 308 | } 309 | } 310 | 311 | /// Find the center of mass of the other boids and adjust velocity slightly to 312 | /// point towards the center of mass. 313 | void _flyTowardsCenter(Boid boid) { 314 | const centeringFactor = 0.005; // adjust velocity by this % 315 | 316 | var centerX = 0.0; 317 | var centerY = 0.0; 318 | var numNeighbors = 0.0; 319 | 320 | for (var otherBoid in boids) { 321 | if (_distance(boid, otherBoid) < visualRange) { 322 | centerX += otherBoid.x; 323 | centerY += otherBoid.y; 324 | numNeighbors += 1; 325 | } 326 | } 327 | 328 | if (numNeighbors > 0.0) { 329 | centerX = centerX / numNeighbors; 330 | centerY = centerY / numNeighbors; 331 | 332 | boid.dx += (centerX - boid.x) * centeringFactor; 333 | boid.dy += (centerY - boid.y) * centeringFactor; 334 | } 335 | } 336 | 337 | /// Move away from other boids that are too close to avoid colliding 338 | void _avoidOthers(Boid boid) { 339 | const minDistance = 10; // The distance to stay away from other boids 340 | const avoidFactor = 0.01; // Adjust velocity by this % 341 | var moveX = 0.0; 342 | var moveY = 0.0; 343 | for (var otherBoid in boids) { 344 | if (otherBoid != boid) { 345 | if (_distance(boid, otherBoid) < minDistance) { 346 | moveX += boid.x - otherBoid.x; 347 | moveY += boid.y - otherBoid.y; 348 | } 349 | } 350 | } 351 | 352 | boid.dx += moveX * avoidFactor; 353 | boid.dy += moveY * avoidFactor; 354 | } 355 | 356 | /// Find the average velocity (speed and direction) of the other boids and 357 | /// adjust velocity slightly to match. 358 | void _matchVelocity(Boid boid) { 359 | const matchingFactor = 0.05; // Adjust by this % of average velocity 360 | 361 | var avgDX = 0.0; 362 | var avgDY = 0.0; 363 | var numNeighbors = 0.0; 364 | 365 | for (var otherBoid in boids) { 366 | if (_distance(boid, otherBoid) < visualRange) { 367 | avgDX += otherBoid.dx; 368 | avgDY += otherBoid.dy; 369 | numNeighbors += 1; 370 | } 371 | } 372 | 373 | if (numNeighbors > 0.0) { 374 | avgDX = avgDX / numNeighbors; 375 | avgDY = avgDY / numNeighbors; 376 | 377 | boid.dx += (avgDX - boid.dx) * matchingFactor; 378 | boid.dy += (avgDY - boid.dy) * matchingFactor; 379 | } 380 | } 381 | 382 | /// Speed will naturally vary in flocking behavior, but real animals can't go 383 | /// arbitrarily fast. 384 | void limitSpeed(boid) { 385 | final speed = math.sqrt(boid.dx * boid.dx + boid.dy * boid.dy); 386 | if (speed > speedLimit) { 387 | boid.dx = (boid.dx / speed) * speedLimit; 388 | boid.dy = (boid.dy / speed) * speedLimit; 389 | } 390 | } 391 | } 392 | 393 | class DashPainter extends CustomPainter { 394 | DashPainter(this.position); 395 | 396 | final Offset position; 397 | 398 | @override 399 | void paint(Canvas canvas, Size size) { 400 | final leftLeg = true; 401 | final rightLeg = true; 402 | 403 | final body = Path() 404 | ..addOval( 405 | Rect.fromLTWH( 406 | 10, 407 | 10, 408 | 100, 409 | 100, 410 | ), 411 | ); 412 | final tail = Path() 413 | ..moveTo(0, 20) 414 | ..lineTo(40, 40) 415 | ..lineTo(40, 80) 416 | ..lineTo(0, 50); 417 | final wing = Path() 418 | ..moveTo(0, 40) 419 | ..lineTo(45, 40) 420 | ..relativeArcToPoint(Offset(0, 40), radius: Radius.circular(20)) 421 | ..lineTo(15, 80) 422 | ..close(); 423 | final tip = Path() 424 | ..moveTo(90, 50) 425 | ..lineTo(140, 60) 426 | ..lineTo(90, 60); 427 | final eye = Path()..addOval(Rect.fromLTWH(80, 40, 10, 10)); 428 | final eyeWhite = Path()..addOval(Rect.fromLTWH(82.5, 42.5, 3, 3)); 429 | final top = Path()..addOval(Rect.fromLTWH(50, 6, 20, 10)); 430 | final legL = Path() 431 | ..moveTo(50, 100) 432 | ..lineTo(50, 130) 433 | ..lineTo(52, 135) 434 | ..lineTo(63, 135) 435 | ..lineTo(58, 130) 436 | ..lineTo(58, 100); 437 | final legR = Path() 438 | ..moveTo(68, 100) 439 | ..lineTo(68, 130) 440 | ..lineTo(70, 135) 441 | ..lineTo(81, 135) 442 | ..lineTo(76, 130) 443 | ..lineTo(76, 100); 444 | final faceWhite = Path() 445 | ..moveTo(90, 50) 446 | ..relativeArcToPoint(Offset(-0, 50), 447 | radius: Radius.circular(15), clockwise: false) 448 | ..lineTo(100, 90) 449 | ..lineTo(110, 80); 450 | canvas.translate(position.dx, position.dy); 451 | canvas.scale(0.1); 452 | if (leftLeg) canvas.drawPath(legL, Paint()..color = Colors.brown[400]!); 453 | if (rightLeg) canvas.drawPath(legR, Paint()..color = Colors.brown[400]!); 454 | canvas.drawPath(body, Paint()..color = Colors.blue[300]!); 455 | canvas.drawPath(tail, Paint()..color = Colors.teal[400]!); 456 | canvas.drawPath(faceWhite, Paint()..color = Colors.white.withOpacity(0.8)); 457 | canvas.drawPath(wing, Paint()..color = Colors.blue[500]!); 458 | canvas.drawPath(tip, Paint()..color = Colors.brown[400]!); 459 | canvas.drawPath(eye, Paint()..color = Colors.black); 460 | canvas.drawPath(eyeWhite, Paint()..color = Colors.white); 461 | canvas.drawPath(top, Paint()..color = Colors.blue[400]!); 462 | } 463 | 464 | @override 465 | bool shouldRepaint(covariant DashPainter oldDelegate) { 466 | return position != oldDelegate.position; 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /lib/pages/explicit/avatar.dart: -------------------------------------------------------------------------------- 1 | // Copyright 2021 Dominik Roszkowski 2 | import 'package:flutter/material.dart'; 3 | 4 | class AvatarAnimation extends StatelessWidget { 5 | @override 6 | Widget build(BuildContext context) { 7 | final radius = 100.0; 8 | return Center( 9 | child: _AnimatedAvatarDecoration( 10 | child: CircleAvatar( 11 | backgroundImage: NetworkImage('https://picsum.photos/id/823/300'), 12 | backgroundColor: Colors.white, 13 | radius: radius, 14 | ), 15 | radius: radius, 16 | ), 17 | ); 18 | } 19 | } 20 | 21 | /// Pulse decoration shown around the avatar 22 | /// 23 | /// It uses several animation controllers to show sequential 24 | /// radiating circles around the avatar. As they extend from 25 | /// the avatar their opacity decreases to 0.0. 26 | class _AnimatedAvatarDecoration extends StatefulWidget { 27 | const _AnimatedAvatarDecoration({ 28 | Key? key, 29 | required this.child, 30 | required this.radius, 31 | }) : super(key: key); 32 | 33 | final Widget child; 34 | final double radius; 35 | 36 | @override 37 | _AnimatedAvatarDecorationState createState() => 38 | _AnimatedAvatarDecorationState(); 39 | } 40 | 41 | class _AnimatedAvatarDecorationState extends State<_AnimatedAvatarDecoration> 42 | with TickerProviderStateMixin { 43 | AnimationController? animationController1; 44 | AnimationController? animationController2; 45 | AnimationController? animationController3; 46 | 47 | final radiatingTween = Tween(begin: 1.0, end: 0.0); 48 | 49 | @override 50 | void initState() { 51 | super.initState(); 52 | animationController1 = AnimationController( 53 | vsync: this, 54 | duration: _kRadiatingAnimationDuration, 55 | debugLabel: 'animated_avatar_decoration1', 56 | ) 57 | ..forward() 58 | ..repeat(); 59 | animationController2 = AnimationController( 60 | vsync: this, 61 | duration: _kRadiatingAnimationDuration, 62 | debugLabel: 'animated_avatar_decoration2', 63 | ) 64 | ..forward(from: 1 / 3) 65 | ..repeat(); 66 | animationController3 = AnimationController( 67 | vsync: this, 68 | duration: _kRadiatingAnimationDuration, 69 | debugLabel: 'animated_avatar_decoration3', 70 | ) 71 | ..forward(from: 2 / 3) 72 | ..repeat(); 73 | } 74 | 75 | @override 76 | void dispose() { 77 | animationController1?.dispose(); 78 | animationController2?.dispose(); 79 | animationController3?.dispose(); 80 | super.dispose(); 81 | } 82 | 83 | @override 84 | Widget build(BuildContext context) { 85 | return AnimatedBuilder( 86 | animation: animationController1!, 87 | builder: (context, child) { 88 | final _padding = widget.radius * 0.75; 89 | final anim1 = radiatingTween.animate(animationController1!); 90 | final anim2 = radiatingTween.animate(animationController2!); 91 | final anim3 = radiatingTween.animate(animationController3!); 92 | 93 | return Center( 94 | child: Stack( 95 | children: [ 96 | _RadiatingCircle( 97 | padding: _padding, 98 | anim1: anim1, 99 | key: const Key('animated_avatar_decoration1'), 100 | ), 101 | _RadiatingCircle( 102 | padding: _padding, 103 | anim1: anim2, 104 | key: const Key('animated_avatar_decoration2'), 105 | ), 106 | _RadiatingCircle( 107 | padding: _padding, 108 | anim1: anim3, 109 | key: const Key('animated_avatar_decoration3'), 110 | ), 111 | Padding( 112 | padding: EdgeInsets.all(_padding), 113 | child: child, 114 | ), 115 | ], 116 | ), 117 | ); 118 | }, 119 | child: DecoratedBox( 120 | decoration: const ShapeDecoration( 121 | shape: CircleBorder(), 122 | shadows: [ 123 | BoxShadow( 124 | color: Color(0x8008212D), 125 | blurRadius: 28, 126 | offset: Offset(4, 4), 127 | ), 128 | ], 129 | ), 130 | child: widget.child, 131 | ), 132 | ); 133 | } 134 | } 135 | 136 | const _kRadiatingAnimationDuration = Duration(milliseconds: 3500); 137 | const _kOpacityFraction = 1 / 5; 138 | 139 | class _RadiatingCircle extends StatelessWidget { 140 | const _RadiatingCircle({ 141 | Key? key, 142 | required this.padding, 143 | required this.anim1, 144 | }) : super(key: key); 145 | 146 | final double padding; 147 | final Animation anim1; 148 | final baseColor = Colors.white10; 149 | 150 | @override 151 | Widget build(BuildContext context) { 152 | return Positioned.fill( 153 | child: Padding( 154 | padding: EdgeInsets.all(padding * anim1.value), 155 | child: DecoratedBox( 156 | decoration: ShapeDecoration( 157 | shape: const CircleBorder(), 158 | color: baseColor.withOpacity(anim1.value * _kOpacityFraction), 159 | ), 160 | ), 161 | ), 162 | ); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /lib/pages/explicit/dots.dart: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021 Dominik Roszkowski on the MIT license 2 | import 'dart:async'; 3 | 4 | import 'package:flutter/material.dart'; 5 | 6 | const _numberOfElements = 5; 7 | const _duration = Duration(milliseconds: 350); 8 | 9 | class DotsLoader extends StatefulWidget { 10 | const DotsLoader({ 11 | Key? key, 12 | this.size = 20.0, 13 | }) : assert(size > 0), 14 | super(key: key); 15 | 16 | /// Size of the largest dot 17 | final double size; 18 | 19 | @override 20 | _DotsLoaderState createState() => _DotsLoaderState(); 21 | } 22 | 23 | class _DotsLoaderState extends State { 24 | Timer? timer; 25 | int index = 0; 26 | 27 | @override 28 | void initState() { 29 | super.initState(); 30 | 31 | timer = Timer.periodic( 32 | _duration, 33 | _incrementIndex, 34 | ); 35 | } 36 | 37 | void _incrementIndex(timer) { 38 | setState(() { 39 | index = (index + 1) % (_numberOfElements + 1); 40 | }); 41 | } 42 | 43 | @override 44 | void dispose() { 45 | timer?.cancel(); 46 | super.dispose(); 47 | } 48 | 49 | @override 50 | Widget build(BuildContext context) { 51 | return Row( 52 | mainAxisAlignment: MainAxisAlignment.center, 53 | mainAxisSize: MainAxisSize.min, 54 | children: [ 55 | for (final i in List.generate(_numberOfElements, (i) => i)) 56 | _Dot( 57 | size: widget.size, 58 | currentIndex: index, 59 | elementIndex: i, 60 | ) 61 | ], 62 | ); 63 | } 64 | } 65 | 66 | class _Dot extends StatelessWidget { 67 | const _Dot({ 68 | Key? key, 69 | this.size, 70 | this.currentIndex, 71 | this.elementIndex, 72 | }) : super(key: key); 73 | 74 | final double? size; 75 | final int? currentIndex; 76 | final int? elementIndex; 77 | 78 | /// Return default size for the main element (index == elementIndex) 79 | /// 80 | /// Return 3/4 of the default size for the 2 neighbouring elements 81 | /// 82 | /// Return 1/2 of the default size for the other elements 83 | double getFraction() { 84 | if (currentIndex == elementIndex) { 85 | return 1.0; 86 | } 87 | final absoluteIndex = (currentIndex! - elementIndex!).abs(); 88 | final isNeighbourToMainPoint = absoluteIndex == 1; 89 | if (isNeighbourToMainPoint) { 90 | return 0.75; 91 | } 92 | return 0.5; 93 | } 94 | 95 | @override 96 | Widget build(BuildContext context) { 97 | final fraction = getFraction(); 98 | return SizedBox( 99 | height: size! + 4, 100 | width: size! + 4, 101 | child: Center( 102 | child: AnimatedContainer( 103 | duration: _duration, 104 | decoration: ShapeDecoration( 105 | color: Colors.white.withOpacity(fraction), 106 | shape: const CircleBorder(), 107 | ), 108 | height: fraction * size!, 109 | width: fraction * size!, 110 | ), 111 | ), 112 | ); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /lib/pages/explicit/explicit.dart: -------------------------------------------------------------------------------- 1 | export 'avatar.dart'; 2 | export 'dots.dart'; 3 | export 'flutter.dart'; 4 | export 'icon.dart'; 5 | export 'slack.dart'; 6 | -------------------------------------------------------------------------------- /lib/pages/explicit/icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AnimatedRadiatingIcon extends StatefulWidget { 4 | @override 5 | _AnimatedRadiatingIconState createState() => _AnimatedRadiatingIconState(); 6 | } 7 | 8 | class _AnimatedRadiatingIconState extends State 9 | with TickerProviderStateMixin { 10 | late AnimationController animationController; 11 | 12 | @override 13 | void initState() { 14 | super.initState(); 15 | animationController = AnimationController( 16 | vsync: this, 17 | duration: Duration(seconds: 1), 18 | ) 19 | ..forward() 20 | ..repeat(reverse: true); 21 | } 22 | 23 | @override 24 | void dispose() { 25 | animationController.dispose(); 26 | super.dispose(); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return ColoredBox( 32 | color: Colors.blue, 33 | child: Center( 34 | child: AnimatedBuilder( 35 | animation: animationController, 36 | builder: (context, child) { 37 | return Container( 38 | decoration: ShapeDecoration( 39 | color: Colors.white.withOpacity(0.5), 40 | shape: CircleBorder(), 41 | ), 42 | child: Padding( 43 | padding: EdgeInsets.all(8.0 * animationController.value), 44 | child: child, 45 | ), 46 | ); 47 | }, 48 | child: DecoratedBox( 49 | decoration: ShapeDecoration( 50 | color: Colors.white, 51 | shape: CircleBorder(), 52 | ), 53 | child: IconButton( 54 | onPressed: () {}, 55 | color: Colors.blue, 56 | icon: Icon(Icons.calendar_today, size: 24), 57 | ), 58 | ), 59 | ), 60 | ), 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/pages/explicit/slack.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | import 'package:flutter/material.dart'; 3 | 4 | class SlackLogo extends StatefulWidget { 5 | SlackLogo({Key? key, this.title}) : super(key: key); 6 | 7 | final String? title; 8 | 9 | @override 10 | _SlackLogoState createState() => _SlackLogoState(); 11 | } 12 | 13 | class _SlackLogoState extends State with TickerProviderStateMixin { 14 | AnimationController? animationController; 15 | 16 | @override 17 | void initState() { 18 | super.initState(); 19 | animationController = AnimationController( 20 | duration: Duration(seconds: 3), 21 | vsync: this, 22 | lowerBound: 0.0, 23 | upperBound: 1.0, 24 | ) 25 | ..forward() 26 | ..repeat(reverse: true); 27 | } 28 | 29 | @override 30 | void dispose() { 31 | animationController?.dispose(); 32 | super.dispose(); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | final size = MediaQuery.of(context).size; 38 | final width = size.shortestSide / 2; 39 | return ClipRect( 40 | child: Scaffold( 41 | body: Center( 42 | child: Container( 43 | height: width, 44 | width: width, 45 | child: AnimatedBuilder( 46 | animation: animationController!, 47 | builder: (context, anim) { 48 | final scale = Tween(begin: 1.0, end: 0.5).animate( 49 | CurvedAnimation( 50 | parent: animationController!, 51 | curve: Interval( 52 | 0.3, 53 | 0.8, 54 | curve: Curves.easeOut, 55 | ), 56 | ), 57 | ); 58 | final oneToZero = Tween(begin: 1.0, end: 0.0).animate( 59 | CurvedAnimation( 60 | parent: animationController!, 61 | curve: Interval( 62 | 0.1, 63 | 0.6, 64 | curve: Curves.easeOutCubic, 65 | ), 66 | ), 67 | ); 68 | final oneToZeroTwo = 69 | Tween(begin: 1.0, end: 0.19).animate( 70 | CurvedAnimation( 71 | parent: animationController!, 72 | curve: Interval( 73 | 0.1, 74 | 0.7, 75 | curve: Curves.easeOutCubic, 76 | ), 77 | ), 78 | ); 79 | 80 | final zeroToOne = Tween(begin: 0.0, end: 1.0).animate( 81 | CurvedAnimation( 82 | parent: animationController!, 83 | curve: Interval( 84 | 0.1, 85 | 0.7, 86 | curve: Curves.easeOutCubic, 87 | ), 88 | ), 89 | ); 90 | 91 | final rotation = 92 | Tween(begin: 0.0, end: math.pi).animate( 93 | CurvedAnimation( 94 | parent: animationController!, 95 | curve: Interval( 96 | 0.1, 97 | 0.7, 98 | curve: Curves.easeOutCubic, 99 | ), 100 | ), 101 | ); 102 | 103 | return LogoStack( 104 | width, 105 | scale.value, 106 | oneToZero.value, 107 | zeroToOne.value, 108 | oneToZeroTwo.value, 109 | rotation.value, 110 | ); 111 | }, 112 | ), 113 | ), 114 | ), 115 | ), 116 | ); 117 | } 118 | } 119 | 120 | class LogoStack extends StatelessWidget { 121 | const LogoStack( 122 | this.size, 123 | this.scale, 124 | this.oneToZero, 125 | this.zeroToOne, 126 | this.oneToZeroTwo, 127 | this.rotation, { 128 | Key? key, 129 | }) : super(key: key); 130 | 131 | final double size; 132 | final double scale; 133 | final double oneToZero; 134 | final double zeroToOne; 135 | final double oneToZeroTwo; 136 | final double rotation; 137 | 138 | @override 139 | Widget build(BuildContext context) { 140 | final width = 0.2 * size; 141 | final elementSpacing = 0.025 * size; 142 | // green blobs as reference 143 | final smallXaxis = 144 | (width / 2 + elementSpacing + width + 0.05 * size) * oneToZero; 145 | final smallYaxis = (-width / 2 - elementSpacing) * oneToZero; 146 | 147 | final largeXaxis = (width / 2 + elementSpacing) * oneToZero; 148 | final largeYaxis = -0.25 * size * oneToZero; 149 | 150 | // final clipSize = size / 2 * oneToZeroTwo; 151 | final yellowRadius = zeroToOne * width / 2; 152 | 153 | return Transform.scale( 154 | scale: scale, 155 | child: Transform.rotate( 156 | angle: rotation, 157 | child: Stack( 158 | children: [ 159 | Center( 160 | child: Transform.translate( 161 | offset: Offset( 162 | largeXaxis, 163 | largeYaxis, 164 | ), 165 | child: LargeBlob( 166 | width: width, 167 | color: SColors.green, 168 | scale: zeroToOne, 169 | ), 170 | ), 171 | ), 172 | Center( 173 | child: Transform.translate( 174 | offset: Offset( 175 | smallXaxis, 176 | smallYaxis, 177 | ), 178 | child: SmallBlob( 179 | width: width, 180 | color: SColors.green, 181 | turns: 0, 182 | radius: yellowRadius, 183 | ), 184 | ), 185 | ), 186 | Center( 187 | child: Transform.translate( 188 | offset: Offset( 189 | largeYaxis, 190 | -largeXaxis, 191 | ), 192 | child: RotatedBox( 193 | quarterTurns: 1, 194 | child: LargeBlob( 195 | width: width, 196 | color: SColors.blue, 197 | scale: zeroToOne, 198 | ), 199 | ), 200 | ), 201 | ), 202 | Center( 203 | child: Transform.translate( 204 | offset: Offset( 205 | smallYaxis, 206 | -smallXaxis, 207 | ), 208 | child: SmallBlob( 209 | width: width, 210 | color: SColors.blue, 211 | turns: 3, 212 | radius: yellowRadius, 213 | ), 214 | ), 215 | ), 216 | Center( 217 | child: Transform.translate( 218 | offset: Offset( 219 | -largeXaxis, 220 | -largeYaxis, 221 | ), 222 | child: LargeBlob( 223 | width: width, 224 | color: SColors.red, 225 | scale: zeroToOne, 226 | ), 227 | ), 228 | ), 229 | Center( 230 | child: Transform.translate( 231 | offset: Offset( 232 | -smallXaxis, 233 | -smallYaxis, 234 | ), 235 | child: SmallBlob( 236 | width: width, 237 | color: SColors.red, 238 | turns: 2, 239 | radius: yellowRadius, 240 | ), 241 | ), 242 | ), 243 | Center( 244 | child: Transform.translate( 245 | offset: Offset( 246 | -largeYaxis, 247 | largeXaxis, 248 | ), 249 | child: RotatedBox( 250 | quarterTurns: 1, 251 | child: LargeBlob( 252 | width: width, 253 | color: SColors.yellow, 254 | scale: zeroToOne, 255 | ), 256 | ), 257 | ), 258 | ), 259 | Center( 260 | child: Transform.translate( 261 | offset: Offset( 262 | -smallYaxis, 263 | smallXaxis, 264 | ), 265 | child: SmallBlob( 266 | width: width, 267 | color: SColors.yellow, 268 | turns: 1, 269 | radius: yellowRadius, 270 | ), 271 | ), 272 | ), 273 | ], 274 | ), 275 | ), 276 | ); 277 | } 278 | } 279 | 280 | class SmallBlob extends StatelessWidget { 281 | const SmallBlob({ 282 | Key? key, 283 | required this.width, 284 | required this.color, 285 | required this.turns, 286 | required this.radius, 287 | }) : super(key: key); 288 | 289 | final double width; 290 | final Color color; 291 | final int turns; 292 | final double radius; 293 | 294 | @override 295 | Widget build(BuildContext context) { 296 | return RotatedBox( 297 | quarterTurns: turns, 298 | child: Container( 299 | width: width, 300 | child: AspectRatio( 301 | aspectRatio: 1, 302 | child: Container( 303 | decoration: BoxDecoration( 304 | color: color, 305 | borderRadius: BorderRadius.only( 306 | topLeft: Radius.circular(width / 2), 307 | topRight: Radius.circular(width / 2), 308 | bottomRight: Radius.circular(width / 2), 309 | bottomLeft: Radius.circular(radius), 310 | ), 311 | ), 312 | ), 313 | ), 314 | ), 315 | ); 316 | } 317 | } 318 | 319 | class LargeBlob extends StatelessWidget { 320 | const LargeBlob({ 321 | Key? key, 322 | required this.width, 323 | required this.color, 324 | required this.scale, 325 | }) : super(key: key); 326 | 327 | final double width; 328 | final double scale; 329 | final Color color; 330 | 331 | @override 332 | Widget build(BuildContext context) { 333 | return Container( 334 | width: width, 335 | height: width / (0.45 + scale * 0.55), 336 | decoration: ShapeDecoration( 337 | color: color, 338 | shape: StadiumBorder(), 339 | ), 340 | ); 341 | } 342 | } 343 | 344 | class SColors { 345 | static const Color red = Color(0xFFE01E5A); 346 | static const Color blue = Color(0xFF36C5F0); 347 | static const Color yellow = Color(0xFFECB22E); 348 | static const Color green = Color(0xFF2EB67D); 349 | } 350 | 351 | class DecreasingCircleClipper extends CustomClipper { 352 | final double radius; 353 | 354 | DecreasingCircleClipper(this.radius); 355 | @override 356 | Path getClip(Size size) { 357 | final path = Path(); 358 | final oval = Rect.fromCircle( 359 | center: Offset( 360 | size.width / 2, 361 | size.height / 2, 362 | ), 363 | radius: radius, 364 | ); 365 | path.addOval(oval); 366 | path.close(); 367 | return path; 368 | } 369 | 370 | @override 371 | bool shouldReclip(DecreasingCircleClipper oldClipper) => true; 372 | } 373 | -------------------------------------------------------------------------------- /lib/pages/explicit_animations.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'explicit/explicit.dart'; 4 | 5 | class ExplicitAnimations extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return ColoredBox( 9 | color: Colors.blue, 10 | child: GridView.count( 11 | crossAxisCount: 2, 12 | children: [ 13 | AvatarAnimation(), 14 | LogoWrapper(), 15 | SlackLogo(), 16 | DotsLoader(), 17 | AnimatedRadiatingIcon(), 18 | ], 19 | ), 20 | ); 21 | } 22 | } 23 | 24 | class LogoWrapper extends StatelessWidget { 25 | const LogoWrapper({ 26 | Key? key, 27 | }) : super(key: key); 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return LayoutBuilder(builder: (context, size) { 32 | return ClipRect( 33 | child: ColoredBox( 34 | color: Colors.white, 35 | child: FlutterAnimatedLogo(size: size.biggest.width), 36 | ), 37 | ); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/pages/funvas.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:funvas/funvas.dart'; 3 | import 'package:url_launcher/url_launcher.dart'; 4 | 5 | class FunvasDemo extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | final funvas = 'https://github.com/creativecreatorormaybenot/funvas'; 9 | return Column( 10 | children: [ 11 | Expanded( 12 | child: AspectRatio( 13 | aspectRatio: 1, 14 | child: FunvasContainer( 15 | funvas: OrbsFunvas(), 16 | ), 17 | ), 18 | ), 19 | Padding( 20 | padding: const EdgeInsets.all(16.0), 21 | child: InkWell( 22 | onTap: () async { 23 | if (await canLaunch(funvas)) { 24 | launch(funvas); 25 | } 26 | }, 27 | child: Text(funvas), 28 | ), 29 | ), 30 | ], 31 | ); 32 | } 33 | } 34 | 35 | class OrbsFunvas extends Funvas { 36 | @override 37 | void u(double t) { 38 | c.scale(x.width / 1920, x.height / 1080); 39 | 40 | final v = t + 400; 41 | for (var q = 255; q > 0; q--) { 42 | final paint = Paint()..color = R(q, q, q); 43 | c.drawCircle( 44 | Offset( 45 | 1920 / 2 + C(v - q) * (v + q), 46 | 540 + S(v - q) * (v - q), 47 | ), 48 | 40, 49 | paint); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/pages/gravity_simulation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/physics.dart'; 3 | 4 | class GravitySimulationWidget extends StatefulWidget { 5 | _GravitySimulationWidget createState() => _GravitySimulationWidget(); 6 | } 7 | 8 | class _GravitySimulationWidget extends State 9 | with TickerProviderStateMixin { 10 | late BallGame game; 11 | @override 12 | void initState() { 13 | super.initState(); 14 | game = BallGame( 15 | this, 16 | bounceFactor: 0.7, 17 | ); 18 | } 19 | 20 | @override 21 | Widget build(BuildContext context) { 22 | return Padding( 23 | padding: const EdgeInsets.only( 24 | bottom: 16.0, 25 | top: 64.0, 26 | ), 27 | child: AnimatedBuilder( 28 | animation: game, 29 | builder: (context, child) { 30 | return CustomPaint( 31 | painter: BallPainter(game.yPosition), 32 | child: SizedBox.expand( 33 | child: Align( 34 | alignment: Alignment.bottomRight, 35 | child: ElevatedButton( 36 | onPressed: () => game.start(), 37 | child: Text('Drop The Ball'), 38 | ), 39 | ), 40 | ), 41 | ); 42 | }, 43 | ), 44 | ); 45 | } 46 | 47 | @override 48 | void dispose() { 49 | game.dispose(); 50 | super.dispose(); 51 | } 52 | } 53 | 54 | const acc = 9.81; 55 | const scaleFactor = 10; 56 | 57 | class BallGame extends ChangeNotifier { 58 | BallGame( 59 | this.vsync, { 60 | this.bounceFactor = 0.7, 61 | }); 62 | 63 | AnimationController? controller; 64 | late GravitySimulation simulation; 65 | 66 | double get yPosition => controller?.value ?? _position; 67 | double _position = 0.0; 68 | double _velocity = 0.0; 69 | final TickerProvider vsync; 70 | final double bounceFactor; 71 | 72 | void onTick() { 73 | final currentVelocity = simulation.dx(controller!.velocity); 74 | _position = controller!.value; 75 | if (controller!.isCompleted) { 76 | simulation = GravitySimulation( 77 | acc, 78 | 9.999, // must be different than the target value 79 | 10, 80 | -_velocity * bounceFactor / scaleFactor, 81 | ); 82 | stop(); 83 | if (_velocity.abs() > 1) { 84 | bounce(); 85 | } 86 | } 87 | _velocity = currentVelocity; 88 | notifyListeners(); 89 | } 90 | 91 | void bounce() { 92 | controller = AnimationController( 93 | vsync: vsync, 94 | upperBound: 1500, 95 | ); 96 | controller!.addListener(onTick); 97 | controller!.animateWith(simulation); 98 | } 99 | 100 | void start() { 101 | controller = AnimationController( 102 | vsync: vsync, 103 | upperBound: 1500, 104 | ); 105 | simulation = GravitySimulation( 106 | acc, 107 | 0, 108 | 10, 109 | 0, 110 | ); 111 | bounce(); 112 | } 113 | 114 | void stop() { 115 | controller?.dispose(); 116 | controller = null; 117 | } 118 | 119 | @override 120 | void dispose() { 121 | super.dispose(); 122 | controller?.dispose(); 123 | } 124 | } 125 | 126 | class BallPainter extends CustomPainter { 127 | BallPainter(this.yPosition); 128 | 129 | final double yPosition; 130 | 131 | @override 132 | void paint(Canvas canvas, Size size) { 133 | final verticalUnit = size.height / scaleFactor; 134 | 135 | drawTicks(size, verticalUnit, canvas); 136 | canvas.drawOval( 137 | Rect.fromCenter( 138 | center: Offset( 139 | size.center(Offset.zero).dx, 140 | yPosition * verticalUnit - 25, 141 | ), 142 | width: 50, 143 | height: 50, 144 | ), 145 | Paint(), 146 | ); 147 | } 148 | 149 | void drawTicks(Size size, double verticalUnit, Canvas canvas) { 150 | for (final i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) { 151 | final yPos = size.height - i * verticalUnit; 152 | canvas.drawLine( 153 | Offset(0, yPos), 154 | Offset(20, yPos), 155 | Paint()..color = Colors.black, 156 | ); 157 | final TextPainter textPainter = TextPainter( 158 | text: TextSpan( 159 | text: i.toString(), 160 | style: TextStyle( 161 | color: Colors.black, 162 | fontSize: 12, 163 | ), 164 | ), 165 | textAlign: TextAlign.end, 166 | textDirection: TextDirection.ltr, 167 | )..layout(minWidth: 20); 168 | textPainter.paint(canvas, Offset(0, yPos - 16)); 169 | } 170 | canvas.drawLine( 171 | Offset(0, size.height), 172 | Offset(size.width, size.height), 173 | Paint()..color = Colors.black, 174 | ); 175 | } 176 | 177 | @override 178 | bool shouldRepaint(covariant BallPainter oldDelegate) { 179 | return yPosition != oldDelegate.yPosition; 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /lib/pages/helpers/staggered_animations_widgets.dart: -------------------------------------------------------------------------------- 1 | part of '../staggered_animations.dart'; 2 | 3 | class _Row3 extends StatelessWidget { 4 | const _Row3({ 5 | Key? key, 6 | }) : super(key: key); 7 | 8 | @override 9 | Widget build(BuildContext context) { 10 | final children = [ 11 | Flexible( 12 | child: SmartCard( 13 | child: Column( 14 | crossAxisAlignment: CrossAxisAlignment.start, 15 | children: [ 16 | Text( 17 | 'Parking', 18 | style: Theme.of(context).textTheme.headline5, 19 | ), 20 | const Gap(16), 21 | _Indicator(), 22 | const Gap(16), 23 | const Center( 24 | child: Icon( 25 | LineIcons.car, 26 | size: 48, 27 | ), 28 | ) 29 | ], 30 | ), 31 | ), 32 | ), 33 | Flexible( 34 | child: SmartCard( 35 | child: Column( 36 | crossAxisAlignment: CrossAxisAlignment.start, 37 | children: [ 38 | Text( 39 | 'Kitchen', 40 | style: Theme.of(context).textTheme.headline5, 41 | ), 42 | const Gap(16), 43 | _Indicator(), 44 | const Gap(16), 45 | const Center( 46 | child: Icon( 47 | LineIcons.cookie, 48 | size: 48, 49 | ), 50 | ) 51 | ], 52 | ), 53 | ), 54 | ), 55 | ]; 56 | 57 | return LayoutBuilder( 58 | builder: (context, size) { 59 | if (size.maxWidth > 600) { 60 | return Row( 61 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 62 | children: children, 63 | ); 64 | } else { 65 | return Column( 66 | mainAxisSize: MainAxisSize.min, 67 | children: children, 68 | ); 69 | } 70 | }, 71 | ); 72 | } 73 | } 74 | 75 | class _Row2 extends StatelessWidget { 76 | const _Row2({ 77 | Key? key, 78 | }) : super(key: key); 79 | 80 | @override 81 | Widget build(BuildContext context) { 82 | final children = [ 83 | Flexible( 84 | // constraints: BoxConstraints(maxWidth: 500), 85 | child: SmartCard( 86 | child: Column( 87 | crossAxisAlignment: CrossAxisAlignment.start, 88 | children: [ 89 | Text( 90 | 'Living Room', 91 | style: Theme.of(context).textTheme.headline5, 92 | ), 93 | const Gap(16), 94 | _Indicator(), 95 | const Gap(16), 96 | SizedBox( 97 | height: 64, 98 | child: LineChart( 99 | sampleData(), 100 | ), 101 | ), 102 | ], 103 | ), 104 | ), 105 | ), 106 | Flexible( 107 | // constraints: BoxConstraints(maxWidth: 500), 108 | child: SmartCard( 109 | child: Column( 110 | crossAxisAlignment: CrossAxisAlignment.start, 111 | children: [ 112 | Text( 113 | 'Bedroom', 114 | style: Theme.of(context).textTheme.headline5, 115 | ), 116 | const Gap(16), 117 | _Indicator(), 118 | const Gap(16), 119 | SizedBox( 120 | height: 64, 121 | child: LineChart( 122 | sampleData(), 123 | ), 124 | ), 125 | ], 126 | ), 127 | ), 128 | ), 129 | ]; 130 | return LayoutBuilder( 131 | builder: (context, size) { 132 | if (size.maxWidth > 600) { 133 | return Row( 134 | mainAxisAlignment: MainAxisAlignment.spaceBetween, 135 | children: children, 136 | ); 137 | } else { 138 | return Column( 139 | mainAxisSize: MainAxisSize.min, 140 | children: children, 141 | ); 142 | } 143 | }, 144 | ); 145 | } 146 | } 147 | 148 | class _Row1 extends StatelessWidget { 149 | const _Row1({ 150 | Key? key, 151 | }) : super(key: key); 152 | 153 | @override 154 | Widget build(BuildContext context) { 155 | return Row( 156 | children: [ 157 | Expanded( 158 | child: SmartCard( 159 | child: Column( 160 | crossAxisAlignment: CrossAxisAlignment.start, 161 | children: [ 162 | Text( 163 | 'Family Room', 164 | style: Theme.of(context).textTheme.headline5, 165 | ), 166 | Gap(16), 167 | OverflowBar( 168 | spacing: 16, 169 | children: [ 170 | _Indicator(), 171 | Gap(16), 172 | _Indicator(), 173 | ], 174 | ), 175 | Gap(16), 176 | OverflowBar( 177 | spacing: 16, 178 | children: [ 179 | _Indicator(), 180 | Gap(16), 181 | _Indicator(), 182 | ], 183 | ), 184 | ], 185 | ), 186 | ), 187 | ), 188 | ], 189 | ); 190 | } 191 | } 192 | 193 | class _Header extends StatelessWidget { 194 | const _Header({ 195 | Key? key, 196 | }) : super(key: key); 197 | 198 | @override 199 | Widget build(BuildContext context) { 200 | return Padding( 201 | padding: const EdgeInsets.symmetric(horizontal: 48.0), 202 | child: Row( 203 | children: [ 204 | const CircleAvatar( 205 | child: Text('JS'), 206 | ), 207 | Gap(16), 208 | Text( 209 | 'John Smith', 210 | style: Theme.of(context).textTheme.headline5, 211 | ), 212 | ], 213 | ), 214 | ); 215 | } 216 | } 217 | 218 | class _Indicator extends StatelessWidget { 219 | const _Indicator({ 220 | Key? key, 221 | }) : super(key: key); 222 | 223 | @override 224 | Widget build(BuildContext context) { 225 | return Row( 226 | mainAxisSize: MainAxisSize.min, 227 | children: [ 228 | const Icon(LineIcons.batteryAlt2Full), 229 | const Gap(16), 230 | Column( 231 | crossAxisAlignment: CrossAxisAlignment.start, 232 | children: const [ 233 | Text('42%'), 234 | Text( 235 | 'John\'s battery', 236 | overflow: TextOverflow.ellipsis, 237 | ), 238 | ], 239 | ), 240 | ], 241 | ); 242 | } 243 | } 244 | 245 | class SmartCard extends StatelessWidget { 246 | const SmartCard({Key? key, this.child}) : super(key: key); 247 | final Widget? child; 248 | 249 | @override 250 | Widget build(BuildContext context) { 251 | return Padding( 252 | padding: const EdgeInsets.all(32.0), 253 | child: DecoratedBox( 254 | decoration: BoxDecoration( 255 | borderRadius: BorderRadius.circular(32), 256 | color: Colors.white, 257 | boxShadow: [ 258 | const BoxShadow( 259 | color: Colors.black12, 260 | blurRadius: 32, 261 | offset: Offset(6, 6), 262 | ), 263 | ], 264 | ), 265 | child: Padding( 266 | padding: const EdgeInsets.all(32.0), 267 | child: child, 268 | ), 269 | ), 270 | ); 271 | } 272 | } 273 | 274 | LineChartData sampleData() { 275 | return LineChartData( 276 | gridData: FlGridData( 277 | show: false, 278 | ), 279 | titlesData: FlTitlesData( 280 | topTitles: SideTitles(showTitles: false), 281 | bottomTitles: SideTitles( 282 | showTitles: true, 283 | reservedSize: 6, 284 | getTextStyles: (context, value) => const TextStyle( 285 | color: Colors.black38, 286 | fontWeight: FontWeight.bold, 287 | fontSize: 11, 288 | ), 289 | interval: 1, 290 | // margin: 10, 291 | getTitles: (value) { 292 | switch (value.toInt() % 7) { 293 | case 1: 294 | return 'MON'; 295 | // case 2: 296 | // return 'TUE'; 297 | case 3: 298 | return 'WED'; 299 | // case 4: 300 | // return 'THU'; 301 | case 5: 302 | return 'FRI'; 303 | case 7: 304 | return 'SUN'; 305 | } 306 | return ''; 307 | }, 308 | ), 309 | rightTitles: SideTitles(showTitles: false), 310 | leftTitles: SideTitles( 311 | showTitles: true, 312 | getTextStyles: (context, value) => const TextStyle( 313 | color: Colors.black38, 314 | fontWeight: FontWeight.bold, 315 | fontSize: 11, 316 | ), 317 | 318 | interval: 50, 319 | // getTitles: (value) { 320 | // switch (value.toInt()) { 321 | // case 1: 322 | // return ''; 323 | // case 2: 324 | // return '20'; 325 | // case 3: 326 | // return ''; 327 | // case 4: 328 | // return '22'; 329 | // } 330 | // return ''; 331 | // }, 332 | margin: 4, 333 | reservedSize: 30, 334 | ), 335 | ), 336 | borderData: FlBorderData( 337 | show: true, 338 | border: const Border( 339 | bottom: BorderSide( 340 | color: Color(0xff4e4965), 341 | width: 1, 342 | ), 343 | left: BorderSide( 344 | color: Colors.transparent, 345 | ), 346 | right: BorderSide( 347 | color: Colors.transparent, 348 | ), 349 | top: BorderSide( 350 | color: Colors.transparent, 351 | ), 352 | ), 353 | ), 354 | minX: 0, 355 | maxX: 14, 356 | maxY: 100, 357 | minY: 0, 358 | lineBarsData: linesBarData1(), 359 | ); 360 | } 361 | 362 | List linesBarData1() { 363 | final LineChartBarData lineChartBarData1 = LineChartBarData( 364 | spots: [ 365 | FlSpot(1, 80), 366 | FlSpot(2, 40), 367 | FlSpot(3, 30), 368 | FlSpot(4, 60), 369 | FlSpot(5, 80), 370 | FlSpot(6, 70), 371 | FlSpot(8, 30), 372 | FlSpot(9, 100), 373 | FlSpot(10, 85), 374 | FlSpot(11, 64), 375 | FlSpot(12, 62), 376 | FlSpot(13, 49), 377 | ], 378 | isCurved: true, 379 | colors: [ 380 | Colors.black54, 381 | ], 382 | barWidth: 4, 383 | isStrokeCapRound: true, 384 | dotData: FlDotData( 385 | show: false, 386 | ), 387 | belowBarData: BarAreaData( 388 | show: false, 389 | ), 390 | ); 391 | 392 | return [ 393 | lineChartBarData1, 394 | ]; 395 | } 396 | -------------------------------------------------------------------------------- /lib/pages/implicit/implicit.dart: -------------------------------------------------------------------------------- 1 | export 'implicit_counter.dart'; 2 | export 'implicit_loader.dart'; 3 | -------------------------------------------------------------------------------- /lib/pages/implicit/implicit_counter.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SmoothCounter extends ImplicitlyAnimatedWidget { 4 | SmoothCounter({ 5 | Key? key, 6 | required this.progress, 7 | Duration duration = const Duration(milliseconds: 700), 8 | Curve curve = Curves.easeOutCubic, 9 | this.style, 10 | }) : super(duration: duration, curve: curve, key: key); 11 | 12 | final double progress; 13 | final TextStyle? style; 14 | 15 | @override 16 | ImplicitlyAnimatedWidgetState createState() => 17 | _SmoothLoadingIndicatorState(); 18 | } 19 | 20 | class _SmoothLoadingIndicatorState 21 | extends AnimatedWidgetBaseState { 22 | Tween? _progress; 23 | 24 | @override 25 | Widget build(BuildContext context) { 26 | final value = _progress?.evaluate(animation); 27 | return SizedBox( 28 | width: 120, 29 | child: Text( 30 | (value != null ? value.toStringAsFixed(0) : '0') + '%', 31 | style: widget.style ?? 32 | TextStyle( 33 | fontSize: 46, 34 | fontWeight: FontWeight.bold, 35 | fontFamily: 'Fira Code', 36 | ), 37 | ), 38 | ); 39 | } 40 | 41 | @override 42 | void forEachTween(TweenVisitor visitor) { 43 | _progress = visitor( 44 | _progress, 45 | widget.progress, 46 | (dynamic value) => Tween(begin: value as double?), 47 | ) as Tween?; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /lib/pages/implicit/implicit_loader.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class SmoothLoadingIndicator extends ImplicitlyAnimatedWidget { 4 | SmoothLoadingIndicator({ 5 | Key? key, 6 | required this.progress, 7 | Duration duration = const Duration(milliseconds: 200), 8 | Curve curve = Curves.linear, 9 | this.color = Colors.blue, 10 | this.backgroundColor = const Color(0xFFBBDEFB), 11 | }) : super(duration: duration, curve: curve, key: key); 12 | 13 | final double progress; 14 | final Color color; 15 | final Color backgroundColor; 16 | 17 | @override 18 | ImplicitlyAnimatedWidgetState createState() => 19 | _SmoothLoadingIndicatorState(); 20 | } 21 | 22 | class _SmoothLoadingIndicatorState 23 | extends AnimatedWidgetBaseState { 24 | Tween? _progress; 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | return CircularProgressIndicator( 29 | backgroundColor: widget.backgroundColor, 30 | color: widget.color, 31 | value: _progress!.evaluate(animation), 32 | strokeWidth: 15, 33 | ); 34 | } 35 | 36 | @override 37 | void forEachTween(TweenVisitor visitor) { 38 | _progress = visitor( 39 | _progress, 40 | (widget.progress).clamp(0.0, 1.0), 41 | (dynamic value) => Tween(begin: value as double?), 42 | ) as Tween?; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/pages/implicit_animations.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | import 'package:animations_sample/pages/implicit/implicit_counter.dart'; 3 | import 'package:animations_sample/pages/implicit/implicit_loader.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | class ImplicitAnimations extends StatefulWidget { 7 | @override 8 | _ImplicitAnimationsState createState() => _ImplicitAnimationsState(); 9 | } 10 | 11 | class _ImplicitAnimationsState extends State { 12 | Alignment alignment = Alignment.center; 13 | 14 | int index = 0; 15 | final colors = [Colors.blue, Colors.deepPurple, Colors.green]; 16 | final bColors = [Colors.transparent, Colors.purple, Colors.green[800]]; 17 | final sizes = [150.0, 195.0, 300.0]; 18 | final tSizes = [12.0, 16.0, 20.0]; 19 | final radius = [0.0, 16.0, 90.0]; 20 | final elevations = [0.0, 16.0, 32.0]; 21 | final opacity = [1.0, 0.3, 0.01]; 22 | 23 | @override 24 | Widget build(BuildContext context) { 25 | final text = 'Size: ${sizes[index]}, Radius: ${radius[index]}, ' 26 | 'Elevation: ${elevations[index]}'; 27 | return ListView( 28 | children: [ 29 | Text(text), 30 | Center(child: LoaderDemo()), 31 | OverflowBar( 32 | alignment: MainAxisAlignment.spaceEvenly, 33 | children: [ 34 | InkWell( 35 | onTap: () { 36 | setState(() { 37 | index = (index + 1) % colors.length; 38 | }); 39 | }, 40 | child: AnimatedPhysicalModel( 41 | child: Padding( 42 | padding: const EdgeInsets.all(32.0), 43 | child: Text('AnimatedPhysicalModel'), 44 | ), 45 | duration: kThemeAnimationDuration, 46 | shape: BoxShape.rectangle, 47 | elevation: elevations[index], 48 | color: Colors.blue[100]!, 49 | shadowColor: colors[index], 50 | ), 51 | ), 52 | AnimatedContainer( 53 | width: sizes[index], 54 | height: sizes[index], 55 | decoration: BoxDecoration( 56 | borderRadius: BorderRadius.circular(radius[index]), 57 | color: colors[index], 58 | boxShadow: [ 59 | BoxShadow( 60 | color: Colors.black54, 61 | blurRadius: radius[index], 62 | ), 63 | ], 64 | border: Border.all( 65 | color: bColors[index]!, 66 | width: index * 10.0, 67 | ), 68 | ), 69 | duration: kThemeAnimationDuration, 70 | curve: Curves.easeInOut, 71 | child: InkWell( 72 | onTap: () { 73 | setState(() { 74 | index = (index + 1) % colors.length; 75 | }); 76 | }, 77 | child: Center( 78 | child: AnimatedDefaultTextStyle( 79 | duration: kThemeAnimationDuration, 80 | style: TextStyle(fontSize: tSizes[index]), 81 | child: Text( 82 | 'AnimatedContainer', 83 | textAlign: TextAlign.center, 84 | ), 85 | ), 86 | ), 87 | ), 88 | ), 89 | AnimatedOpacity( 90 | opacity: opacity[index], 91 | duration: kThemeAnimationDuration, 92 | child: InkWell( 93 | onTap: () { 94 | setState(() { 95 | index = (index + 1) % colors.length; 96 | }); 97 | }, 98 | child: Padding( 99 | padding: const EdgeInsets.all(32.0), 100 | child: Text('AnimatedOpacity ${opacity[index]}'), 101 | ), 102 | ), 103 | ), 104 | ], 105 | ), 106 | SizedBox( 107 | height: 400, 108 | child: Stack( 109 | children: [ 110 | AnimatedAlign( 111 | child: TextButton( 112 | onPressed: () { 113 | setState(() { 114 | final random1 = (math.Random().nextDouble() - 0.5); 115 | final random2 = (math.Random().nextDouble() - 0.5); 116 | alignment = Alignment(random1, random2); 117 | }); 118 | }, 119 | style: 120 | TextButton.styleFrom(textStyle: TextStyle(fontSize: 24)), 121 | child: Padding( 122 | padding: const EdgeInsets.all(16.0), 123 | child: Text('AnimatedAlign $alignment'), 124 | ), 125 | ), 126 | duration: kThemeAnimationDuration, 127 | alignment: alignment, 128 | ), 129 | ], 130 | ), 131 | ), 132 | ], 133 | ); 134 | } 135 | } 136 | 137 | class LoaderDemo extends StatefulWidget { 138 | const LoaderDemo({ 139 | Key? key, 140 | }) : super(key: key); 141 | 142 | @override 143 | _LoaderDemoState createState() => _LoaderDemoState(); 144 | } 145 | 146 | class _LoaderDemoState extends State { 147 | double progress = 0.0; 148 | 149 | @override 150 | Widget build(BuildContext context) { 151 | return Padding( 152 | padding: const EdgeInsets.all(32.0), 153 | child: OverflowBar( 154 | spacing: 16, 155 | children: [ 156 | OutlinedButton( 157 | onPressed: () { 158 | setState(() { 159 | progress = progress + 0.1; 160 | }); 161 | if (progress >= 1.0) { 162 | setState(() { 163 | progress = 0.0; 164 | }); 165 | } 166 | }, 167 | child: Text('Progress: ${progress.toStringAsFixed(2)}'), 168 | ), 169 | SmoothCounter(progress: progress * 100), 170 | SizedBox( 171 | width: 50, 172 | height: 50, 173 | child: SmoothLoadingIndicator( 174 | progress: progress, 175 | ), 176 | ), 177 | ], 178 | ), 179 | ); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /lib/pages/material.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'material/example.dart'; 4 | 5 | class MaterialAnimationsDemo extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) { 8 | return OpenContainerTransformDemo(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/pages/page.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppPage { 4 | AppPage(this.title, this.widget); 5 | final String title; 6 | final Widget widget; 7 | } 8 | -------------------------------------------------------------------------------- /lib/pages/pages.dart: -------------------------------------------------------------------------------- 1 | export 'about.dart'; 2 | export 'custom_controller.dart'; 3 | export 'explicit_animations.dart'; 4 | export 'funvas.dart'; 5 | export 'implicit_animations.dart'; 6 | export 'material.dart'; 7 | export 'page.dart'; 8 | export 'spring_simulation.dart'; 9 | export 'staggered_animations.dart'; 10 | export 'tween_animation_builder.dart'; 11 | export 'tweens.dart'; 12 | -------------------------------------------------------------------------------- /lib/pages/spring_simulation.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/physics.dart'; 3 | 4 | class PhysicsAnimation extends StatefulWidget { 5 | _PhysicsAnimation createState() => _PhysicsAnimation(); 6 | } 7 | 8 | class _PhysicsAnimation extends State 9 | with SingleTickerProviderStateMixin { 10 | late AnimationController controller; 11 | 12 | late SpringSimulation simulation; 13 | 14 | @override 15 | void initState() { 16 | super.initState(); 17 | 18 | simulation = SpringSimulation( 19 | SpringDescription( 20 | mass: 2, 21 | stiffness: 100, 22 | damping: 1, 23 | ), 24 | 0.0, 25 | 500.0, 26 | 10, 27 | ); 28 | 29 | controller = AnimationController( 30 | vsync: this, 31 | upperBound: 1500, 32 | ); 33 | 34 | controller.animateWith(simulation); 35 | } 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return AnimatedBuilder( 40 | animation: controller, 41 | builder: (context, child) { 42 | return Stack( 43 | children: [ 44 | Positioned( 45 | top: controller.value, 46 | left: 0, 47 | right: 0, 48 | child: Container( 49 | height: 100, 50 | color: Colors.redAccent, 51 | ), 52 | ), 53 | ], 54 | ); 55 | }, 56 | ); 57 | } 58 | 59 | @override 60 | void dispose() { 61 | controller.dispose(); 62 | super.dispose(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/pages/staggered_animations.dart: -------------------------------------------------------------------------------- 1 | import 'package:fl_chart/fl_chart.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:gap/gap.dart'; 4 | import 'package:google_fonts/google_fonts.dart'; 5 | import 'package:line_icons/line_icons.dart'; 6 | 7 | part 'helpers/staggered_animations_widgets.dart'; 8 | 9 | class StaggeredAnimations extends StatefulWidget { 10 | @override 11 | _StaggeredAnimationsState createState() => _StaggeredAnimationsState(); 12 | } 13 | 14 | class _StaggeredAnimationsState extends State 15 | with TickerProviderStateMixin { 16 | late AnimationController animationController; 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | animationController = AnimationController( 22 | vsync: this, 23 | duration: Duration(seconds: 4), 24 | ) 25 | ..forward() 26 | ..repeat(); 27 | } 28 | 29 | @override 30 | void dispose() { 31 | animationController.dispose(); 32 | super.dispose(); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return Theme( 38 | data: Theme.of(context).copyWith( 39 | textTheme: GoogleFonts.ralewayTextTheme(Theme.of(context).textTheme), 40 | ), 41 | child: PageLayout(controller: animationController), 42 | ); 43 | } 44 | } 45 | 46 | class PageLayout extends StatelessWidget { 47 | const PageLayout({ 48 | Key? key, 49 | required this.controller, 50 | }) : super(key: key); 51 | 52 | final AnimationController controller; 53 | 54 | @override 55 | Widget build(BuildContext context) { 56 | return SingleChildScrollView( 57 | child: Stack( 58 | children: [ 59 | HomePageAnimatedBuilder( 60 | animation: controller, 61 | builder: (context, child, animation) { 62 | return Column( 63 | children: [ 64 | Gap(16), 65 | Opacity( 66 | opacity: animation.headerOpacity.value, 67 | child: const _Header(), 68 | ), 69 | SlideTransition( 70 | position: animation.row1Offset, 71 | child: const _Row1(), 72 | ), 73 | SlideTransition( 74 | position: animation.row2Offset, 75 | child: const _Row2(), 76 | ), 77 | SlideTransition( 78 | position: animation.row3Offset, 79 | child: const _Row3(), 80 | ), 81 | ], 82 | ); 83 | }, 84 | ), 85 | Positioned( 86 | right: 20, 87 | top: 20, 88 | child: IconButton( 89 | onPressed: () { 90 | if (controller.isAnimating) { 91 | controller.stop(); 92 | } else { 93 | controller 94 | ..forward() 95 | ..repeat(); 96 | } 97 | }, 98 | icon: Icon(Icons.pause_circle), 99 | ), 100 | ) 101 | ], 102 | ), 103 | ); 104 | } 105 | } 106 | 107 | class HomePageAnimatedBuilder extends StatelessWidget { 108 | const HomePageAnimatedBuilder({ 109 | Key? key, 110 | required this.builder, 111 | required this.animation, 112 | this.child, 113 | }) : super(key: key); 114 | 115 | final MyTransitionBuilder builder; 116 | final Listenable animation; 117 | final Widget? child; 118 | 119 | @override 120 | Widget build(BuildContext context) { 121 | return AnimatedBuilder( 122 | animation: animation, 123 | builder: (context, child) { 124 | return builder( 125 | context, 126 | child, 127 | HomePageEnterAnimation(animation as AnimationController), 128 | ); 129 | }, 130 | child: child, 131 | ); 132 | } 133 | } 134 | 135 | typedef MyTransitionBuilder = Widget Function( 136 | BuildContext context, 137 | Widget? child, 138 | HomePageEnterAnimation animation, 139 | ); 140 | 141 | class HomePageEnterAnimation { 142 | HomePageEnterAnimation(this.controller) 143 | : headerOpacity = Tween(begin: 0, end: 1.0).animate( 144 | CurvedAnimation( 145 | parent: controller, 146 | curve: Interval(0, 0.2, curve: Curves.easeIn), 147 | ), 148 | ), 149 | row1Offset = 150 | Tween(begin: Offset(0, 5), end: Offset.zero).animate( 151 | CurvedAnimation( 152 | parent: controller, 153 | curve: Interval(0.1, 0.25, curve: Curves.easeOut), 154 | ), 155 | ), 156 | row2Offset = 157 | Tween(begin: Offset(0, 5), end: Offset.zero).animate( 158 | CurvedAnimation( 159 | parent: controller, 160 | curve: Interval(0.15, 0.30, curve: Curves.easeOut), 161 | ), 162 | ), 163 | row3Offset = 164 | Tween(begin: Offset(0, 5), end: Offset.zero).animate( 165 | CurvedAnimation( 166 | parent: controller, 167 | curve: Interval(0.2, 0.35, curve: Curves.easeOut), 168 | ), 169 | ); 170 | 171 | final AnimationController controller; 172 | final Animation headerOpacity; 173 | final Animation row1Offset; 174 | final Animation row2Offset; 175 | final Animation row3Offset; 176 | } 177 | -------------------------------------------------------------------------------- /lib/pages/tween_animation_builder.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import 'package:flutter/material.dart'; 4 | 5 | class TweenPageController extends StatefulWidget { 6 | @override 7 | _TweenPageControllerState createState() => _TweenPageControllerState(); 8 | } 9 | 10 | class _TweenPageControllerState extends State { 11 | double scale = 1.0; 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return ClipRect( 16 | child: Stack( 17 | children: [ 18 | Column( 19 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 20 | children: [ 21 | TweenAnimationDemo( 22 | scale: scale, 23 | ), 24 | TweenAnimationRotationDemo( 25 | scale: scale, 26 | ), 27 | ], 28 | ), 29 | Padding( 30 | padding: const EdgeInsets.all(32.0), 31 | child: Row( 32 | mainAxisAlignment: MainAxisAlignment.spaceEvenly, 33 | children: [ 34 | ElevatedButton( 35 | onPressed: () { 36 | setState(() { 37 | scale = 1.0; 38 | }); 39 | }, 40 | child: Text('x1.0'), 41 | ), 42 | ElevatedButton( 43 | onPressed: () { 44 | setState(() { 45 | scale = 10.0; 46 | }); 47 | }, 48 | child: Text('x10.0'), 49 | ), 50 | ElevatedButton( 51 | onPressed: () { 52 | setState(() { 53 | scale = 30.0; 54 | }); 55 | }, 56 | child: Text('x30.0'), 57 | ), 58 | ], 59 | ), 60 | ), 61 | ], 62 | ), 63 | ); 64 | } 65 | } 66 | 67 | class TweenAnimationDemo extends StatelessWidget { 68 | const TweenAnimationDemo({Key? key, this.scale}) : super(key: key); 69 | 70 | final double? scale; 71 | 72 | @override 73 | Widget build(BuildContext context) { 74 | return Center( 75 | child: TweenAnimationBuilder( 76 | duration: Duration(seconds: 2), 77 | tween: Tween(begin: 0.0, end: scale ?? 1.0), 78 | curve: Curves.easeInOut, 79 | builder: (context, dynamic value, child) { 80 | return Transform.scale( 81 | scale: value, 82 | child: child, 83 | ); 84 | }, 85 | child: Text( 86 | 'Hello Flutter Meetup!', 87 | textAlign: TextAlign.center, 88 | ), 89 | ), 90 | ); 91 | } 92 | } 93 | 94 | class TweenAnimationRotationDemo extends StatelessWidget { 95 | const TweenAnimationRotationDemo({Key? key, this.scale}) : super(key: key); 96 | 97 | final double? scale; 98 | 99 | @override 100 | Widget build(BuildContext context) { 101 | return Center( 102 | child: TweenAnimationBuilder( 103 | duration: Duration(seconds: 2), 104 | tween: Tween(begin: 0.0, end: scale ?? 1.0), 105 | curve: Curves.easeInOut, 106 | builder: (context, dynamic value, child) { 107 | return Transform.rotate( 108 | angle: (value - 1) / 5 * pi, 109 | child: child, 110 | ); 111 | }, 112 | child: FlutterLogo( 113 | size: 200, 114 | ), 115 | ), 116 | ); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /lib/pages/tweens.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:gap/gap.dart'; 3 | 4 | class Tweens extends StatefulWidget { 5 | @override 6 | _TweensState createState() => _TweensState(); 7 | } 8 | 9 | class _TweensState extends State with TickerProviderStateMixin { 10 | AnimationController? animationController; 11 | 12 | final curves = { 13 | 'Curves.linear': Curves.linear, 14 | 'Curves.bounceIn': Curves.bounceIn, 15 | 'Curves.decelerate': Curves.decelerate, 16 | 'Curves.ease': Curves.ease, 17 | 'Curves.easeInOut': Curves.easeInOut, 18 | 'Curves.easeOutBack': Curves.easeOutBack, 19 | 'Curves.easeOutExpo': Curves.easeOutExpo, 20 | 'Curves.easeOutSine': Curves.easeOutSine, 21 | 'Curves.easeInOutExpo': Curves.easeInOutExpo, 22 | 'Curves.easeInOutCubicEmphasized': Curves.easeInOutCubicEmphasized, 23 | 'Curves.elasticIn': Curves.elasticIn, 24 | 'Curves.fastLinearToSlowEaseIn': Curves.fastLinearToSlowEaseIn, 25 | 'Curves.slowMiddle': Curves.slowMiddle, 26 | }; 27 | 28 | @override 29 | void initState() { 30 | super.initState(); 31 | animationController = AnimationController( 32 | vsync: this, 33 | duration: Duration(seconds: 5), 34 | ) 35 | ..forward() 36 | ..repeat(); 37 | } 38 | 39 | @override 40 | void dispose() { 41 | animationController?.dispose(); 42 | super.dispose(); 43 | } 44 | 45 | Animation getTween(AnimationController _controller) { 46 | return Tween( 47 | begin: const Offset(-2.0, 0.0), 48 | end: const Offset(2.0, 0.0), 49 | ).animate(_controller); 50 | } 51 | 52 | Animation getCurvedTween( 53 | AnimationController _controller, Curve curve) { 54 | return Tween( 55 | begin: const Offset(-2.0, 0.0), 56 | end: const Offset(2.0, 0.0), 57 | ).animate( 58 | CurvedAnimation( 59 | parent: _controller, 60 | curve: curve, 61 | ), 62 | ); 63 | } 64 | 65 | @override 66 | Widget build(BuildContext context) { 67 | final widget = ListView.separated( 68 | itemCount: curves.entries.length, 69 | separatorBuilder: (_, __) => const Gap(16), 70 | itemBuilder: (context, index) { 71 | final curve = curves.entries.elementAt(index); 72 | return Stack( 73 | children: [ 74 | Center( 75 | child: AnimatedBuilder( 76 | animation: animationController!, 77 | builder: (context, child) { 78 | final position = 79 | getCurvedTween(animationController!, curve.value); 80 | return SlideTransition( 81 | position: position, 82 | child: child, 83 | ); 84 | }, 85 | child: Container( 86 | height: 200, 87 | width: 200, 88 | color: Color.fromARGB( 89 | 255, 90 | 160 + 20 * (index % 3), 91 | 180 + 30 * (index % 2), 92 | 170 + 20 * (index % 5), 93 | ), 94 | ), 95 | ), 96 | ), 97 | SizedBox( 98 | height: 200, 99 | width: double.infinity, 100 | child: CustomPaint( 101 | painter: CurvePainter(curve.value), 102 | ), 103 | ), 104 | Align( 105 | alignment: Alignment.topLeft, 106 | child: Padding( 107 | padding: const EdgeInsets.all(8.0), 108 | child: Text( 109 | '${curve.key}', 110 | style: Theme.of(context).textTheme.headline6, 111 | ), 112 | ), 113 | ), 114 | ], 115 | ); 116 | }, 117 | ); 118 | 119 | return ClipRect( 120 | child: widget, 121 | ); 122 | } 123 | } 124 | 125 | class CurvePainter extends CustomPainter { 126 | CurvePainter(this.curve); 127 | 128 | final Curve curve; 129 | 130 | @override 131 | void paint(Canvas canvas, Size size) { 132 | final path = Path(); 133 | for (var i = 0; i < size.width; i++) { 134 | path.lineTo(i.toDouble(), curve.transform(i / size.width) * size.height); 135 | } 136 | canvas.drawPath( 137 | path, 138 | Paint() 139 | ..color = Colors.black45 140 | ..style = PaintingStyle.stroke 141 | ..strokeWidth = 2, 142 | ); 143 | } 144 | 145 | @override 146 | bool shouldRepaint(covariant CustomPainter oldDelegate) { 147 | return true; 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | project(runner LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "animations") 5 | set(APPLICATION_ID "dev.roszkowski.animations") 6 | 7 | cmake_policy(SET CMP0063 NEW) 8 | 9 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 10 | 11 | # Configure build options. 12 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 13 | set(CMAKE_BUILD_TYPE "Debug" CACHE 14 | STRING "Flutter build mode" FORCE) 15 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 16 | "Debug" "Profile" "Release") 17 | endif() 18 | 19 | # Compilation settings that should be applied to most targets. 20 | function(APPLY_STANDARD_SETTINGS TARGET) 21 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 22 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 23 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 24 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 25 | endfunction() 26 | 27 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 28 | 29 | # Flutter library and tool build rules. 30 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 31 | 32 | # System-level dependencies. 33 | find_package(PkgConfig REQUIRED) 34 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 35 | 36 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 37 | 38 | # Application build 39 | add_executable(${BINARY_NAME} 40 | "main.cc" 41 | "my_application.cc" 42 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 43 | ) 44 | apply_standard_settings(${BINARY_NAME}) 45 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 46 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 47 | add_dependencies(${BINARY_NAME} flutter_assemble) 48 | # Only the install-generated bundle's copy of the executable will launch 49 | # correctly, since the resources must in the right relative locations. To avoid 50 | # people trying to run the unbundled copy, put it in a subdirectory instead of 51 | # the default top-level location. 52 | set_target_properties(${BINARY_NAME} 53 | PROPERTIES 54 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 55 | ) 56 | 57 | # Generated plugin build rules, which manage building the plugins and adding 58 | # them to the application. 59 | include(flutter/generated_plugins.cmake) 60 | 61 | 62 | # === Installation === 63 | # By default, "installing" just makes a relocatable bundle in the build 64 | # directory. 65 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 66 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 67 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 68 | endif() 69 | 70 | # Start with a clean build bundle directory every time. 71 | install(CODE " 72 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 73 | " COMPONENT Runtime) 74 | 75 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 76 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 77 | 78 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 79 | COMPONENT Runtime) 80 | 81 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 82 | COMPONENT Runtime) 83 | 84 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 85 | COMPONENT Runtime) 86 | 87 | if(PLUGIN_BUNDLED_LIBRARIES) 88 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 89 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 90 | COMPONENT Runtime) 91 | endif() 92 | 93 | # Fully re-copy the assets directory on each build to avoid having stale files 94 | # from a previous install. 95 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 96 | install(CODE " 97 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 98 | " COMPONENT Runtime) 99 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 100 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 101 | 102 | # Install the AOT library on non-Debug builds only. 103 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 104 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 105 | COMPONENT Runtime) 106 | endif() 107 | -------------------------------------------------------------------------------- /linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | 11 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 12 | # which isn't available in 3.10. 13 | function(list_prepend LIST_NAME PREFIX) 14 | set(NEW_LIST "") 15 | foreach(element ${${LIST_NAME}}) 16 | list(APPEND NEW_LIST "${PREFIX}${element}") 17 | endforeach(element) 18 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 19 | endfunction() 20 | 21 | # === Flutter Library === 22 | # System-level dependencies. 23 | find_package(PkgConfig REQUIRED) 24 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 25 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 26 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 27 | pkg_check_modules(BLKID REQUIRED IMPORTED_TARGET blkid) 28 | pkg_check_modules(LZMA REQUIRED IMPORTED_TARGET liblzma) 29 | 30 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 31 | 32 | # Published to parent scope for install step. 33 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 34 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 35 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 36 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 37 | 38 | list(APPEND FLUTTER_LIBRARY_HEADERS 39 | "fl_basic_message_channel.h" 40 | "fl_binary_codec.h" 41 | "fl_binary_messenger.h" 42 | "fl_dart_project.h" 43 | "fl_engine.h" 44 | "fl_json_message_codec.h" 45 | "fl_json_method_codec.h" 46 | "fl_message_codec.h" 47 | "fl_method_call.h" 48 | "fl_method_channel.h" 49 | "fl_method_codec.h" 50 | "fl_method_response.h" 51 | "fl_plugin_registrar.h" 52 | "fl_plugin_registry.h" 53 | "fl_standard_message_codec.h" 54 | "fl_standard_method_codec.h" 55 | "fl_string_codec.h" 56 | "fl_value.h" 57 | "fl_view.h" 58 | "flutter_linux.h" 59 | ) 60 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 61 | add_library(flutter INTERFACE) 62 | target_include_directories(flutter INTERFACE 63 | "${EPHEMERAL_DIR}" 64 | ) 65 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 66 | target_link_libraries(flutter INTERFACE 67 | PkgConfig::GTK 68 | PkgConfig::GLIB 69 | PkgConfig::GIO 70 | PkgConfig::BLKID 71 | PkgConfig::LZMA 72 | ) 73 | add_dependencies(flutter flutter_assemble) 74 | 75 | # === Flutter tool backend === 76 | # _phony_ is a non-existent file to force this command to run every time, 77 | # since currently there's no way to get a full input/output list from the 78 | # flutter tool. 79 | add_custom_command( 80 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 81 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 82 | COMMAND ${CMAKE_COMMAND} -E env 83 | ${FLUTTER_TOOL_ENVIRONMENT} 84 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 85 | linux-x64 ${CMAKE_BUILD_TYPE} 86 | VERBATIM 87 | ) 88 | add_custom_target(flutter_assemble DEPENDS 89 | "${FLUTTER_LIBRARY}" 90 | ${FLUTTER_LIBRARY_HEADERS} 91 | ) 92 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | 11 | void fl_register_plugins(FlPluginRegistry* registry) { 12 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 13 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 14 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 15 | } 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | url_launcher_linux 7 | ) 8 | 9 | set(PLUGIN_BUNDLED_LIBRARIES) 10 | 11 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 12 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 13 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 14 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 15 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 16 | endforeach(plugin) 17 | -------------------------------------------------------------------------------- /linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /linux/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen *screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar *header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "animations"); 44 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 46 | } 47 | else { 48 | gtk_window_set_title(window, "animations"); 49 | } 50 | 51 | gtk_window_set_default_size(window, 1280, 720); 52 | gtk_widget_show(GTK_WIDGET(window)); 53 | 54 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 55 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 56 | 57 | FlView* view = fl_view_new(project); 58 | gtk_widget_show(GTK_WIDGET(view)); 59 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 60 | 61 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 62 | 63 | gtk_widget_grab_focus(GTK_WIDGET(view)); 64 | } 65 | 66 | // Implements GApplication::local_command_line. 67 | static gboolean my_application_local_command_line(GApplication* application, gchar ***arguments, int *exit_status) { 68 | MyApplication* self = MY_APPLICATION(application); 69 | // Strip out the first argument as it is the binary name. 70 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 71 | 72 | g_autoptr(GError) error = nullptr; 73 | if (!g_application_register(application, nullptr, &error)) { 74 | g_warning("Failed to register: %s", error->message); 75 | *exit_status = 1; 76 | return TRUE; 77 | } 78 | 79 | g_application_activate(application); 80 | *exit_status = 0; 81 | 82 | return TRUE; 83 | } 84 | 85 | // Implements GObject::dispose. 86 | static void my_application_dispose(GObject *object) { 87 | MyApplication* self = MY_APPLICATION(object); 88 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 89 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 90 | } 91 | 92 | static void my_application_class_init(MyApplicationClass* klass) { 93 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 94 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 95 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 96 | } 97 | 98 | static void my_application_init(MyApplication* self) {} 99 | 100 | MyApplication* my_application_new() { 101 | return MY_APPLICATION(g_object_new(my_application_get_type(), 102 | "application-id", APPLICATION_ID, 103 | nullptr)); 104 | } 105 | -------------------------------------------------------------------------------- /linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/xcuserdata/ 7 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import path_provider_macos 9 | import url_launcher_macos 10 | 11 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 12 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 13 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 14 | } 15 | -------------------------------------------------------------------------------- /macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.11' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /macos/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - FlutterMacOS (1.0.0) 3 | - path_provider_macos (0.0.1): 4 | - FlutterMacOS 5 | - url_launcher_macos (0.0.1): 6 | - FlutterMacOS 7 | 8 | DEPENDENCIES: 9 | - FlutterMacOS (from `Flutter/ephemeral`) 10 | - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`) 11 | - url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`) 12 | 13 | EXTERNAL SOURCES: 14 | FlutterMacOS: 15 | :path: Flutter/ephemeral 16 | path_provider_macos: 17 | :path: Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos 18 | url_launcher_macos: 19 | :path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos 20 | 21 | SPEC CHECKSUMS: 22 | FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424 23 | path_provider_macos: 160cab0d5461f0c0e02995469a98f24bdb9a3f1f 24 | url_launcher_macos: 45af3d61de06997666568a7149c1be98b41c95d4 25 | 26 | PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c 27 | 28 | COCOAPODS: 1.11.2 29 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = animations 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = dev.roszkowski.animations 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2021 dev.roszkowski. All rights reserved. 15 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.client 10 | 11 | com.apple.security.network.server 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | LSApplicationQueriesSchemes 32 | 33 | https 34 | http 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: animations_sample 2 | description: A new Flutter project. 3 | 4 | publish_to: "none" 5 | 6 | version: 1.0.0+1 7 | 8 | environment: 9 | sdk: ">=2.12.0 <3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | animations: ^2.0.0 15 | cupertino_icons: ^1.0.2 16 | fl_chart: ^0.40.2 17 | funvas: ^0.1.3 18 | fps_widget: ^1.0.1+1 19 | gap: ^2.0.0 20 | google_fonts: ^2.0.0 21 | line_icons: ^2.0.1 22 | provider: ^6.0.1 23 | url_launcher: ^6.0.12 24 | 25 | dev_dependencies: 26 | flutter_test: 27 | sdk: flutter 28 | very_good_analysis: ^2.3.0 29 | build_web_compilers: ^3.2.1 30 | build_runner: ^2.1.4 31 | 32 | flutter: 33 | uses-material-design: true 34 | assets: 35 | - assets/ 36 | -------------------------------------------------------------------------------- /test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:animations_sample/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that our counter starts at 0. 19 | expect(find.text('0'), findsOneWidget); 20 | expect(find.text('1'), findsNothing); 21 | 22 | // Tap the '+' icon and trigger a frame. 23 | await tester.tap(find.byIcon(Icons.add)); 24 | await tester.pump(); 25 | 26 | // Verify that our counter has incremented. 27 | expect(find.text('0'), findsNothing); 28 | expect(find.text('1'), findsOneWidget); 29 | }); 30 | } 31 | -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/web/favicon.png -------------------------------------------------------------------------------- /web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/web/icons/Icon-192.png -------------------------------------------------------------------------------- /web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/web/icons/Icon-512.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | animations 30 | 31 | 32 | 33 | 36 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "animations", 3 | "short_name": "animations", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(animations LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "animations") 5 | 6 | cmake_policy(SET CMP0063 NEW) 7 | 8 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 9 | 10 | # Configure build options. 11 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 12 | if(IS_MULTICONFIG) 13 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 14 | CACHE STRING "" FORCE) 15 | else() 16 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 17 | set(CMAKE_BUILD_TYPE "Debug" CACHE 18 | STRING "Flutter build mode" FORCE) 19 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 20 | "Debug" "Profile" "Release") 21 | endif() 22 | endif() 23 | 24 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 25 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 26 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 27 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 28 | 29 | # Use Unicode for all projects. 30 | add_definitions(-DUNICODE -D_UNICODE) 31 | 32 | # Compilation settings that should be applied to most targets. 33 | function(APPLY_STANDARD_SETTINGS TARGET) 34 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 35 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 36 | target_compile_options(${TARGET} PRIVATE /EHsc) 37 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 38 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 39 | endfunction() 40 | 41 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 42 | 43 | # Flutter library and tool build rules. 44 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 45 | 46 | # Application build 47 | add_subdirectory("runner") 48 | 49 | # Generated plugin build rules, which manage building the plugins and adding 50 | # them to the application. 51 | include(flutter/generated_plugins.cmake) 52 | 53 | 54 | # === Installation === 55 | # Support files are copied into place next to the executable, so that it can 56 | # run in place. This is done instead of making a separate bundle (as on Linux) 57 | # so that building and running from within Visual Studio will work. 58 | set(BUILD_BUNDLE_DIR "$") 59 | # Make the "install" step default, as it's required to run. 60 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 61 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 62 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 63 | endif() 64 | 65 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 66 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 67 | 68 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 69 | COMPONENT Runtime) 70 | 71 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 72 | COMPONENT Runtime) 73 | 74 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 75 | COMPONENT Runtime) 76 | 77 | if(PLUGIN_BUNDLED_LIBRARIES) 78 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 79 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 80 | COMPONENT Runtime) 81 | endif() 82 | 83 | # Fully re-copy the assets directory on each build to avoid having stale files 84 | # from a previous install. 85 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 86 | install(CODE " 87 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 88 | " COMPONENT Runtime) 89 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 90 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 91 | 92 | # Install the AOT library on non-Debug builds only. 93 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 94 | CONFIGURATIONS Profile;Release 95 | COMPONENT Runtime) 96 | -------------------------------------------------------------------------------- /windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 11 | 12 | # === Flutter Library === 13 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 14 | 15 | # Published to parent scope for install step. 16 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 17 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 18 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 19 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 20 | 21 | list(APPEND FLUTTER_LIBRARY_HEADERS 22 | "flutter_export.h" 23 | "flutter_windows.h" 24 | "flutter_messenger.h" 25 | "flutter_plugin_registrar.h" 26 | "flutter_texture_registrar.h" 27 | ) 28 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 29 | add_library(flutter INTERFACE) 30 | target_include_directories(flutter INTERFACE 31 | "${EPHEMERAL_DIR}" 32 | ) 33 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 34 | add_dependencies(flutter flutter_assemble) 35 | 36 | # === Wrapper === 37 | list(APPEND CPP_WRAPPER_SOURCES_CORE 38 | "core_implementations.cc" 39 | "standard_codec.cc" 40 | ) 41 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 42 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 43 | "plugin_registrar.cc" 44 | ) 45 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 46 | list(APPEND CPP_WRAPPER_SOURCES_APP 47 | "flutter_engine.cc" 48 | "flutter_view_controller.cc" 49 | ) 50 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 51 | 52 | # Wrapper sources needed for a plugin. 53 | add_library(flutter_wrapper_plugin STATIC 54 | ${CPP_WRAPPER_SOURCES_CORE} 55 | ${CPP_WRAPPER_SOURCES_PLUGIN} 56 | ) 57 | apply_standard_settings(flutter_wrapper_plugin) 58 | set_target_properties(flutter_wrapper_plugin PROPERTIES 59 | POSITION_INDEPENDENT_CODE ON) 60 | set_target_properties(flutter_wrapper_plugin PROPERTIES 61 | CXX_VISIBILITY_PRESET hidden) 62 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 63 | target_include_directories(flutter_wrapper_plugin PUBLIC 64 | "${WRAPPER_ROOT}/include" 65 | ) 66 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 67 | 68 | # Wrapper sources needed for the runner. 69 | add_library(flutter_wrapper_app STATIC 70 | ${CPP_WRAPPER_SOURCES_CORE} 71 | ${CPP_WRAPPER_SOURCES_APP} 72 | ) 73 | apply_standard_settings(flutter_wrapper_app) 74 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 75 | target_include_directories(flutter_wrapper_app PUBLIC 76 | "${WRAPPER_ROOT}/include" 77 | ) 78 | add_dependencies(flutter_wrapper_app flutter_assemble) 79 | 80 | # === Flutter tool backend === 81 | # _phony_ is a non-existent file to force this command to run every time, 82 | # since currently there's no way to get a full input/output list from the 83 | # flutter tool. 84 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 85 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 86 | add_custom_command( 87 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 88 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 89 | ${CPP_WRAPPER_SOURCES_APP} 90 | ${PHONY_OUTPUT} 91 | COMMAND ${CMAKE_COMMAND} -E env 92 | ${FLUTTER_TOOL_ENVIRONMENT} 93 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 94 | windows-x64 $ 95 | VERBATIM 96 | ) 97 | add_custom_target(flutter_assemble DEPENDS 98 | "${FLUTTER_LIBRARY}" 99 | ${FLUTTER_LIBRARY_HEADERS} 100 | ${CPP_WRAPPER_SOURCES_CORE} 101 | ${CPP_WRAPPER_SOURCES_PLUGIN} 102 | ${CPP_WRAPPER_SOURCES_APP} 103 | ) 104 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | 11 | void RegisterPlugins(flutter::PluginRegistry* registry) { 12 | UrlLauncherWindowsRegisterWithRegistrar( 13 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 14 | } 15 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | url_launcher_windows 7 | ) 8 | 9 | set(PLUGIN_BUNDLED_LIBRARIES) 10 | 11 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 12 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 13 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 14 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 15 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 16 | endforeach(plugin) 17 | -------------------------------------------------------------------------------- /windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(runner LANGUAGES CXX) 3 | 4 | add_executable(${BINARY_NAME} WIN32 5 | "flutter_window.cpp" 6 | "main.cpp" 7 | "run_loop.cpp" 8 | "utils.cpp" 9 | "win32_window.cpp" 10 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 11 | "Runner.rc" 12 | "runner.exe.manifest" 13 | ) 14 | apply_standard_settings(${BINARY_NAME}) 15 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 16 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 17 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 18 | add_dependencies(${BINARY_NAME} flutter_assemble) 19 | -------------------------------------------------------------------------------- /windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #ifdef FLUTTER_BUILD_NUMBER 64 | #define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0 67 | #endif 68 | 69 | #ifdef FLUTTER_BUILD_NAME 70 | #define VERSION_AS_STRING #FLUTTER_BUILD_NAME 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "dev.roszkowski" "\0" 93 | VALUE "FileDescription", "A new Flutter project." "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "animations" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2021 dev.roszkowski. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "animations.exe" "\0" 98 | VALUE "ProductName", "animations" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(RunLoop* run_loop, 8 | const flutter::DartProject& project) 9 | : run_loop_(run_loop), project_(project) {} 10 | 11 | FlutterWindow::~FlutterWindow() {} 12 | 13 | bool FlutterWindow::OnCreate() { 14 | if (!Win32Window::OnCreate()) { 15 | return false; 16 | } 17 | 18 | RECT frame = GetClientArea(); 19 | 20 | // The size here must match the window dimensions to avoid unnecessary surface 21 | // creation / destruction in the startup path. 22 | flutter_controller_ = std::make_unique( 23 | frame.right - frame.left, frame.bottom - frame.top, project_); 24 | // Ensure that basic setup of the controller was successful. 25 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 26 | return false; 27 | } 28 | RegisterPlugins(flutter_controller_->engine()); 29 | run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); 30 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 31 | return true; 32 | } 33 | 34 | void FlutterWindow::OnDestroy() { 35 | if (flutter_controller_) { 36 | run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); 37 | flutter_controller_ = nullptr; 38 | } 39 | 40 | Win32Window::OnDestroy(); 41 | } 42 | 43 | LRESULT 44 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 45 | WPARAM const wparam, 46 | LPARAM const lparam) noexcept { 47 | // Give Flutter, including plugins, an opporutunity to handle window messages. 48 | if (flutter_controller_) { 49 | std::optional result = 50 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 51 | lparam); 52 | if (result) { 53 | return *result; 54 | } 55 | } 56 | 57 | switch (message) { 58 | case WM_FONTCHANGE: 59 | flutter_controller_->engine()->ReloadSystemFonts(); 60 | break; 61 | } 62 | 63 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 64 | } 65 | -------------------------------------------------------------------------------- /windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "run_loop.h" 10 | #include "win32_window.h" 11 | 12 | // A window that does nothing but host a Flutter view. 13 | class FlutterWindow : public Win32Window { 14 | public: 15 | // Creates a new FlutterWindow driven by the |run_loop|, hosting a 16 | // Flutter view running |project|. 17 | explicit FlutterWindow(RunLoop* run_loop, 18 | const flutter::DartProject& project); 19 | virtual ~FlutterWindow(); 20 | 21 | protected: 22 | // Win32Window: 23 | bool OnCreate() override; 24 | void OnDestroy() override; 25 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 26 | LPARAM const lparam) noexcept override; 27 | 28 | private: 29 | // The run loop driving events for this window. 30 | RunLoop* run_loop_; 31 | 32 | // The project to run. 33 | flutter::DartProject project_; 34 | 35 | // The Flutter instance hosted by this window. 36 | std::unique_ptr flutter_controller_; 37 | }; 38 | 39 | #endif // RUNNER_FLUTTER_WINDOW_H_ 40 | -------------------------------------------------------------------------------- /windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "run_loop.h" 7 | #include "utils.h" 8 | 9 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 10 | _In_ wchar_t *command_line, _In_ int show_command) { 11 | // Attach to console when present (e.g., 'flutter run') or create a 12 | // new console when running with a debugger. 13 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 14 | CreateAndAttachConsole(); 15 | } 16 | 17 | // Initialize COM, so that it is available for use in the library and/or 18 | // plugins. 19 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 20 | 21 | RunLoop run_loop; 22 | 23 | flutter::DartProject project(L"data"); 24 | 25 | std::vector command_line_arguments = 26 | GetCommandLineArguments(); 27 | 28 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 29 | 30 | FlutterWindow window(&run_loop, project); 31 | Win32Window::Point origin(10, 10); 32 | Win32Window::Size size(1280, 720); 33 | if (!window.CreateAndShow(L"animations", origin, size)) { 34 | return EXIT_FAILURE; 35 | } 36 | window.SetQuitOnClose(true); 37 | 38 | run_loop.Run(); 39 | 40 | ::CoUninitialize(); 41 | return EXIT_SUCCESS; 42 | } 43 | -------------------------------------------------------------------------------- /windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/orestesgaolin/animations_samples/3680de04ece1eea8d4338f0dabf353fc8e9fec4e/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /windows/runner/run_loop.cpp: -------------------------------------------------------------------------------- 1 | #include "run_loop.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | RunLoop::RunLoop() {} 8 | 9 | RunLoop::~RunLoop() {} 10 | 11 | void RunLoop::Run() { 12 | bool keep_running = true; 13 | TimePoint next_flutter_event_time = TimePoint::clock::now(); 14 | while (keep_running) { 15 | std::chrono::nanoseconds wait_duration = 16 | std::max(std::chrono::nanoseconds(0), 17 | next_flutter_event_time - TimePoint::clock::now()); 18 | ::MsgWaitForMultipleObjects( 19 | 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), 20 | QS_ALLINPUT); 21 | bool processed_events = false; 22 | MSG message; 23 | // All pending Windows messages must be processed; MsgWaitForMultipleObjects 24 | // won't return again for items left in the queue after PeekMessage. 25 | while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { 26 | processed_events = true; 27 | if (message.message == WM_QUIT) { 28 | keep_running = false; 29 | break; 30 | } 31 | ::TranslateMessage(&message); 32 | ::DispatchMessage(&message); 33 | // Allow Flutter to process messages each time a Windows message is 34 | // processed, to prevent starvation. 35 | next_flutter_event_time = 36 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 37 | } 38 | // If the PeekMessage loop didn't run, process Flutter messages. 39 | if (!processed_events) { 40 | next_flutter_event_time = 41 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 42 | } 43 | } 44 | } 45 | 46 | void RunLoop::RegisterFlutterInstance( 47 | flutter::FlutterEngine* flutter_instance) { 48 | flutter_instances_.insert(flutter_instance); 49 | } 50 | 51 | void RunLoop::UnregisterFlutterInstance( 52 | flutter::FlutterEngine* flutter_instance) { 53 | flutter_instances_.erase(flutter_instance); 54 | } 55 | 56 | RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { 57 | TimePoint next_event_time = TimePoint::max(); 58 | for (auto instance : flutter_instances_) { 59 | std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); 60 | if (wait_duration != std::chrono::nanoseconds::max()) { 61 | next_event_time = 62 | std::min(next_event_time, TimePoint::clock::now() + wait_duration); 63 | } 64 | } 65 | return next_event_time; 66 | } 67 | -------------------------------------------------------------------------------- /windows/runner/run_loop.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_RUN_LOOP_H_ 2 | #define RUNNER_RUN_LOOP_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // A runloop that will service events for Flutter instances as well 10 | // as native messages. 11 | class RunLoop { 12 | public: 13 | RunLoop(); 14 | ~RunLoop(); 15 | 16 | // Prevent copying 17 | RunLoop(RunLoop const&) = delete; 18 | RunLoop& operator=(RunLoop const&) = delete; 19 | 20 | // Runs the run loop until the application quits. 21 | void Run(); 22 | 23 | // Registers the given Flutter instance for event servicing. 24 | void RegisterFlutterInstance( 25 | flutter::FlutterEngine* flutter_instance); 26 | 27 | // Unregisters the given Flutter instance from event servicing. 28 | void UnregisterFlutterInstance( 29 | flutter::FlutterEngine* flutter_instance); 30 | 31 | private: 32 | using TimePoint = std::chrono::steady_clock::time_point; 33 | 34 | // Processes all currently pending messages for registered Flutter instances. 35 | TimePoint ProcessFlutterMessages(); 36 | 37 | std::set flutter_instances_; 38 | }; 39 | 40 | #endif // RUNNER_RUN_LOOP_H_ 41 | -------------------------------------------------------------------------------- /windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | if (target_length == 0) { 52 | return std::string(); 53 | } 54 | std::string utf8_string; 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /windows/runner/win32_window.cpp: -------------------------------------------------------------------------------- 1 | #include "win32_window.h" 2 | 3 | #include 4 | 5 | #include "resource.h" 6 | 7 | namespace { 8 | 9 | constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; 10 | 11 | // The number of Win32Window objects that currently exist. 12 | static int g_active_window_count = 0; 13 | 14 | using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); 15 | 16 | // Scale helper to convert logical scaler values to physical using passed in 17 | // scale factor 18 | int Scale(int source, double scale_factor) { 19 | return static_cast(source * scale_factor); 20 | } 21 | 22 | // Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. 23 | // This API is only needed for PerMonitor V1 awareness mode. 24 | void EnableFullDpiSupportIfAvailable(HWND hwnd) { 25 | HMODULE user32_module = LoadLibraryA("User32.dll"); 26 | if (!user32_module) { 27 | return; 28 | } 29 | auto enable_non_client_dpi_scaling = 30 | reinterpret_cast( 31 | GetProcAddress(user32_module, "EnableNonClientDpiScaling")); 32 | if (enable_non_client_dpi_scaling != nullptr) { 33 | enable_non_client_dpi_scaling(hwnd); 34 | FreeLibrary(user32_module); 35 | } 36 | } 37 | 38 | } // namespace 39 | 40 | // Manages the Win32Window's window class registration. 41 | class WindowClassRegistrar { 42 | public: 43 | ~WindowClassRegistrar() = default; 44 | 45 | // Returns the singleton registar instance. 46 | static WindowClassRegistrar* GetInstance() { 47 | if (!instance_) { 48 | instance_ = new WindowClassRegistrar(); 49 | } 50 | return instance_; 51 | } 52 | 53 | // Returns the name of the window class, registering the class if it hasn't 54 | // previously been registered. 55 | const wchar_t* GetWindowClass(); 56 | 57 | // Unregisters the window class. Should only be called if there are no 58 | // instances of the window. 59 | void UnregisterWindowClass(); 60 | 61 | private: 62 | WindowClassRegistrar() = default; 63 | 64 | static WindowClassRegistrar* instance_; 65 | 66 | bool class_registered_ = false; 67 | }; 68 | 69 | WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; 70 | 71 | const wchar_t* WindowClassRegistrar::GetWindowClass() { 72 | if (!class_registered_) { 73 | WNDCLASS window_class{}; 74 | window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); 75 | window_class.lpszClassName = kWindowClassName; 76 | window_class.style = CS_HREDRAW | CS_VREDRAW; 77 | window_class.cbClsExtra = 0; 78 | window_class.cbWndExtra = 0; 79 | window_class.hInstance = GetModuleHandle(nullptr); 80 | window_class.hIcon = 81 | LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); 82 | window_class.hbrBackground = 0; 83 | window_class.lpszMenuName = nullptr; 84 | window_class.lpfnWndProc = Win32Window::WndProc; 85 | RegisterClass(&window_class); 86 | class_registered_ = true; 87 | } 88 | return kWindowClassName; 89 | } 90 | 91 | void WindowClassRegistrar::UnregisterWindowClass() { 92 | UnregisterClass(kWindowClassName, nullptr); 93 | class_registered_ = false; 94 | } 95 | 96 | Win32Window::Win32Window() { 97 | ++g_active_window_count; 98 | } 99 | 100 | Win32Window::~Win32Window() { 101 | --g_active_window_count; 102 | Destroy(); 103 | } 104 | 105 | bool Win32Window::CreateAndShow(const std::wstring& title, 106 | const Point& origin, 107 | const Size& size) { 108 | Destroy(); 109 | 110 | const wchar_t* window_class = 111 | WindowClassRegistrar::GetInstance()->GetWindowClass(); 112 | 113 | const POINT target_point = {static_cast(origin.x), 114 | static_cast(origin.y)}; 115 | HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); 116 | UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); 117 | double scale_factor = dpi / 96.0; 118 | 119 | HWND window = CreateWindow( 120 | window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, 121 | Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), 122 | Scale(size.width, scale_factor), Scale(size.height, scale_factor), 123 | nullptr, nullptr, GetModuleHandle(nullptr), this); 124 | 125 | if (!window) { 126 | return false; 127 | } 128 | 129 | return OnCreate(); 130 | } 131 | 132 | // static 133 | LRESULT CALLBACK Win32Window::WndProc(HWND const window, 134 | UINT const message, 135 | WPARAM const wparam, 136 | LPARAM const lparam) noexcept { 137 | if (message == WM_NCCREATE) { 138 | auto window_struct = reinterpret_cast(lparam); 139 | SetWindowLongPtr(window, GWLP_USERDATA, 140 | reinterpret_cast(window_struct->lpCreateParams)); 141 | 142 | auto that = static_cast(window_struct->lpCreateParams); 143 | EnableFullDpiSupportIfAvailable(window); 144 | that->window_handle_ = window; 145 | } else if (Win32Window* that = GetThisFromHandle(window)) { 146 | return that->MessageHandler(window, message, wparam, lparam); 147 | } 148 | 149 | return DefWindowProc(window, message, wparam, lparam); 150 | } 151 | 152 | LRESULT 153 | Win32Window::MessageHandler(HWND hwnd, 154 | UINT const message, 155 | WPARAM const wparam, 156 | LPARAM const lparam) noexcept { 157 | switch (message) { 158 | case WM_DESTROY: 159 | window_handle_ = nullptr; 160 | Destroy(); 161 | if (quit_on_close_) { 162 | PostQuitMessage(0); 163 | } 164 | return 0; 165 | 166 | case WM_DPICHANGED: { 167 | auto newRectSize = reinterpret_cast(lparam); 168 | LONG newWidth = newRectSize->right - newRectSize->left; 169 | LONG newHeight = newRectSize->bottom - newRectSize->top; 170 | 171 | SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, 172 | newHeight, SWP_NOZORDER | SWP_NOACTIVATE); 173 | 174 | return 0; 175 | } 176 | case WM_SIZE: { 177 | RECT rect = GetClientArea(); 178 | if (child_content_ != nullptr) { 179 | // Size and position the child window. 180 | MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, 181 | rect.bottom - rect.top, TRUE); 182 | } 183 | return 0; 184 | } 185 | 186 | case WM_ACTIVATE: 187 | if (child_content_ != nullptr) { 188 | SetFocus(child_content_); 189 | } 190 | return 0; 191 | } 192 | 193 | return DefWindowProc(window_handle_, message, wparam, lparam); 194 | } 195 | 196 | void Win32Window::Destroy() { 197 | OnDestroy(); 198 | 199 | if (window_handle_) { 200 | DestroyWindow(window_handle_); 201 | window_handle_ = nullptr; 202 | } 203 | if (g_active_window_count == 0) { 204 | WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); 205 | } 206 | } 207 | 208 | Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { 209 | return reinterpret_cast( 210 | GetWindowLongPtr(window, GWLP_USERDATA)); 211 | } 212 | 213 | void Win32Window::SetChildContent(HWND content) { 214 | child_content_ = content; 215 | SetParent(content, window_handle_); 216 | RECT frame = GetClientArea(); 217 | 218 | MoveWindow(content, frame.left, frame.top, frame.right - frame.left, 219 | frame.bottom - frame.top, true); 220 | 221 | SetFocus(child_content_); 222 | } 223 | 224 | RECT Win32Window::GetClientArea() { 225 | RECT frame; 226 | GetClientRect(window_handle_, &frame); 227 | return frame; 228 | } 229 | 230 | HWND Win32Window::GetHandle() { 231 | return window_handle_; 232 | } 233 | 234 | void Win32Window::SetQuitOnClose(bool quit_on_close) { 235 | quit_on_close_ = quit_on_close; 236 | } 237 | 238 | bool Win32Window::OnCreate() { 239 | // No-op; provided for subclasses. 240 | return true; 241 | } 242 | 243 | void Win32Window::OnDestroy() { 244 | // No-op; provided for subclasses. 245 | } 246 | -------------------------------------------------------------------------------- /windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates and shows a win32 window with |title| and position and size using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size to will treat the width height passed in to this function 35 | // as logical pixels and scale to appropriate for the default monitor. Returns 36 | // true if the window was created successfully. 37 | bool CreateAndShow(const std::wstring& title, 38 | const Point& origin, 39 | const Size& size); 40 | 41 | // Release OS resources associated with window. 42 | void Destroy(); 43 | 44 | // Inserts |content| into the window tree. 45 | void SetChildContent(HWND content); 46 | 47 | // Returns the backing Window handle to enable clients to set icon and other 48 | // window properties. Returns nullptr if the window has been destroyed. 49 | HWND GetHandle(); 50 | 51 | // If true, closing this window will quit the application. 52 | void SetQuitOnClose(bool quit_on_close); 53 | 54 | // Return a RECT representing the bounds of the current client area. 55 | RECT GetClientArea(); 56 | 57 | protected: 58 | // Processes and route salient window messages for mouse handling, 59 | // size change and DPI. Delegates handling of these to member overloads that 60 | // inheriting classes can handle. 61 | virtual LRESULT MessageHandler(HWND window, 62 | UINT const message, 63 | WPARAM const wparam, 64 | LPARAM const lparam) noexcept; 65 | 66 | // Called when CreateAndShow is called, allowing subclass window-related 67 | // setup. Subclasses should return false if setup fails. 68 | virtual bool OnCreate(); 69 | 70 | // Called when Destroy is called. 71 | virtual void OnDestroy(); 72 | 73 | private: 74 | friend class WindowClassRegistrar; 75 | 76 | // OS callback called by message pump. Handles the WM_NCCREATE message which 77 | // is passed when the non-client area is being created and enables automatic 78 | // non-client DPI scaling so that the non-client area automatically 79 | // responsponds to changes in DPI. All other messages are handled by 80 | // MessageHandler. 81 | static LRESULT CALLBACK WndProc(HWND const window, 82 | UINT const message, 83 | WPARAM const wparam, 84 | LPARAM const lparam) noexcept; 85 | 86 | // Retrieves a class instance pointer for |window| 87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 88 | 89 | bool quit_on_close_ = false; 90 | 91 | // window handle for top level window. 92 | HWND window_handle_ = nullptr; 93 | 94 | // window handle for hosted content. 95 | HWND child_content_ = nullptr; 96 | }; 97 | 98 | #endif // RUNNER_WIN32_WINDOW_H_ 99 | --------------------------------------------------------------------------------