├── .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 | [](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 | [](
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 | [](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 |
--------------------------------------------------------------------------------