├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── example ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── com │ │ │ │ │ └── github │ │ │ │ │ └── florent37 │ │ │ │ │ └── kenburns_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── lib │ └── main.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── lib ├── KenburnsGenerator.dart ├── generated │ └── i18n.dart └── kenburns.dart ├── medias ├── kenburns.gif └── kenburns_slow.gif ├── publish.sh ├── pubspec.yaml ├── res └── values │ └── strings_en.arb └── test └── kenburns_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | build/ 13 | 14 | 15 | # IntelliJ related 16 | *.iml 17 | *.ipr 18 | *.iws 19 | .idea/ 20 | 21 | 22 | # Flutter/Dart/Pub related 23 | **/doc/api/ 24 | .dart_tool/ 25 | .flutter-plugins 26 | .flutter-plugins-dependencies 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | pubspec.lock -------------------------------------------------------------------------------- /.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: b712a172f9694745f50505c93340883493b505e5 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.0.5 2 | 3 | * Handle widget updates 4 | 5 | ## 1.0.3 6 | 7 | * Fixed scale & translation animation 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Florent Champigny (florent37) 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KenBurns 2 | 3 | The Ken Burns effect is a type of panning and zooming effect used in video production from still imagery. 4 | 5 | Wrap your image with a KenBurns widget 6 | ```dart 7 | Container( 8 | height: 300, 9 | child: KenBurns( 10 | child: Image.network("https://lemag.nikonclub.fr/wp-content/uploads/2017/07/08.jpg", fit: BoxFit.cover,), 11 | ), 12 | ), 13 | ``` 14 | 15 | [![screen](https://raw.githubusercontent.com/florent37/Flutter-KenBurns/master/medias/kenburns_slow.gif)](https://www.github.com/florent37/Flutter-KenBurns) 16 | 17 | # Configuration 18 | 19 | You can configure KenBurns Widget 20 | 21 | ``` 22 | KenBurns( 23 | minAnimationDuration : Duration(milliseconds: 3000), 24 | maxAnimationDuration : Duration(milliseconds: 10000), 25 | maxScale : 8, 26 | child: ... 27 | }); 28 | ``` 29 | 30 | # Multiple images 31 | 32 | You can display multiple child in KenBurns with a CrossFade animation 33 | 34 | ```dart 35 | Container( 36 | height: 300, 37 | child: KenBurns.multiple( 38 | childLoop: 3, 39 | children: [ 40 | Image.network( 41 | "https://www.photo-paysage.com/?file=pic_download_link/picture&pid=3100", 42 | fit: BoxFit.cover, 43 | ), 44 | Image.network( 45 | "https://cdn.getyourguide.com/img/location_img-59-1969619245-148.jpg", 46 | fit: BoxFit.cover, 47 | ), 48 | Image.network( 49 | "https://www.theglobeandmail.com/resizer/vq3O7LI3hvsjTP2N0m9NwU4W3Eg=/1500x0/filters:quality(80)/arc-anglerfish-tgam-prod-tgam.s3.amazonaws.com/public/4ETF3GZR3NA3RDDW23XDRBKKCI", 50 | fit: BoxFit.cover, 51 | ), 52 | ], 53 | ), 54 | ), 55 | ``` 56 | 57 | # Download 58 | 59 | [![pub package](https://img.shields.io/pub/v/kenburns.svg)]( 60 | https://pub.dartlang.org/packages/kenburns) 61 | 62 | ``` 63 | dependencies: 64 | kenburns: ^1.0.5 65 | ``` 66 | 67 | # License 68 | 69 | Copyright 2019 florent37, Inc. 70 | 71 | Licensed under the Apache License, Version 2.0 (the "License"); 72 | you may not use this file except in compliance with the License. 73 | You may obtain a copy of the License at 74 | 75 | http://www.apache.org/licenses/LICENSE-2.0 76 | 77 | Unless required by applicable law or agreed to in writing, software 78 | distributed under the License is distributed on an "AS IS" BASIS, 79 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 80 | See the License for the specific language governing permissions and 81 | limitations under the License. 82 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | pubspec.lock 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Generated.xcconfig 62 | **/ios/Flutter/app.flx 63 | **/ios/Flutter/app.zip 64 | **/ios/Flutter/flutter_assets/ 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: b712a172f9694745f50505c93340883493b505e5 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # KenBurns example 2 | 3 | The Ken Burns effect is a type of panning and zooming effect used in video production from still imagery. 4 | 5 | Wrap your image with a KenBurns widget 6 | ```dart 7 | Container( 8 | height: 300, 9 | child: KenBurns( 10 | child: Image.network("https://lemag.nikonclub.fr/wp-content/uploads/2017/07/08.jpg", fit: BoxFit.cover,), 11 | ), 12 | ), 13 | ``` 14 | 15 | [![screen](https://raw.githubusercontent.com/florent37/Flutter-KenBurns/master/medias/kenburns_slow.gif)](https://www.github.com/florent37/Flutter-KenBurns) 16 | 17 | # Configuration 18 | 19 | You can configure KenBurns Widget 20 | 21 | ``` 22 | KenBurns( 23 | minAnimationDuration : Duration(milliseconds: 3000), 24 | maxAnimationDuration : Duration(milliseconds: 10000), 25 | maxScale : 8, 26 | child: ... 27 | }); 28 | ``` 29 | 30 | # Download 31 | 32 | https://pub.dev/packages/kenburns 33 | 34 | ``` 35 | dependencies: 36 | kenburns: 37 | ``` 38 | 39 | ## Getting Started with Flutter 40 | 41 | For help getting started with Flutter, view our 42 | [online documentation](https://flutter.dev/docs), which offers tutorials, 43 | samples, guidance on mobile development, and a full API reference. 44 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 28 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.github.florent37.kenburns_example" 42 | minSdkVersion 16 43 | targetSdkVersion 28 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 47 | } 48 | 49 | buildTypes { 50 | release { 51 | // TODO: Add your own signing config for the release build. 52 | // Signing with the debug keys for now, so `flutter run --release` works. 53 | signingConfig signingConfigs.debug 54 | } 55 | } 56 | } 57 | 58 | flutter { 59 | source '../..' 60 | } 61 | 62 | dependencies { 63 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 64 | testImplementation 'junit:junit:4.12' 65 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 66 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 67 | } 68 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 13 | 20 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/github/florent37/kenburns_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.github.florent37.kenburns_example 2 | 3 | import android.os.Bundle 4 | 5 | import io.flutter.app.FlutterActivity 6 | import io.flutter.plugins.GeneratedPluginRegistrant 7 | 8 | class MainActivity: FlutterActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | GeneratedPluginRegistrant.registerWith(this) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florent37/Flutter-KenBurns/a42c5b1cef86e5fc15b5fbba8d49c4210f491f99/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florent37/Flutter-KenBurns/a42c5b1cef86e5fc15b5fbba8d49c4210f491f99/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florent37/Flutter-KenBurns/a42c5b1cef86e5fc15b5fbba8d49c4210f491f99/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florent37/Flutter-KenBurns/a42c5b1cef86e5fc15b5fbba8d49c4210f491f99/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florent37/Flutter-KenBurns/a42c5b1cef86e5fc15b5fbba8d49c4210f491f99/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.2.71' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.2.1' 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 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | 3 | android.enableR8=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:kenburns/kenburns.dart'; 4 | 5 | void main() => runApp(MyApp()); 6 | 7 | class MyApp extends StatefulWidget { 8 | @override 9 | _MyAppState createState() => _MyAppState(); 10 | } 11 | 12 | class _MyAppState extends State { 13 | @override 14 | Widget build(BuildContext context) { 15 | return MaterialApp( 16 | home: Scaffold( 17 | appBar: AppBar( 18 | title: const Text('Plugin example app'), 19 | ), 20 | body: Padding( 21 | padding: const EdgeInsets.all(30.0), 22 | child: Column( 23 | crossAxisAlignment: CrossAxisAlignment.stretch, 24 | mainAxisSize: MainAxisSize.max, 25 | mainAxisAlignment: MainAxisAlignment.center, 26 | children: [ 27 | /* 28 | Container( 29 | height: 300, 30 | child: KenBurns( 31 | child: Image.network( 32 | "https://www.photo-paysage.com/?file=pic_download_link/picture&pid=3100", 33 | fit: BoxFit.cover, 34 | ), 35 | ), 36 | ), 37 | */Container( 38 | height: 300, 39 | child: KenBurns.multiple( 40 | maxAnimationDuration: Duration(seconds: 10), 41 | minAnimationDuration: Duration(seconds: 3), 42 | children: [ 43 | Image.network( 44 | "https://cdn.hasselblad.com/hasselblad-com/6cb604081ef3086569319ddb5adcae66298a28c5_x1d-ii-sample-01-web.jpg?auto=format&q=97", 45 | fit: BoxFit.cover, 46 | ), 47 | Image.network( 48 | "https://cdn.getyourguide.com/img/location_img-59-1969619245-148.jpg", 49 | fit: BoxFit.cover, 50 | ), 51 | Image.network( 52 | "https://www.theglobeandmail.com/resizer/vq3O7LI3hvsjTP2N0m9NwU4W3Eg=/1500x0/filters:quality(80)/arc-anglerfish-tgam-prod-tgam.s3.amazonaws.com/public/4ETF3GZR3NA3RDDW23XDRBKKCI", 53 | fit: BoxFit.cover, 54 | ), 55 | ], 56 | ), 57 | ), 58 | ], 59 | ), 60 | ), 61 | ), 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: kenburns_example 2 | description: Demonstrates how to use the kenburns plugin. 3 | publish_to: 'none' 4 | 5 | environment: 6 | sdk: ">=2.1.0 <3.0.0" 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | 12 | # The following adds the Cupertino Icons font to your application. 13 | # Use with the CupertinoIcons class for iOS style icons. 14 | cupertino_icons: ^0.1.2 15 | 16 | dev_dependencies: 17 | flutter_test: 18 | sdk: flutter 19 | 20 | kenburns: 21 | path: ../ 22 | 23 | # For information on the generic Dart part of this file, see the 24 | # following page: https://dart.dev/tools/pub/pubspec 25 | 26 | # The following section is specific to Flutter. 27 | flutter: 28 | 29 | # The following line ensures that the Material Icons font is 30 | # included with your application, so that you can use the icons in 31 | # the material Icons class. 32 | uses-material-design: true 33 | 34 | # To add assets to your application, add an assets section, like this: 35 | # assets: 36 | # - images/a_dot_burr.jpeg 37 | # - images/a_dot_ham.jpeg 38 | 39 | # An image asset can refer to one or more resolution-specific "variants", see 40 | # https://flutter.dev/assets-and-images/#resolution-aware. 41 | 42 | # For details regarding adding assets from package dependencies, see 43 | # https://flutter.dev/assets-and-images/#from-packages 44 | 45 | # To add custom fonts to your application, add a fonts section here, 46 | # in this "flutter" section. Each entry in this list should have a 47 | # "family" key with the font family name, and a "fonts" key with a 48 | # list giving the asset and other descriptors for the font. For 49 | # example: 50 | # fonts: 51 | # - family: Schyler 52 | # fonts: 53 | # - asset: fonts/Schyler-Regular.ttf 54 | # - asset: fonts/Schyler-Italic.ttf 55 | # style: italic 56 | # - family: Trajan Pro 57 | # fonts: 58 | # - asset: fonts/TrajanPro.ttf 59 | # - asset: fonts/TrajanPro_Bold.ttf 60 | # weight: 700 61 | # 62 | # For details regarding fonts from package dependencies, 63 | # see https://flutter.dev/custom-fonts/#from-packages 64 | -------------------------------------------------------------------------------- /example/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:kenburns_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => widget is Text && 22 | widget.data.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /lib/KenburnsGenerator.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | import 'dart:ui'; 3 | 4 | /// The generated configuration of KenBurns (scale, translation, duration) 5 | class KenBurnsGeneratorConfig { 6 | double newScale; 7 | Offset newTranslation; 8 | Duration newDuration; 9 | 10 | KenBurnsGeneratorConfig({ 11 | required this.newScale, 12 | required this.newTranslation, 13 | required this.newDuration, 14 | }); 15 | } 16 | 17 | /// The random scale, translation, duration generator 18 | class KenburnsGenerator { 19 | Random _random = Random(); 20 | 21 | KenburnsGenerator(); 22 | 23 | /// Generates a positive random integer distributed on the range 24 | double _randomValue(double min, double max) => 25 | min + _random.nextDouble() * (max - min); 26 | 27 | double generateNextScale( 28 | {double? lastScale, double? maxScale, required bool scaleDown}) { 29 | final double minScale = 1.0; 30 | if (scaleDown && minScale < lastScale!) { 31 | return _randomValue(minScale, lastScale); 32 | } else { 33 | return _randomValue(max(minScale, lastScale!), maxScale!); 34 | } 35 | } 36 | 37 | Duration generateNextDuration( 38 | {required double minDurationMillis, required double maxDurationMillis}) { 39 | return Duration( 40 | milliseconds: 41 | _randomValue(minDurationMillis, maxDurationMillis).floor()); 42 | } 43 | 44 | Offset generateNextTranslation( 45 | {required double width, 46 | required double height, 47 | required Size nextSize, 48 | double? nextScale}) { 49 | final availableXOffset = ((nextSize.width - width) / 2); 50 | final availableYOffset = ((nextSize.height - height) / 2); 51 | 52 | final x = _randomValue(-1 * availableXOffset, availableXOffset); 53 | final y = _randomValue(-1 * availableYOffset, availableYOffset); 54 | return Offset(x, y); 55 | } 56 | 57 | KenBurnsGeneratorConfig generateNextConfig({ 58 | required double width, 59 | required double height, 60 | double? maxScale, 61 | double? lastScale, 62 | required bool scaleDown, 63 | required double minDurationMillis, 64 | required double maxDurationMillis, 65 | Offset? lastTranslation, 66 | }) { 67 | Duration nextDuration; 68 | double nextScale; 69 | Offset nextTranslation; 70 | 71 | nextDuration = generateNextDuration( 72 | minDurationMillis: minDurationMillis, 73 | maxDurationMillis: maxDurationMillis, 74 | ); 75 | 76 | nextScale = generateNextScale( 77 | lastScale: lastScale, 78 | maxScale: maxScale, 79 | scaleDown: scaleDown, 80 | ); 81 | 82 | Size nextSize = Size(width * nextScale, height * nextScale); 83 | 84 | nextTranslation = generateNextTranslation( 85 | width: width, 86 | height: height, 87 | nextScale: nextScale, 88 | nextSize: nextSize, 89 | ); 90 | 91 | return KenBurnsGeneratorConfig( 92 | newDuration: nextDuration, 93 | newTranslation: nextTranslation, 94 | newScale: nextScale, 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/generated/i18n.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | 6 | // ignore_for_file: non_constant_identifier_names 7 | // ignore_for_file: camel_case_types 8 | // ignore_for_file: prefer_single_quotes 9 | 10 | // This file is automatically generated. DO NOT EDIT, all your changes would be lost. 11 | class S implements WidgetsLocalizations { 12 | const S(); 13 | 14 | static S? current; 15 | 16 | static const GeneratedLocalizationsDelegate delegate = 17 | GeneratedLocalizationsDelegate(); 18 | 19 | static S? of(BuildContext context) => Localizations.of(context, S); 20 | 21 | @override 22 | TextDirection get textDirection => TextDirection.ltr; 23 | } 24 | 25 | class $en extends S { 26 | const $en(); 27 | } 28 | 29 | class GeneratedLocalizationsDelegate extends LocalizationsDelegate { 30 | const GeneratedLocalizationsDelegate(); 31 | 32 | List get supportedLocales { 33 | return const [ 34 | Locale("en", ""), 35 | ]; 36 | } 37 | 38 | LocaleListResolutionCallback listResolution( 39 | {Locale? fallback, bool withCountry = true}) { 40 | return (List? locales, Iterable supported) { 41 | if (locales == null || locales.isEmpty) { 42 | return fallback ?? supported.first; 43 | } else { 44 | return _resolve(locales.first, fallback, supported, withCountry); 45 | } 46 | }; 47 | } 48 | 49 | LocaleResolutionCallback resolution( 50 | {Locale? fallback, bool withCountry = true}) { 51 | return (Locale? locale, Iterable supported) { 52 | return _resolve(locale, fallback, supported, withCountry); 53 | }; 54 | } 55 | 56 | @override 57 | Future load(Locale locale) { 58 | final String? lang = getLang(locale); 59 | if (lang != null) { 60 | switch (lang) { 61 | case "en": 62 | S.current = const $en(); 63 | return SynchronousFuture(S.current); 64 | default: 65 | // NO-OP. 66 | } 67 | } 68 | S.current = const S(); 69 | return SynchronousFuture(S.current); 70 | } 71 | 72 | @override 73 | bool isSupported(Locale locale) => _isSupported(locale, true); 74 | 75 | @override 76 | bool shouldReload(GeneratedLocalizationsDelegate old) => false; 77 | 78 | /// 79 | /// Internal method to resolve a locale from a list of locales. 80 | /// 81 | Locale _resolve(Locale? locale, Locale? fallback, Iterable supported, 82 | bool withCountry) { 83 | if (locale == null || !_isSupported(locale, withCountry)) { 84 | return fallback ?? supported.first; 85 | } 86 | 87 | final Locale languageLocale = Locale(locale.languageCode, ""); 88 | if (supported.contains(locale)) { 89 | return locale; 90 | } else if (supported.contains(languageLocale)) { 91 | return languageLocale; 92 | } else { 93 | final Locale fallbackLocale = fallback ?? supported.first; 94 | return fallbackLocale; 95 | } 96 | } 97 | 98 | /// 99 | /// Returns true if the specified locale is supported, false otherwise. 100 | /// 101 | bool _isSupported(Locale locale, bool withCountry) { 102 | for (Locale supportedLocale in supportedLocales) { 103 | // Language must always match both locales. 104 | if (supportedLocale.languageCode != locale.languageCode) { 105 | continue; 106 | } 107 | 108 | // If country code matches, return this locale. 109 | if (supportedLocale.countryCode == locale.countryCode) { 110 | return true; 111 | } 112 | 113 | // If no country requirement is requested, check if this locale has no country. 114 | if (true != withCountry && 115 | (supportedLocale.countryCode == null || 116 | supportedLocale.countryCode!.isEmpty)) { 117 | return true; 118 | } 119 | } 120 | return false; 121 | } 122 | } 123 | 124 | String? getLang(Locale l) => l.countryCode != null && l.countryCode!.isEmpty 125 | ? l.languageCode 126 | : l.toString(); 127 | -------------------------------------------------------------------------------- /lib/kenburns.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'KenburnsGenerator.dart'; 4 | 5 | /// KenBurns widget, please provide a `child` Widget, 6 | /// Will animate the child, using random scale, translation & duration 7 | class KenBurns extends StatefulWidget { 8 | final Widget? child; 9 | 10 | /// minimum translation & scale duration, not null 11 | final Duration minAnimationDuration; 12 | 13 | /// maximum translation & scale duration, not null 14 | final Duration maxAnimationDuration; 15 | 16 | /// Maximum allowed child scale, > 1 17 | final double maxScale; 18 | 19 | //region multiple images 20 | /// If specified (using the constructor multiple) 21 | /// Will animate [childLoop] each children then will fade to the next child 22 | /// Not Null & Size must be > 1 23 | /// if size == 1 -> Will use the KenBurns as a single child 24 | final List? children; 25 | 26 | /// If specified (using the constructor multiple) 27 | /// Will specify the fade in duration between 2 child 28 | final Duration? childrenFadeDuration; 29 | 30 | /// If specified (using the constructor multiple) 31 | /// Will determine how many times each child will stay in the KenBurns 32 | /// Until the next child will be displayed 33 | final int? childLoop; 34 | 35 | //endregion 36 | 37 | /// Constructor for a single child KenBurns 38 | KenBurns({ 39 | required Widget this.child, 40 | this.minAnimationDuration = const Duration(milliseconds: 3000), 41 | this.maxAnimationDuration = const Duration(milliseconds: 10000), 42 | this.maxScale = 8, 43 | }) : this.childrenFadeDuration = null, 44 | this.children = null, 45 | this.childLoop = null, 46 | assert(minAnimationDuration.inMilliseconds > 0), 47 | assert(maxAnimationDuration.inMilliseconds > 0), 48 | assert(minAnimationDuration < maxAnimationDuration), 49 | assert(maxScale > 1); 50 | 51 | /// Constructor for multiple child KenBurns 52 | KenBurns.multiple( 53 | {this.minAnimationDuration = const Duration(milliseconds: 1000), 54 | this.maxAnimationDuration = const Duration(milliseconds: 10000), 55 | this.maxScale = 10, 56 | this.childLoop = 3, 57 | this.children, 58 | this.childrenFadeDuration = const Duration(milliseconds: 800)}) 59 | : this.child = null; 60 | 61 | @override 62 | _KenBurnsState createState() => _KenBurnsState(); 63 | } 64 | 65 | class _KenBurnsState extends State with TickerProviderStateMixin { 66 | bool _running = false; 67 | 68 | /// The generated scale controller 69 | /// Will be destroyed / created at each loop (because duration is different) 70 | AnimationController? _scaleController; 71 | 72 | /// The generated scale controller's animation 73 | /// Will be destroyed / created at each loop (because duration is different) 74 | late Animation _scaleAnim; 75 | 76 | /// The generated translation controller 77 | /// Will be destroyed / created at each loop (because duration is different) 78 | AnimationController? _translationController; 79 | 80 | /// The generated translation controller's X animation 81 | /// Will be destroyed / created at each loop (because duration is different) 82 | late Animation _translationXAnim; 83 | 84 | /// The generated translation controller's Y animation 85 | /// Will be destroyed / created at each loop (because duration is different) 86 | late Animation _translationYAnim; 87 | 88 | /// The animated current scale 89 | double _currentScale = 1; 90 | 91 | /// The animated current translation X 92 | double _currentTranslationX = 0; 93 | 94 | /// The animated current translation Y 95 | double _currentTranslationY = 0; 96 | 97 | /// If true : next animation will scale down, 98 | /// false : next animation will scale up 99 | bool _scaleDown = true; 100 | 101 | /// For developpers : set to true to enable logs 102 | bool _displayLogs = false; 103 | 104 | /// The random [scale/duration/translation] generator 105 | KenburnsGenerator _kenburnsGenerator = KenburnsGenerator(); 106 | 107 | //region multiple childs 108 | /// if true : the widget setup is multipleImages 109 | bool get _displayMultipleImage => 110 | widget.children != null && widget.children!.length > 1; 111 | int _nextChildIndex = -1; 112 | int _currentChildIndex = 0; 113 | int _currentChildLoop = 0; 114 | 115 | double _opacityCurrentChild = 1; 116 | double _opacityNextChild = 0; 117 | 118 | /// The generated fade controller 119 | AnimationController? _fadeController; 120 | 121 | /// The generated opacity fade in controller's animation 122 | late Animation _fadeInAnim; 123 | 124 | /// The generated opacity fade out controller's animation 125 | late Animation _fadeOutAnim; 126 | 127 | //endregion 128 | 129 | /// Generate the fade (in & out) animations 130 | Future _createFadeAnimations() async { 131 | _fadeController?.dispose(); 132 | _fadeController = AnimationController( 133 | duration: widget.childrenFadeDuration, 134 | vsync: this, 135 | ); 136 | _fadeInAnim = Tween(begin: 0.0, end: 1.0).animate( 137 | CurvedAnimation(parent: _fadeController!, curve: Curves.linear), 138 | )..addListener(() { 139 | setState(() { 140 | _opacityNextChild = _fadeInAnim.value; 141 | }); 142 | }); 143 | _fadeOutAnim = Tween(begin: 1.0, end: 0.0).animate( 144 | CurvedAnimation(parent: _fadeController!, curve: Curves.linear), 145 | )..addListener(() { 146 | setState(() { 147 | _opacityCurrentChild = _fadeOutAnim.value; 148 | }); 149 | }); 150 | } 151 | 152 | /// Generate the next animation [scale, duration, translation] 153 | /// Using the [KenBurnsGenerator] generateNextConfig 154 | Future _createNextAnimations( 155 | {required double height, required double width}) async { 156 | final KenBurnsGeneratorConfig nextConfig = 157 | _kenburnsGenerator.generateNextConfig( 158 | width: width, 159 | height: height, 160 | maxScale: widget.maxScale, 161 | lastScale: _currentScale, 162 | scaleDown: _scaleDown, 163 | minDurationMillis: 164 | widget.minAnimationDuration.inMilliseconds.toDouble(), 165 | maxDurationMillis: 166 | widget.maxAnimationDuration.inMilliseconds.toDouble(), 167 | lastTranslation: 168 | Offset(_currentTranslationX, _currentTranslationY)); 169 | 170 | /// Recreate the scale animations 171 | _scaleController?.dispose(); 172 | _scaleController = AnimationController( 173 | duration: nextConfig.newDuration, 174 | vsync: this, 175 | ); 176 | 177 | _scaleAnim = 178 | Tween(begin: this._currentScale, end: nextConfig.newScale).animate( 179 | CurvedAnimation(parent: _scaleController!, curve: Curves.linear), 180 | )..addListener(() { 181 | setState(() { 182 | _currentScale = _scaleAnim.value; 183 | }); 184 | }); 185 | 186 | /// Recreate the translations animations 187 | _translationController?.dispose(); 188 | _translationController = AnimationController( 189 | duration: nextConfig.newDuration, 190 | vsync: this, 191 | ); 192 | 193 | _translationXAnim = Tween( 194 | begin: this._currentTranslationX, end: nextConfig.newTranslation.dx) 195 | .animate( 196 | CurvedAnimation(parent: _translationController!, curve: Curves.linear), 197 | )..addListener(() { 198 | setState(() { 199 | _currentTranslationX = _translationXAnim.value; 200 | }); 201 | }); 202 | _translationYAnim = Tween( 203 | begin: this._currentTranslationY, end: nextConfig.newTranslation.dy) 204 | .animate( 205 | CurvedAnimation(parent: _translationController!, curve: Curves.linear), 206 | )..addListener(() { 207 | setState(() { 208 | _currentTranslationY = _translationYAnim.value; 209 | }); 210 | }); 211 | 212 | log("kenburns started"); 213 | log("kenburns d(${nextConfig.newDuration}) translation(${nextConfig.newTranslation.dx}, ${nextConfig.newTranslation.dy}) scale(${nextConfig.newScale})"); 214 | 215 | /// Next scale animation will be inverted 216 | _scaleDown = !_scaleDown; 217 | 218 | /// fire scale & translation animations 219 | await Future.wait( 220 | [_scaleController!.forward(), _translationController!.forward()]); 221 | 222 | log("kenburns finished"); 223 | } 224 | 225 | /// Display on debug logs (enable with [_displayLogs]) 226 | void log(String text) { 227 | if (_displayLogs) { 228 | print(text); 229 | } 230 | } 231 | 232 | /// Fire the fade (in/out) animation 233 | Future _fade() async { 234 | await _fadeController!.forward(); 235 | 236 | if (!_running) return; 237 | 238 | setState(() { 239 | _currentChildIndex = _nextChildIndex; 240 | 241 | _nextChildIndex = _currentChildIndex + 1; 242 | _nextChildIndex = _nextChildIndex % widget.children!.length; 243 | }); 244 | 245 | _fadeController!.reset(); 246 | } 247 | 248 | Future fire({double? height, double? width}) async { 249 | _running = true; 250 | if (_displayMultipleImage) { 251 | _nextChildIndex = 1; 252 | 253 | /// Create one time the fade animation 254 | await _createFadeAnimations(); 255 | 256 | /// Cancel if _running go to false 257 | while (_running) { 258 | await _createNextAnimations(width: width!, height: height!); 259 | if (!_running) return; 260 | 261 | if (_currentChildLoop % widget.childLoop! == 0) { 262 | _fade(); //parallel 263 | } 264 | _currentChildLoop++; 265 | } 266 | } else { 267 | /// Cancel if _running go to false 268 | while (_running) { 269 | await _createNextAnimations(width: width!, height: height!); 270 | } 271 | } 272 | } 273 | 274 | @override 275 | void initState() { 276 | /// Reset _runnint state 277 | _running = false; 278 | super.initState(); 279 | } 280 | 281 | @override 282 | void didUpdateWidget(KenBurns oldWidget) { 283 | super.didUpdateWidget(oldWidget); 284 | 285 | if (oldWidget.children?.length != oldWidget.children?.length) { 286 | _running = false; 287 | _scaleController?.dispose(); 288 | _fadeController?.dispose(); 289 | _translationController?.dispose(); 290 | _currentChildIndex = 0; 291 | } 292 | } 293 | 294 | @override 295 | Widget build(BuildContext context) { 296 | /// Layout builder to provide constrains height/width 297 | return LayoutBuilder(builder: (context, constraints) { 298 | /// create the animation only if we have a size (not possible in initState()) 299 | if (!_running) { 300 | fire(height: constraints.maxHeight, width: constraints.maxWidth); 301 | } 302 | return ClipRect( 303 | ///Clip because we scale up children, if not clipped : child can take all the screen 304 | /// Apply the current animated translation 305 | child: Transform.translate( 306 | offset: Offset(_currentTranslationX, _currentTranslationY), 307 | 308 | /// Apply the current animated scale 309 | child: Transform.scale( 310 | scale: _currentScale, 311 | child: _buildChild(), 312 | ), 313 | ), 314 | ); 315 | }); 316 | } 317 | 318 | Widget _buildChild() { 319 | if (_displayMultipleImage) { 320 | /// If the [currentChildIndex] changed (different than [lastChildIndex]) 321 | /// -> we animate to display the next child 322 | /// We use the stack to keep the same structure as multiple/single child 323 | return Stack(fit: StackFit.expand, children: [ 324 | Opacity( 325 | opacity: _opacityCurrentChild, 326 | child: widget.children![_currentChildIndex]), 327 | Opacity( 328 | opacity: _opacityNextChild, 329 | child: widget.children![_nextChildIndex]), 330 | ]); 331 | } else { 332 | /// If we have only 1 child 333 | /// We use the stack to keep the same structure as multiple/single child 334 | return Stack( 335 | fit: StackFit.expand, 336 | children: [ 337 | widget.child!, 338 | ], 339 | ); 340 | } 341 | } 342 | 343 | @override 344 | void dispose() { 345 | /// will stop the [fire()] loop 346 | _running = false; 347 | _scaleController?.dispose(); 348 | _translationController?.dispose(); 349 | _fadeController?.dispose(); 350 | super.dispose(); 351 | } 352 | } 353 | -------------------------------------------------------------------------------- /medias/kenburns.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florent37/Flutter-KenBurns/a42c5b1cef86e5fc15b5fbba8d49c4210f491f99/medias/kenburns.gif -------------------------------------------------------------------------------- /medias/kenburns_slow.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florent37/Flutter-KenBurns/a42c5b1cef86e5fc15b5fbba8d49c4210f491f99/medias/kenburns_slow.gif -------------------------------------------------------------------------------- /publish.sh: -------------------------------------------------------------------------------- 1 | flutter format lib/ 2 | flutter format lib/generated/ 3 | flutter pub pub publish -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: kenburns 2 | description: Kenburns effect on flutter. The Ken Burns effect is a type of panning and zooming effect used in video production from still imagery. 3 | version: 1.0.5 4 | #author: Florent Champigny 5 | homepage: https://github.com/florent37/Flutter-KenBurns 6 | 7 | environment: 8 | sdk: '>=2.12.0 <3.0.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | flutter_test: 16 | sdk: flutter 17 | 18 | # The following section is specific to Flutter. 19 | flutter: 20 | -------------------------------------------------------------------------------- /res/values/strings_en.arb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/florent37/Flutter-KenBurns/a42c5b1cef86e5fc15b5fbba8d49c4210f491f99/res/values/strings_en.arb -------------------------------------------------------------------------------- /test/kenburns_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | void main() { 5 | const MethodChannel channel = MethodChannel('kenburns'); 6 | 7 | setUp(() { 8 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 9 | return '42'; 10 | }); 11 | }); 12 | 13 | tearDown(() { 14 | channel.setMockMethodCallHandler(null); 15 | }); 16 | 17 | } 18 | --------------------------------------------------------------------------------