Compare commits

...

8 Commits

Author SHA1 Message Date
fc450a88f4 Fix some warnings with Flutter 3.3 (#235) 2022-09-14 13:21:29 +02:00
c0bc257f4f use FilterQuality.low to default when draw image layer to fix some image render sawtooth (#230) 2022-08-25 11:55:26 +02:00
0e7499d82e Allow AlignmentGeometry for alignment (#228)
Thanks for the contribution
2022-08-03 11:52:58 +02:00
4cd9ec759a Update publish-on-pub action 2022-07-27 11:15:31 +02:00
d8f5b872ef [feat] support image layer quality setting (#214) 2022-07-27 11:11:26 +02:00
bc3eb4621b Add latest feature/fixes from Lottie-Android (#209)
- Added support for rounded corners on shapes and rects
- Add support for text in dynamic properties (ValueDelegate)
- Improve stroke with offset
- Add support for reversed polystar paths
- Enforce order of operations to avoid rounding errors
2022-04-14 22:20:22 +02:00
08d7da747a Add pubignore 2022-02-14 10:07:39 +01:00
b2ebd2058d Fix new lints in Flutter 2.10 (#196) 2022-02-14 09:31:30 +01:00
132 changed files with 10052 additions and 307 deletions

View File

@ -10,11 +10,11 @@ jobs:
name: Flutter analyze
strategy:
matrix:
flutter: ['beta']
flutter: ['stable']
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- uses: subosito/flutter-action@v1
- uses: subosito/flutter-action@v2
with:
channel: ${{ matrix.flutter }}
- run: flutter doctor
@ -22,8 +22,8 @@ jobs:
- run: flutter pub get
working-directory: example
- run: flutter analyze
- run: flutter test test # https://github.com/flutter/flutter/issues/20907
- run: flutter test test
- run: flutter test
- run: flutter test
working-directory: example
- run: flutter pub run tool/prepare_submit.dart
- name: "check for uncommitted changes"
@ -38,10 +38,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: subosito/flutter-action@v1
- uses: subosito/flutter-action@v2
with:
channel: 'beta'
- run: flutter config --enable-web
channel: 'stable'
- run: flutter precache web
- run: flutter pub get
working-directory: example

View File

@ -8,9 +8,9 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: subosito/flutter-action@v1
- uses: subosito/flutter-action@v2
with:
channel: 'beta'
channel: 'stable'
- run: flutter pub get
- run: flutter pub run tool/publish/comment_dependency_overrides.dart
- run: flutter pub get

1
.gitignore vendored
View File

@ -3,6 +3,7 @@ _*
!.gitignore
!.github
!.pubignore
**/failures/*.png

15
.pubignore Normal file
View File

@ -0,0 +1,15 @@
.dart_tool/
.packages
build/
doc/api/
*.iml
_*
.*
**/failures/*.png
test/golden/
test/goldens/
example/assets/
example/android/
example/ios/
example/macos/
example/web/

View File

@ -1,3 +1,40 @@
## [1.4.3]
- Fixed some lints with Flutter 3.3.
## [1.4.2]
- Use `FilterQuality.low` as default to draw image layers.
## [1.4.1]
- Allow `AlignmentGeometry` for `alignment`.
## [1.4.0]
- Added `filterQuality` property to control the performance vs quality trade-off to use when drawing images
## [1.3.0]
- Added support for rounded corners on shapes and rects
- Add support for text in dynamic properties (`ValueDelegate`)
Example:
```dart
Lottie.asset(
'assets/DynamicText.json',
delegates: LottieDelegates(values: [
ValueDelegate.text(
['Text layer'], // The path to the text element to change
value: 'The new text',
),
]),
)
```
- Improve stroke with offset
- Add support for reversed polystar paths
- Enforce order of operations to avoid rounding errors
## [1.2.2]
- Internal maintenance: fix lints for Flutter 2.10
## [1.2.1]
- Fix: Revert Cubic to `PathInterpolator.cubic`

View File

@ -64,7 +64,7 @@ class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
@ -139,7 +139,7 @@ class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
_MyWidgetState createState() => _MyWidgetState();
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {

View File

@ -5,3 +5,9 @@ gradle-wrapper.jar
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks

View File

@ -8,7 +8,7 @@ if (localPropertiesFile.exists()) {
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new Exception("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
@ -26,23 +26,28 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 28
compileSdkVersion flutter.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
applicationId "com.github.xvrh.lottie.sample"
minSdkVersion 16
targetSdkVersion 28
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.github.xvrh.lottie.example"
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
@ -60,7 +65,4 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.xvrh.lottie.sample">
package="com.github.xvrh.lottie.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

View File

@ -1,21 +1,25 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.xvrh.lottie.sample">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
package="com.github.xvrh.lottie.example">
<application
android:name="io.flutter.app.FlutterApplication"
android:label="example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>

View File

@ -0,0 +1,6 @@
package com.github.xvrh.lottie.example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@ -1,12 +0,0 @@
package com.github.xvrh.lottie.sample
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugins.GeneratedPluginRegistrant
class MainActivity: FlutterActivity() {
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -1,8 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -1,5 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.xvrh.lottie.sample">
package="com.github.xvrh.lottie.example">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->

View File

@ -1,12 +1,12 @@
buildscript {
ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.6.10'
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@ -14,7 +14,7 @@ buildscript {
allprojects {
repositories {
google()
jcenter()
mavenCentral()
}
}

View File

@ -1,4 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

View File

@ -1,15 +1,11 @@
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,288 @@
{
"v": "5.8.2",
"fr": 24,
"ip": 0,
"op": 94,
"w": 150,
"h": 150,
"nm": "Anim_load",
"ddd": 0,
"assets": [],
"layers": [
{
"ddd": 0,
"ind": 1,
"ty": 4,
"nm": "LF03",
"sr": 1,
"ks": {
"o": {
"a": 0,
"k": 100,
"ix": 11
},
"r": {
"a": 0,
"k": -19,
"ix": 10
},
"p": {
"a": 0,
"k": [
75,
76.005,
0
],
"ix": 2,
"l": 2
},
"a": {
"a": 0,
"k": [
0,
0,
0
],
"ix": 1,
"l": 2
},
"s": {
"a": 0,
"k": [
19.986,
19.986,
100
],
"ix": 6,
"l": 2
}
},
"ao": 0,
"shapes": [
{
"ty": "gr",
"it": [
{
"ty": "sr",
"sy": 1,
"d": 3,
"pt": {
"a": 0,
"k": 14,
"ix": 3
},
"p": {
"a": 0,
"k": [
0,
0
],
"ix": 4
},
"r": {
"a": 0,
"k": 0,
"ix": 5
},
"ir": {
"a": 0,
"k": 91.612,
"ix": 6
},
"is": {
"a": 0,
"k": 0,
"ix": 8
},
"or": {
"a": 0,
"k": 113.225,
"ix": 7
},
"os": {
"a": 0,
"k": 0,
"ix": 9
},
"ix": 1,
"nm": "Polystar Path 1",
"mn": "ADBE Vector Shape - Star",
"hd": false
},
{
"ty": "st",
"c": {
"a": 0,
"k": [
0.937254905701,
0.89411765337,
0.850980401039,
1
],
"ix": 3
},
"o": {
"a": 0,
"k": 100,
"ix": 4
},
"w": {
"a": 0,
"k": 30,
"ix": 5
},
"lc": 1,
"lj": 1,
"ml": 4,
"bm": 0,
"nm": "Stroke 1",
"mn": "ADBE Vector Graphic - Stroke",
"hd": false
},
{
"ty": "tr",
"p": {
"a": 0,
"k": [
-1.979,
-0.604
],
"ix": 2
},
"a": {
"a": 0,
"k": [
0,
0
],
"ix": 1
},
"s": {
"a": 0,
"k": [
100,
100
],
"ix": 3
},
"r": {
"a": 0,
"k": 0,
"ix": 6
},
"o": {
"a": 0,
"k": 100,
"ix": 7
},
"sk": {
"a": 0,
"k": 0,
"ix": 4
},
"sa": {
"a": 0,
"k": 0,
"ix": 5
},
"nm": "Transform"
}
],
"nm": "Polystar 1",
"np": 2,
"cix": 2,
"bm": 0,
"ix": 1,
"mn": "ADBE Vector Group",
"hd": false
},
{
"ty": "tm",
"s": {
"a": 1,
"k": [
{
"i": {
"x": [
0.667
],
"y": [
1
]
},
"o": {
"x": [
0.333
],
"y": [
0
]
},
"t": -0.127,
"s": [
100
]
},
{
"t": 32,
"s": [
0
]
}
],
"ix": 1
},
"e": {
"a": 1,
"k": [
{
"i": {
"x": [
0.833
],
"y": [
1
]
},
"o": {
"x": [
0.333
],
"y": [
0
]
},
"t": 36,
"s": [
100
]
},
{
"t": 68,
"s": [
0
]
}
],
"ix": 2
},
"o": {
"a": 0,
"k": -43,
"ix": 3
},
"m": 1,
"ix": 2,
"nm": "Trim Paths 1",
"mn": "ADBE Vector Filter - Trim",
"hd": false
}
],
"ip": 0,
"op": 94,
"st": 18,
"bm": 0
}
],
"markers": []
}

View File

@ -0,0 +1,390 @@
{
"v": "5.8.1",
"fr": 29.9700012207031,
"ip": 0,
"op": 900.000036657751,
"w": 1000,
"h": 1000,
"nm": "Rounded Corners",
"ddd": 0,
"assets": [],
"layers": [
{
"ddd": 0,
"ind": 1,
"ty": 4,
"nm": "Shape Layer 1",
"sr": 1,
"ks": {
"o": {
"a": 0,
"k": 100,
"ix": 11
},
"r": {
"a": 0,
"k": 0,
"ix": 10
},
"p": {
"a": 0,
"k": [
500,
500,
0
],
"ix": 2,
"l": 2
},
"a": {
"a": 0,
"k": [
0,
0,
0
],
"ix": 1,
"l": 2
},
"s": {
"a": 0,
"k": [
100,
100,
100
],
"ix": 6,
"l": 2
}
},
"ao": 0,
"shapes": [
{
"ind": 0,
"ty": "sh",
"ix": 1,
"ks": {
"a": 0,
"k": {
"i": [
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
]
],
"o": [
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
]
],
"v": [
[
-75,
-475
],
[
-75,
-75
],
[
-475,
-75
],
[
-475,
-475
]
],
"c": true
},
"ix": 2
},
"nm": "Path 1",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "rc",
"d": 1,
"s": {
"a": 0,
"k": [
400,
400
],
"ix": 2
},
"p": {
"a": 0,
"k": [
275,
-275
],
"ix": 3
},
"r": {
"a": 0,
"k": 0,
"ix": 4
},
"nm": "Rectangle Path 1",
"mn": "ADBE Vector Shape - Rect",
"hd": false
},
{
"ty": "sr",
"sy": 1,
"d": 1,
"pt": {
"a": 0,
"k": 5,
"ix": 3
},
"p": {
"a": 0,
"k": [
275,
275
],
"ix": 4
},
"r": {
"a": 0,
"k": 0,
"ix": 5
},
"ir": {
"a": 0,
"k": 107,
"ix": 6
},
"is": {
"a": 0,
"k": 0,
"ix": 8
},
"or": {
"a": 0,
"k": 275,
"ix": 7
},
"os": {
"a": 0,
"k": 0,
"ix": 9
},
"ix": 3,
"nm": "Polystar Path 1",
"mn": "ADBE Vector Shape - Star",
"hd": false
},
{
"ind": 3,
"ty": "sh",
"ix": 4,
"ks": {
"a": 0,
"k": {
"i": [
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
]
],
"o": [
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
],
[
0,
0
]
],
"v": [
[
-275,
0
],
[
-212.107,
188.435
],
[
-13.459,
190.02
],
[
-173.237,
308.065
],
[
-113.359,
497.48
],
[
-275,
382
],
[
-436.641,
497.48
],
[
-376.763,
308.065
],
[
-536.541,
190.02
],
[
-337.893,
188.435
]
],
"c": true
},
"ix": 2
},
"nm": "Path 2",
"mn": "ADBE Vector Shape - Group",
"hd": false
},
{
"ty": "rd",
"nm": "Round Corners 1",
"r": {
"a": 0,
"k": 136,
"ix": 1
},
"ix": 5,
"mn": "ADBE Vector Filter - RC",
"hd": false
},
{
"ty": "fl",
"c": {
"a": 0,
"k": [
1,
0,
0,
1
],
"ix": 4
},
"o": {
"a": 0,
"k": 100,
"ix": 5
},
"r": 1,
"bm": 0,
"nm": "Fill 1",
"mn": "ADBE Vector Graphic - Fill",
"hd": false
}
],
"ip": 0,
"op": 900.000036657751,
"st": 0,
"bm": 0
}
],
"markers": []
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -25,7 +25,7 @@ class __PageState extends State<_Page> {
void initState() {
super.initState();
SchedulerBinding.instance!.addPostFrameCallback((_) => _showLoader());
SchedulerBinding.instance.addPostFrameCallback((_) => _showLoader());
}
void _showLoader() {

View File

@ -7,7 +7,7 @@ class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {

View File

@ -15,7 +15,7 @@ class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:lottie/lottie.dart';
void main() => runApp(const MyApp());
@ -22,7 +21,7 @@ class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
_MyWidgetState createState() => _MyWidgetState();
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:lottie/lottie.dart';
void main() => runApp(const MyApp());
@ -23,7 +22,7 @@ class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
@override
_MyWidgetState createState() => _MyWidgetState();
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {

View File

@ -1,4 +1,3 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import 'package:lottie/lottie.dart';
@ -11,7 +10,7 @@ class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
@override
_AppState createState() => _AppState();
State<App> createState() => _AppState();
}
class _AppState extends State<App> with TickerProviderStateMixin {
@ -72,7 +71,7 @@ class _AppState extends State<App> with TickerProviderStateMixin {
_color = newColor;
});
},
showLabel: false,
labelTypes: const [],
enableAlpha: false,
pickerAreaHeightPercent: 0.8,
),

View File

@ -10,7 +10,7 @@ class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
@override
_AppState createState() => _AppState();
State<App> createState() => _AppState();
}
class _AppState extends State<App> with TickerProviderStateMixin {

View File

@ -9,7 +9,7 @@ class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
@override
_AppState createState() => _AppState();
State<App> createState() => _AppState();
}
class _AppState extends State<App> with TickerProviderStateMixin {

View File

@ -12,7 +12,7 @@ class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
@override
_AppState createState() => _AppState();
State<App> createState() => _AppState();
}
class _AppState extends State<App> with TickerProviderStateMixin {

View File

@ -7,7 +7,7 @@ class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {

View File

@ -15,7 +15,7 @@ class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
@override
_AppState createState() => _AppState();
State<App> createState() => _AppState();
}
class _AppState extends State<App> with TickerProviderStateMixin {

View File

@ -13,7 +13,7 @@ class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {

View File

@ -1,4 +1,3 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:lottie/lottie.dart';
@ -93,7 +92,7 @@ class Detail extends StatefulWidget {
const Detail(this.assetName, {Key? key}) : super(key: key);
@override
_DetailState createState() => _DetailState();
State<Detail> createState() => _DetailState();
}
class _DetailState extends State<Detail> with TickerProviderStateMixin {

View File

@ -15,8 +15,8 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
path_provider_macos: a0a3fd666cb7cd0448e936fb4abad4052961002b
path_provider_macos: 160cab0d5461f0c0e02995469a98f24bdb9a3f1f
PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c
COCOAPODS: 1.10.1
COCOAPODS: 1.11.3

View File

@ -7,14 +7,14 @@ packages:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.2"
version: "3.3.1"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.5.0"
version: "2.9.0"
boolean_selector:
dependency: transitive
description:
@ -28,56 +28,49 @@ packages:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.2.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.1.1"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
version: "1.16.0"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
version: "3.0.2"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.1"
ffi:
dependency: transitive
description:
name: ffi
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.2"
version: "2.0.1"
file:
dependency: transitive
description:
name: file
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.2"
version: "6.1.4"
flutter:
dependency: "direct main"
description: flutter
@ -89,14 +82,14 @@ packages:
name: flutter_colorpicker
url: "https://pub.dartlang.org"
source: hosted
version: "0.5.0"
version: "1.0.3"
flutter_lints:
dependency: "direct dev"
description:
name: flutter_lints
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
version: "2.0.1"
flutter_test:
dependency: "direct dev"
description: flutter
@ -108,126 +101,140 @@ packages:
name: golden_toolkit
url: "https://pub.dartlang.org"
source: hosted
version: "0.9.0"
version: "0.13.0"
http:
dependency: "direct main"
description:
name: http
url: "https://pub.dartlang.org"
source: hosted
version: "0.13.3"
version: "0.13.5"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
version: "4.0.1"
lints:
dependency: transitive
description:
name: lints
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
version: "2.0.0"
logging:
dependency: "direct main"
description:
name: logging
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
version: "1.0.2"
lottie:
dependency: "direct main"
description:
path: ".."
relative: true
source: path
version: "1.2.0"
version: "1.4.3"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10"
version: "0.12.12"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.5"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.8.0"
path:
dependency: transitive
dependency: "direct main"
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
version: "1.8.2"
path_provider:
dependency: "direct main"
description:
name: path_provider
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
version: "2.0.11"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.20"
path_provider_ios:
dependency: transitive
description:
name: path_provider_ios
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.11"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.1.7"
path_provider_macos:
dependency: transitive
description:
name: path_provider_macos
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.0.6"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
version: "2.0.4"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.1"
version: "2.1.3"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
version: "3.1.0"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.0"
version: "2.1.2"
process:
dependency: transitive
description:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.1"
version: "4.2.4"
sky_engine:
dependency: transitive
description: flutter
@ -239,7 +246,7 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
version: "1.9.0"
stack_trace:
dependency: transitive
description:
@ -260,49 +267,49 @@ packages:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.1.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.0"
version: "0.4.12"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.3.1"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "2.1.2"
win32:
dependency: transitive
description:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.5"
version: "3.0.0"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.0"
version: "0.2.0+2"
sdks:
dart: ">=2.13.0 <3.0.0"
flutter: ">=1.20.0"
dart: ">=2.18.0 <3.0.0"
flutter: ">=3.3.0"

View File

@ -13,6 +13,7 @@ dependencies:
logging:
lottie:
path: ../
path:
path_provider:
dev_dependencies:

BIN
example/web/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 917 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

35
example/web/manifest.json Normal file
View File

@ -0,0 +1,35 @@
{
"name": "example",
"short_name": "example",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",
"theme_color": "#0175C2",
"description": "A new Flutter project.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
},
{
"src": "icons/Icon-maskable-192.png",
"sizes": "192x192",
"type": "image/png",
"purpose": "maskable"
},
{
"src": "icons/Icon-maskable-512.png",
"sizes": "512x512",
"type": "image/png",
"purpose": "maskable"
}
]
}

View File

@ -196,7 +196,8 @@ abstract class BaseStrokeContent
void _applyTrimPath(
Canvas canvas, _PathGroup pathGroup, Matrix4 parentMatrix) {
L.beginSection('StrokeContent#applyTrimPath');
if (pathGroup.trimPath == null) {
var trimPath = pathGroup.trimPath;
if (trimPath == null) {
L.endSection('StrokeContent#applyTrimPath');
return;
}
@ -205,13 +206,24 @@ abstract class BaseStrokeContent
_path.addPath(pathGroup.paths[j].getPath(), Offset.zero,
matrix4: parentMatrix.storage);
}
var animStartValue = trimPath.start.value / 100;
var animEndValue = trimPath.end.value / 100;
var animOffsetValue = trimPath.offset.value / 360;
// If the start-end is ~100, consider it to be the full path.
if (animStartValue < 0.01 && animEndValue > 0.99) {
canvas.drawPath(_path, paint);
L.endSection('StrokeContent#applyTrimPath');
return;
}
var pathMetrics = _path.computeMetrics().toList();
var totalLength = pathMetrics.fold<double>(0.0, (a, b) => a + b.length);
var trimPath = pathGroup.trimPath!;
var offsetLength = totalLength * trimPath.offset.value / 360.0;
var startLength = totalLength * trimPath.start.value / 100.0 + offsetLength;
var endLength = totalLength * trimPath.end.value / 100.0 + offsetLength;
var offsetLength = totalLength * animOffsetValue;
var startLength = totalLength * animStartValue + offsetLength;
var endLength = min(totalLength * animEndValue + offsetLength,
startLength + totalLength - 1);
var currentLength = 0.0;
for (var j = pathGroup.paths.length - 1; j >= 0; j--) {

View File

@ -134,6 +134,9 @@ class PolystarContent implements PathContent, KeyPathElementContent {
currentAngle = radians(currentAngle);
// adjust current angle for partial points
var anglePerPoint = 2 * pi / points;
if (_polystarShape.isReversed) {
anglePerPoint *= -1;
}
var halfAnglePerPoint = anglePerPoint / 2.0;
var partialPointAmount = points - points.toInt();
if (partialPointAmount != 0) {

View File

@ -15,6 +15,7 @@ import 'compound_trim_path_content.dart';
import 'content.dart';
import 'key_path_element_content.dart';
import 'path_content.dart';
import 'rounded_corners_content.dart';
import 'trim_path_content.dart';
class RectangleContent implements KeyPathElementContent, PathContent {
@ -29,6 +30,9 @@ class RectangleContent implements KeyPathElementContent, PathContent {
final BaseKeyframeAnimation<Object, double> _cornerRadiusAnimation;
final CompoundTrimPathContent _trimPaths = CompoundTrimPathContent();
/// This corner radius is from a layer item. The first one is from the roundedness on this specific rect.
BaseKeyframeAnimation<double, double>? _roundedCornersAnimation;
bool _isPathValid = false;
RectangleContent(
@ -61,6 +65,8 @@ class RectangleContent implements KeyPathElementContent, PathContent {
var trimPath = content;
_trimPaths.addTrimPath(trimPath);
trimPath.addListener(invalidate);
} else if (content is RoundedCornersContent) {
_roundedCornersAnimation = content.roundedCorners;
}
}
}
@ -82,6 +88,10 @@ class RectangleContent implements KeyPathElementContent, PathContent {
var halfWidth = size.dx / 2.0;
var halfHeight = size.dy / 2.0;
var radius = _cornerRadiusAnimation.value;
var roundedCornersAnimation = _roundedCornersAnimation;
if (radius == 0 && roundedCornersAnimation != null) {
radius = min(roundedCornersAnimation.value, min(halfWidth, halfHeight));
}
var maxRadius = min(halfWidth, halfHeight);
if (radius > maxRadius) {
radius = maxRadius;

View File

@ -0,0 +1,236 @@
import 'dart:math' as math;
import 'dart:ui';
import '../../lottie_drawable.dart';
import '../../model/content/rounded_corners.dart';
import '../../model/content/shape_data.dart';
import '../../model/cubic_curve_data.dart';
import '../../model/layer/base_layer.dart';
import '../../utils.dart';
import '../keyframe/base_keyframe_animation.dart';
import 'content.dart';
import 'shape_modifier_content.dart';
class RoundedCornersContent implements ShapeModifierContent {
/// Copied from:
/// https://github.com/airbnb/lottie-web/blob/bb71072a26e03f1ca993da60915860f39aae890b/player/js/utils/common.js#L47
static const _roundedCornerMagicNumber = 0.5519;
final LottieDrawable lottieDrawable;
@override
final String name;
final BaseKeyframeAnimation<double, double> roundedCorners;
ShapeData? shapeData;
RoundedCornersContent(
this.lottieDrawable, BaseLayer layer, RoundedCorners roundedCorners)
: name = roundedCorners.name,
roundedCorners = roundedCorners.cornerRadius.createAnimation() {
layer.addAnimation(this.roundedCorners);
this.roundedCorners.addUpdateListener(_onValueChanged);
}
void _onValueChanged() {
lottieDrawable.invalidateSelf();
}
@override
void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
// Do nothing.
}
/// Rounded corner algorithm:
/// Iterate through each vertex.
/// If a vertex is a sharp corner, it rounds it.
/// If a vertex has control points, it is already rounded, so it does nothing.
/// <p>
/// To round a vertex:
/// Split the vertex into two.
/// Move vertex 1 directly towards the previous vertex.
/// Set vertex 1's in control point to itself so it is not rounded on that side.
/// Extend vertex 1's out control point towards the original vertex.
/// <p>
/// Repeat for vertex 2:
/// Move vertex 2 directly towards the next vertex.
/// Set vertex 2's out point to itself so it is not rounded on that side.
/// Extend vertex 2's in control point towards the original vertex.
/// <p>
/// The distance that the vertices and control points are moved are relative to the
/// shape's vertex distances and the roundedness set in the animation.
@override
ShapeData modifyShape(ShapeData startingShapeData) {
var startingCurves = startingShapeData.curves;
if (startingCurves.length <= 2) {
return startingShapeData;
}
var roundedness = roundedCorners.value;
if (roundedness == 0) {
return startingShapeData;
}
var modifiedShapeData = _getShapeData(startingShapeData);
modifiedShapeData.setInitialPoint(
startingShapeData.initialPoint.dx, startingShapeData.initialPoint.dy);
var modifiedCurves = modifiedShapeData.curves;
var modifiedCurvesIndex = 0;
var isClosed = startingShapeData.isClosed;
// i represents which vertex we are currently on. Refer to the docs of CubicCurveData prior to working with
// this code.
// When i == 0
// vertex=ShapeData.initialPoint
// inCp=if closed vertex else curves[size - 1].cp2
// outCp=curves[0].cp1
// When i == 1
// vertex=curves[0].vertex
// inCp=curves[0].cp2
// outCp=curves[1].cp1.
// When i == size - 1
// vertex=curves[size - 1].vertex
// inCp=curves[size - 1].cp2
// outCp=if closed vertex else curves[0].cp1
for (var i = 0; i < startingCurves.length; i++) {
var startingCurve = startingCurves[i];
var previousCurve =
startingCurves[floorMod(i - 1, startingCurves.length)];
var previousPreviousCurve =
startingCurves[floorMod(i - 2, startingCurves.length)];
var vertex = (i == 0 && !isClosed)
? startingShapeData.initialPoint
: previousCurve.vertex;
var inPoint =
(i == 0 && !isClosed) ? vertex : previousCurve.controlPoint2;
var outPoint = startingCurve.controlPoint1;
var previousVertex = previousPreviousCurve.vertex;
var nextVertex = startingCurve.vertex;
// We can't round the corner of the end of a non-closed curve.
var isEndOfCurve = !startingShapeData.isClosed &&
(i == 0 && i == startingCurves.length - 1);
if (inPoint == vertex && outPoint == vertex && !isEndOfCurve) {
// This vertex is a point. Round its corners
var dxToPreviousVertex = vertex.dx - previousVertex.dx;
var dyToPreviousVertex = vertex.dy - previousVertex.dy;
var dxToNextVertex = nextVertex.dx - vertex.dx;
var dyToNextVertex = nextVertex.dy - vertex.dy;
var dToPreviousVertex = hypot(dxToPreviousVertex, dyToPreviousVertex);
var dToNextVertex = hypot(dxToNextVertex, dyToNextVertex);
double previousVertexPercent =
math.min(roundedness / dToPreviousVertex, 0.5);
double nextVertexPercent = math.min(roundedness / dToNextVertex, 0.5);
// Split the vertex into two and move each vertex towards the previous/next vertex.
var newVertex1X =
vertex.dx + (previousVertex.dx - vertex.dx) * previousVertexPercent;
var newVertex1Y =
vertex.dy + (previousVertex.dy - vertex.dy) * previousVertexPercent;
var newVertex2X =
vertex.dx + (nextVertex.dx - vertex.dx) * nextVertexPercent;
var newVertex2Y =
vertex.dy + (nextVertex.dy - vertex.dy) * nextVertexPercent;
// Extend the new vertex control point towards the original vertex.
var newVertex1OutPointX =
newVertex1X - (newVertex1X - vertex.dx) * _roundedCornerMagicNumber;
var newVertex1OutPointY =
newVertex1Y - (newVertex1Y - vertex.dy) * _roundedCornerMagicNumber;
var newVertex2InPointX =
newVertex2X - (newVertex2X - vertex.dx) * _roundedCornerMagicNumber;
var newVertex2InPointY =
newVertex2Y - (newVertex2Y - vertex.dy) * _roundedCornerMagicNumber;
// Remap vertex/in/out point to CubicCurveData.
// Refer to the docs for CubicCurveData for more info on the difference.
var previousCurveData = modifiedCurves[
floorMod(modifiedCurvesIndex - 1, modifiedCurves.length)];
var currentCurveData = modifiedCurves[modifiedCurvesIndex];
previousCurveData.controlPoint2 = Offset(newVertex1X, newVertex1Y);
previousCurveData.vertex = Offset(newVertex1X, newVertex1Y);
if (i == 0) {
modifiedShapeData.setInitialPoint(newVertex1X, newVertex1Y);
}
currentCurveData.controlPoint1 =
Offset(newVertex1OutPointX, newVertex1OutPointY);
modifiedCurvesIndex++;
previousCurveData = currentCurveData;
currentCurveData = modifiedCurves[modifiedCurvesIndex];
previousCurveData.controlPoint2 =
Offset(newVertex2InPointX, newVertex2InPointY);
previousCurveData.vertex = Offset(newVertex2X, newVertex2Y);
currentCurveData.controlPoint1 = Offset(newVertex2X, newVertex2Y);
modifiedCurvesIndex++;
} else {
// This vertex is not a point. Don't modify it. Refer to the documentation above and for CubicCurveData for mapping a vertex
// oriented point to CubicCurveData (path segments).
var previousCurveData = modifiedCurves[
floorMod(modifiedCurvesIndex - 1, modifiedCurves.length)];
var currentCurveData = modifiedCurves[modifiedCurvesIndex];
previousCurveData.controlPoint2 =
Offset(previousCurve.vertex.dx, previousCurve.vertex.dy);
previousCurveData.vertex =
Offset(previousCurve.vertex.dx, previousCurve.vertex.dy);
currentCurveData.controlPoint1 =
Offset(startingCurve.vertex.dx, startingCurve.vertex.dy);
modifiedCurvesIndex++;
}
}
return modifiedShapeData;
}
/// Returns a shape data with the correct number of vertices for the rounded corners shape.
/// This just returns the object. It does not update any values within the shape.
ShapeData _getShapeData(ShapeData startingShapeData) {
var startingCurves = startingShapeData.curves;
var isClosed = startingShapeData.isClosed;
var vertices = 0;
for (var i = startingCurves.length - 1; i >= 0; i--) {
var startingCurve = startingCurves[i];
var previousCurve =
startingCurves[floorMod(i - 1, startingCurves.length)];
var vertex = (i == 0 && !isClosed)
? startingShapeData.initialPoint
: previousCurve.vertex;
var inPoint =
(i == 0 && !isClosed) ? vertex : previousCurve.controlPoint2;
var outPoint = startingCurve.controlPoint1;
var isEndOfCurve = !startingShapeData.isClosed &&
(i == 0 && i == startingCurves.length - 1);
if (inPoint == vertex && outPoint == vertex && !isEndOfCurve) {
vertices += 2;
} else {
vertices += 1;
}
}
var shapeData = this.shapeData;
if (shapeData == null || shapeData.curves.length != vertices) {
var newCurves = <CubicCurveData>[];
for (var i = 0; i < vertices; i++) {
newCurves.add(CubicCurveData());
}
this.shapeData = shapeData =
ShapeData(newCurves, initialPoint: Offset.zero, closed: false);
}
shapeData.setClosed(isClosed);
return shapeData;
}
/// Copied from the API 24+ AOSP source.
static int floorMod(int x, int y) {
return x - floorDiv(x, y) * y;
}
/// Copied from the API 24+ AOSP source.
static int floorDiv(int x, int y) {
var r = x ~/ y;
// if the signs are different and modulo not zero, round down
if ((x ^ y) < 0 && (r * y != x)) {
r--;
}
return r;
}
}

View File

@ -5,10 +5,11 @@ import '../../model/content/shape_trim_path.dart';
import '../../model/layer/base_layer.dart';
import '../../utils.dart';
import '../../utils/path_factory.dart';
import '../keyframe/base_keyframe_animation.dart';
import '../keyframe/shape_keyframe_animation.dart';
import 'compound_trim_path_content.dart';
import 'content.dart';
import 'path_content.dart';
import 'shape_modifier_content.dart';
import 'trim_path_content.dart';
class ShapeContent implements PathContent {
@ -17,7 +18,7 @@ class ShapeContent implements PathContent {
final ShapePath _shape;
final LottieDrawable lottieDrawable;
final BaseKeyframeAnimation<Object, Path> _shapeAnimation;
final ShapeKeyframeAnimation _shapeAnimation;
bool _isPathValid = false;
final _trimPaths = CompoundTrimPathContent();
@ -35,6 +36,7 @@ class ShapeContent implements PathContent {
@override
void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
List<ShapeModifierContent>? shapeModifierContents;
for (var i = 0; i < contentsBefore.length; i++) {
var content = contentsBefore[i];
if (content is TrimPathContent &&
@ -43,8 +45,12 @@ class ShapeContent implements PathContent {
var trimPath = content;
_trimPaths.addTrimPath(trimPath);
trimPath.addListener(_invalidate);
} else if (content is ShapeModifierContent) {
shapeModifierContents ??= [];
shapeModifierContents.add(content);
}
}
_shapeAnimation.setShapeModifiers(shapeModifierContents);
}
@override

View File

@ -0,0 +1,6 @@
import '../../model/content/shape_data.dart';
import 'content.dart';
abstract class ShapeModifierContent extends Content {
ShapeData modifyShape(ShapeData shapeData);
}

View File

@ -10,7 +10,7 @@ import 'base_keyframe_animation.dart';
import 'color_keyframe_animation.dart';
class DropShadowKeyframeAnimation {
static final double _degToRad = math.pi / 180.0;
static const double _degToRad = math.pi / 180.0;
final void Function() listener;
late final ColorKeyframeAnimation _color;

View File

@ -3,11 +3,13 @@ import '../../model/content/shape_data.dart';
import '../../utils/misc.dart';
import '../../utils/path_factory.dart';
import '../../value/keyframe.dart';
import '../content/shape_modifier_content.dart';
import 'base_keyframe_animation.dart';
class ShapeKeyframeAnimation extends BaseKeyframeAnimation<ShapeData, Path> {
final ShapeData _tempShapeData = ShapeData.empty();
final Path _tempPath = PathFactory.create();
List<ShapeModifierContent>? _shapeModifiers;
ShapeKeyframeAnimation(List<Keyframe<ShapeData>> keyframes)
: super(keyframes);
@ -19,7 +21,18 @@ class ShapeKeyframeAnimation extends BaseKeyframeAnimation<ShapeData, Path> {
_tempShapeData.interpolateBetween(
startShapeData, endShapeData, keyframeProgress);
MiscUtils.getPathFromData(_tempShapeData, _tempPath);
var modifiedShapeData = _tempShapeData;
var shapeModifiers = _shapeModifiers;
if (shapeModifiers != null) {
for (var i = shapeModifiers.length - 1; i >= 0; i--) {
modifiedShapeData = shapeModifiers[i].modifyShape(modifiedShapeData);
}
}
MiscUtils.getPathFromData(modifiedShapeData, _tempPath);
return _tempPath;
}
void setShapeModifiers(List<ShapeModifierContent>? shapeModifiers) {
_shapeModifiers = shapeModifiers;
}
}

View File

@ -1,5 +1,7 @@
import '../../model/document_data.dart';
import '../../value/keyframe.dart';
import '../../value/lottie_frame_info.dart';
import '../../value/lottie_value_callback.dart';
import 'keyframe_animation.dart';
class TextKeyframeAnimation extends KeyframeAnimation<DocumentData> {
@ -9,10 +11,58 @@ class TextKeyframeAnimation extends KeyframeAnimation<DocumentData> {
@override
DocumentData getValue(
Keyframe<DocumentData> keyframe, double keyframeProgress) {
if (keyframeProgress != 1.0 || keyframe.endValue == null) {
var valueCallback = this.valueCallback;
if (valueCallback != null) {
return valueCallback.getValueInternal(
keyframe.startFrame,
keyframe.endFrame ?? double.maxFinite,
keyframe.startValue,
keyframe.endValue ?? keyframe.startValue,
keyframeProgress,
getInterpolatedCurrentKeyframeProgress(),
progress)!;
} else if (keyframeProgress != 1.0 || keyframe.endValue == null) {
return keyframe.startValue!;
} else {
return keyframe.endValue!;
}
}
void setStringValueCallback(LottieValueCallback<String> valueCallback) {
super.setValueCallback(_DocumentDataValueCallback(valueCallback));
}
}
class _DocumentDataValueCallback extends LottieValueCallback<DocumentData> {
final LottieValueCallback<String> valueCallback;
_DocumentDataValueCallback(this.valueCallback) : super(null);
@override
DocumentData getValue(LottieFrameInfo<DocumentData> frameInfo) {
var stringFrameInfo = LottieFrameInfo<String>(
frameInfo.startFrame,
frameInfo.endFrame,
frameInfo.startValue!.text,
frameInfo.endValue!.text,
frameInfo.linearKeyframeProgress,
frameInfo.interpolatedKeyframeProgress,
frameInfo.overallProgress);
var text = valueCallback.getValue(stringFrameInfo)!;
var baseDocumentData = frameInfo.interpolatedKeyframeProgress == 1
? frameInfo.endValue!
: frameInfo.startValue!;
return DocumentData(
text: text,
fontName: baseDocumentData.fontName,
size: baseDocumentData.size,
justification: baseDocumentData.justification,
tracking: baseDocumentData.tracking,
lineHeight: baseDocumentData.lineHeight,
baselineShift: baseDocumentData.baselineShift,
color: baseDocumentData.color,
strokeColor: baseDocumentData.strokeColor,
strokeWidth: baseDocumentData.strokeWidth,
strokeOverFill: baseDocumentData.strokeOverFill);
}
}

View File

@ -1,13 +1,9 @@
import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import '../lottie.dart';
import 'composition.dart';
import 'frame_rate.dart';
import 'l.dart';
import 'lottie_builder.dart';
import 'options.dart';
import 'providers/load_image.dart';
/// A widget to display a loaded [LottieComposition].
/// The [controller] property allows to specify a custom AnimationController that
@ -30,6 +26,7 @@ class Lottie extends StatefulWidget {
this.delegates,
this.options,
bool? addRepaintBoundary,
this.filterQuality,
}) : animate = animate ?? true,
reverse = reverse ?? false,
repeat = repeat ?? true,
@ -55,9 +52,10 @@ class Lottie extends StatefulWidget {
double? width,
double? height,
BoxFit? fit,
Alignment? alignment,
AlignmentGeometry? alignment,
String? package,
bool? addRepaintBoundary,
FilterQuality? filterQuality,
WarningCallback? onWarning,
}) =>
LottieBuilder.asset(
@ -81,6 +79,7 @@ class Lottie extends StatefulWidget {
alignment: alignment,
package: package,
addRepaintBoundary: addRepaintBoundary,
filterQuality: filterQuality,
onWarning: onWarning,
);
@ -102,8 +101,9 @@ class Lottie extends StatefulWidget {
double? width,
double? height,
BoxFit? fit,
Alignment? alignment,
AlignmentGeometry? alignment,
bool? addRepaintBoundary,
FilterQuality? filterQuality,
WarningCallback? onWarning,
}) =>
LottieBuilder.file(
@ -125,6 +125,7 @@ class Lottie extends StatefulWidget {
fit: fit,
alignment: alignment,
addRepaintBoundary: addRepaintBoundary,
filterQuality: filterQuality,
onWarning: onWarning,
);
@ -146,8 +147,9 @@ class Lottie extends StatefulWidget {
double? width,
double? height,
BoxFit? fit,
Alignment? alignment,
AlignmentGeometry? alignment,
bool? addRepaintBoundary,
FilterQuality? filterQuality,
WarningCallback? onWarning,
}) =>
LottieBuilder.memory(
@ -169,6 +171,7 @@ class Lottie extends StatefulWidget {
fit: fit,
alignment: alignment,
addRepaintBoundary: addRepaintBoundary,
filterQuality: filterQuality,
onWarning: onWarning,
);
@ -190,8 +193,9 @@ class Lottie extends StatefulWidget {
double? width,
double? height,
BoxFit? fit,
Alignment? alignment,
AlignmentGeometry? alignment,
bool? addRepaintBoundary,
FilterQuality? filterQuality,
WarningCallback? onWarning,
}) =>
LottieBuilder.network(
@ -213,6 +217,7 @@ class Lottie extends StatefulWidget {
fit: fit,
alignment: alignment,
addRepaintBoundary: addRepaintBoundary,
filterQuality: filterQuality,
onWarning: onWarning,
);
@ -303,13 +308,19 @@ class Lottie extends StatefulWidget {
/// This property is `true` by default.
final bool addRepaintBoundary;
/// The quality of the image layer. See [FilterQuality]
/// [FilterQuality.high] is highest quality but slowest.
///
/// Defaults to [FilterQuality.low]
final FilterQuality? filterQuality;
static bool get traceEnabled => L.traceEnabled;
static set traceEnabled(bool enabled) {
L.traceEnabled = enabled;
}
@override
_LottieState createState() => _LottieState();
State<Lottie> createState() => _LottieState();
}
class _LottieState extends State<Lottie> with TickerProviderStateMixin {
@ -370,6 +381,7 @@ class _LottieState extends State<Lottie> with TickerProviderStateMixin {
height: widget.height,
fit: widget.fit,
alignment: widget.alignment,
filterQuality: widget.filterQuality,
);
},
);

View File

@ -1,12 +1,11 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import '../lottie.dart';
import 'composition.dart';
import 'frame_rate.dart';
import 'lottie.dart';
import 'lottie_delegates.dart';
import 'options.dart';
import 'providers/asset_provider.dart';
import 'providers/file_provider.dart';
import 'providers/load_image.dart';
@ -33,12 +32,12 @@ typedef LottieErrorWidgetBuilder = Widget Function(
/// Several constructors are provided for the various ways that a Lottie file
/// can be provided:
///
/// * [new Lottie], for obtaining a composition from a [LottieProvider].
/// * [new Lottie.asset], for obtaining a Lottie file from an [AssetBundle]
/// * [Lottie], for obtaining a composition from a [LottieProvider].
/// * [Lottie.asset], for obtaining a Lottie file from an [AssetBundle]
/// using a key.
/// * [new Lottie.network], for obtaining a lottie file from a URL.
/// * [new Lottie.file], for obtaining a lottie file from a [File].
/// * [new Lottie.memory], for obtaining a lottie file from a [Uint8List].
/// * [Lottie.network], for obtaining a lottie file from a URL.
/// * [Lottie.file], for obtaining a lottie file from a [File].
/// * [Lottie.memory], for obtaining a lottie file from a [Uint8List].
///
class LottieBuilder extends StatefulWidget {
const LottieBuilder({
@ -59,6 +58,7 @@ class LottieBuilder extends StatefulWidget {
this.fit,
this.alignment,
this.addRepaintBoundary,
this.filterQuality,
this.onWarning,
}) : super(key: key);
@ -83,6 +83,7 @@ class LottieBuilder extends StatefulWidget {
this.fit,
this.alignment,
this.addRepaintBoundary,
this.filterQuality,
this.onWarning,
}) : lottie = NetworkLottie(src,
headers: headers, imageProviderFactory: imageProviderFactory),
@ -117,6 +118,7 @@ class LottieBuilder extends StatefulWidget {
this.fit,
this.alignment,
this.addRepaintBoundary,
this.filterQuality,
this.onWarning,
}) : lottie = FileLottie(file, imageProviderFactory: imageProviderFactory),
super(key: key);
@ -143,6 +145,7 @@ class LottieBuilder extends StatefulWidget {
this.alignment,
String? package,
this.addRepaintBoundary,
this.filterQuality,
this.onWarning,
}) : lottie = AssetLottie(name,
bundle: bundle,
@ -170,6 +173,7 @@ class LottieBuilder extends StatefulWidget {
this.fit,
this.alignment,
this.addRepaintBoundary,
this.filterQuality,
this.onWarning,
}) : lottie =
MemoryLottie(bytes, imageProviderFactory: imageProviderFactory),
@ -361,6 +365,12 @@ class LottieBuilder extends StatefulWidget {
/// This property is `true` by default.
final bool? addRepaintBoundary;
/// The quality of the image layer. See [FilterQuality]
/// [FilterQuality.high] is highest quality but slowest.
///
/// Defaults to [FilterQuality.low]
final FilterQuality? filterQuality;
/// A callback called when there is a warning during the loading or painting
/// of the animation.
final WarningCallback? onWarning;
@ -398,7 +408,7 @@ class LottieBuilder extends StatefulWidget {
final ImageErrorWidgetBuilder? errorBuilder;
@override
_LottieBuilderState createState() => _LottieBuilderState();
State<LottieBuilder> createState() => _LottieBuilderState();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
@ -482,6 +492,7 @@ class _LottieBuilderState extends State<LottieBuilder> {
fit: widget.fit,
alignment: widget.alignment,
addRepaintBoundary: widget.addRepaintBoundary,
filterQuality: widget.filterQuality,
);
if (widget.frameBuilder != null) {

View File

@ -1,7 +1,8 @@
import 'dart:ui' as ui;
import 'package:flutter/widgets.dart';
import '../lottie.dart';
import 'composition.dart';
import 'lottie_drawable.dart';
import 'lottie_image_asset.dart';
import 'value_delegate.dart';
// TODO(xha): recognize style Bold, Medium, Regular, SemiBold, etc...
@ -81,5 +82,5 @@ class LottieDelegates {
values == other.values;
@override
int get hashCode => hashValues(text, textStyle, values);
int get hashCode => Object.hash(text, textStyle, values);
}

View File

@ -1,6 +1,5 @@
import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
import 'package:vector_math/vector_math_64.dart';
import 'composition.dart';
import 'frame_rate.dart';
import 'lottie_delegates.dart';
@ -17,16 +16,15 @@ class LottieDrawable {
final Size size;
LottieDelegates? _delegates;
bool _isDirty = true;
final bool enableMergePaths;
bool enableMergePaths = false;
FilterQuality? filterQuality;
/// Gives a suggestion whether to paint with anti-aliasing, or not. Default is true.
bool antiAliasingSuggested = true;
LottieDrawable(this.composition,
{LottieDelegates? delegates, bool? enableMergePaths})
LottieDrawable(this.composition, {LottieDelegates? delegates})
: size = Size(composition.bounds.width.toDouble(),
composition.bounds.height.toDouble()),
enableMergePaths = enableMergePaths ?? false {
composition.bounds.height.toDouble()) {
this.delegates = delegates;
_compositionLayer = CompositionLayer(
this, LayerParser.parse(composition), composition.layers, composition);

View File

@ -129,8 +129,12 @@ abstract class LottieProperty {
static const dropShadow = DropShadow(
color: Color(0x00000000), direction: 0, distance: 0, radius: 0);
/// Set the color filter for an entire drawable content. Can be applied to fills, strokes, images, and solids.
static const ColorFilter colorFilter =
ColorFilter.mode(Color(0xFF000000), BlendMode.dst);
static final List<Color> gradientColor = [];
/// Replace the text for a text layer.
static const text = 'dynamic_text';
}

View File

@ -1,5 +1,4 @@
import 'dart:ui';
import '../../animation/keyframe/base_keyframe_animation.dart';
import '../../animation/keyframe/shape_keyframe_animation.dart';
import '../../value/keyframe.dart';
import '../content/shape_data.dart';
@ -10,7 +9,7 @@ class AnimatableShapeValue extends BaseAnimatableValue<ShapeData, Path> {
: super.fromKeyframes(keyframes);
@override
BaseKeyframeAnimation<ShapeData, Path> createAnimation() {
ShapeKeyframeAnimation createAnimation() {
return ShapeKeyframeAnimation(keyframes);
}
}

View File

@ -37,6 +37,7 @@ class PolystarShape implements ContentModel {
final AnimatableDoubleValue? innerRoundedness;
final AnimatableDoubleValue outerRoundedness;
final bool hidden;
final bool isReversed;
PolystarShape({
this.name,
@ -49,6 +50,7 @@ class PolystarShape implements ContentModel {
this.innerRoundedness,
required this.outerRoundedness,
required this.hidden,
required this.isReversed,
});
@override

View File

@ -0,0 +1,18 @@
import '../../animation/content/content.dart';
import '../../animation/content/rounded_corners_content.dart';
import '../../lottie_drawable.dart';
import '../animatable/animatable_value.dart';
import '../layer/base_layer.dart';
import 'content_model.dart';
class RoundedCorners implements ContentModel {
final String name;
final AnimatableValue<double, double> cornerRadius;
RoundedCorners(this.name, this.cornerRadius);
@override
Content toContent(LottieDrawable drawable, BaseLayer layer) {
return RoundedCornersContent(drawable, layer, this);
}
}

View File

@ -22,6 +22,10 @@ class ShapeData {
return _initialPoint;
}
void setClosed(bool closed) {
_closed = closed;
}
bool get isClosed {
return _closed;
}

View File

@ -1,7 +1,44 @@
import 'dart:ui';
/// One cubic path operation. CubicCurveData is structured such that it is easy to iterate through
/// it and build a path. However, it is modeled differently than most path operations.
///
/// CubicCurveData
/// | - vertex
/// | /
/// | cp1 cp2
/// | /
/// | |
/// | /
/// --------------------------
///
/// When incrementally building a path, it will already have a "current point" so that is
/// not captured in this data structure.
/// The control points here represent {@link android.graphics.Path#cubicTo(float, float, float, float, float, float)}.
///
/// Most path operations are centered around a vertex and its in control point and out control point like this:
/// | outCp
/// | /
/// | |
/// | v
/// | /
/// | inCp
/// --------------------------
class CubicCurveData {
Offset controlPoint1 = Offset.zero;
Offset controlPoint2 = Offset.zero;
Offset vertex = Offset.zero;
void setFrom(CubicCurveData curveData) {
vertex = Offset(curveData.vertex.dx, curveData.vertex.dy);
controlPoint1 =
Offset(curveData.controlPoint1.dx, curveData.controlPoint1.dy);
controlPoint2 =
Offset(curveData.controlPoint2.dx, curveData.controlPoint2.dy);
}
@override
String toString() {
return 'v=$vertex cp1$controlPoint1 cp2=$controlPoint2';
}
}

View File

@ -31,7 +31,7 @@ class DocumentData {
@override
int get hashCode {
return hashValues(
return Object.hash(
text,
fontName,
size,

View File

@ -2,7 +2,6 @@ import 'dart:math';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart' hide Layer;
import 'package:vector_math/vector_math_64.dart';
import '../../animation/content/content.dart';
import '../../animation/content/drawing_content.dart';
import '../../animation/keyframe/base_keyframe_animation.dart';

View File

@ -25,6 +25,7 @@ class ImageLayer extends BaseLayer {
}
var density = window.devicePixelRatio;
paint.filterQuality = lottieDrawable.filterQuality ?? FilterQuality.low;
paint.setAlpha(parentAlpha);
if (_colorFilterAnimation != null) {
paint.colorFilter = _colorFilterAnimation!.value;

View File

@ -1,7 +1,5 @@
import 'dart:ui';
import 'package:characters/characters.dart';
import 'package:flutter/widgets.dart';
import 'package:vector_math/vector_math_64.dart';
import '../../animation/content/content_group.dart';
import '../../animation/keyframe/base_keyframe_animation.dart';
import '../../animation/keyframe/text_keyframe_animation.dart';
@ -486,6 +484,11 @@ class TextLayer extends BaseLayer {
..addUpdateListener(invalidateSelf);
addAnimation(_textSizeCallbackAnimation);
}
} else if (property == LottieProperty.text) {
if (callback != null) {
_textAnimation
.setStringValueCallback(callback as LottieValueCallback<String>);
}
}
}
}

View File

@ -9,6 +9,7 @@ import 'moshi/json_reader.dart';
import 'polysar_shape_parser.dart';
import 'rectangle_shape_parser.dart';
import 'repeat_parser.dart';
import 'rounded_corners_parser.dart';
import 'shape_fill_parser.dart';
import 'shape_group_parser.dart';
import 'shape_path_parser.dart';
@ -80,7 +81,7 @@ class ContentModelParser {
model = ShapeTrimPathParser.parse(reader, composition);
break;
case 'sr':
model = PolystarShapeParser.parse(reader, composition);
model = PolystarShapeParser.parse(reader, composition, d: d);
break;
case 'mm':
model = MergePathsParser.parse(reader);
@ -88,6 +89,9 @@ class ContentModelParser {
case 'rp':
model = RepeaterParser.parse(reader, composition);
break;
case 'rd':
model = RoundedCornersParser.parse(reader, composition);
break;
default:
composition.addWarning('Unknown shape type $type');
}

View File

@ -3,8 +3,19 @@ import '../model/document_data.dart';
import 'json_utils.dart';
import 'moshi/json_reader.dart';
final JsonReaderOptions _names = JsonReaderOptions.of(
['t', 'f', 's', 'j', 'tr', 'lh', 'ls', 'fc', 'sc', 'sw', 'of']);
final JsonReaderOptions _names = JsonReaderOptions.of([
't', // 0
'f', // 1
's', // 2
'j', // 3
'tr', // 4
'lh', // 5
'ls', // 6
'fc', // 7
'sc', // 8
'sw', // 9
'of', // 10
]);
DocumentData documentDataParser(JsonReader reader, {required double scale}) {
String? text;

View File

@ -37,6 +37,17 @@ class GradientColorParser {
while (reader.hasNext()) {
array.add(reader.nextDouble());
}
if (array.length == 4 && array[0] == 1) {
// If a gradient color only contains one color at position 1, add a second stop with the same
// color at position 0. Android's LinearGradient shader requires at least two colors.
// https://github.com/airbnb/lottie-android/issues/1967
array[0] = 0;
array.add(1);
array.add(array[1]);
array.add(array[2]);
array.add(array[3]);
_colorPoints = 2;
}
if (isArray) {
reader.endArray();
}

View File

@ -13,7 +13,7 @@ class KeyframeParser {
/// PathInterpolator fails to create the interpolator in those cases and hangs.
/// Clamping the cp helps prevent that.
static const _maxCpValue = 100.0;
static final _linearInterpolator = Curves.linear;
static const _linearInterpolator = Curves.linear;
static final _pathInterpolatorCache = <int, Curve>{};
static final JsonReaderOptions _names =

View File

@ -9,11 +9,12 @@ import 'moshi/json_reader.dart';
class PolystarShapeParser {
static final JsonReaderOptions _names = JsonReaderOptions.of(
['nm', 'sy', 'pt', 'p', 'r', 'or', 'os', 'ir', 'is', 'hd']);
['nm', 'sy', 'pt', 'p', 'r', 'or', 'os', 'ir', 'is', 'hd', 'd']);
PolystarShapeParser._();
static PolystarShape parse(JsonReader reader, LottieComposition composition) {
static PolystarShape parse(JsonReader reader, LottieComposition composition,
{required int d}) {
String? name;
PolystarShapeType? type;
late AnimatableDoubleValue points;
@ -24,6 +25,7 @@ class PolystarShapeParser {
AnimatableDoubleValue? innerRadius;
AnimatableDoubleValue? innerRoundedness;
var hidden = false;
var reversed = d == 3;
while (reader.hasNext()) {
switch (reader.selectName(_names)) {
@ -64,6 +66,10 @@ class PolystarShapeParser {
case 9:
hidden = reader.nextBoolean();
break;
case 10:
// "d" is 2 for normal and 3 for reversed.
reversed = reader.nextInt() == 3;
break;
default:
reader.skipName();
reader.skipValue();
@ -81,6 +87,7 @@ class PolystarShapeParser {
innerRoundedness: innerRoundedness,
outerRoundedness: outerRoundedness,
hidden: hidden,
isReversed: reversed,
);
}
}

View File

@ -0,0 +1,39 @@
import '../composition.dart';
import '../model/animatable/animatable_value.dart';
import '../model/content/rounded_corners.dart';
import 'animatable_value_parser.dart';
import 'moshi/json_reader.dart';
class RoundedCornersParser {
static final _names = JsonReaderOptions.of([
'nm', // 0
'r', // 1
'hd' // 1
]);
static RoundedCorners? parse(
JsonReader reader, LottieComposition composition) {
String? name;
AnimatableValue<double, double>? cornerRadius;
var hidden = false;
while (reader.hasNext()) {
switch (reader.selectName(_names)) {
case 0: //nm
name = reader.nextString();
break;
case 1: // r
cornerRadius =
AnimatableValueParser.parseFloat(reader, composition, isDp: true);
break;
case 2: // hd
hidden = reader.nextBoolean();
break;
default:
reader.skipValue();
}
}
return hidden ? null : RoundedCorners(name!, cornerRadius!);
}
}

View File

@ -67,7 +67,7 @@ class AssetLottie extends LottieProvider {
}
@override
int get hashCode => hashValues(keyName, bundle);
int get hashCode => Object.hash(keyName, bundle);
@override
String toString() => '$runtimeType(bundle: $bundle, name: "$keyName")';

View File

@ -1,5 +1,4 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:path/path.dart' as p;

View File

@ -1,8 +1,10 @@
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import '../lottie.dart';
import 'composition.dart';
import 'frame_rate.dart';
import 'lottie_delegates.dart';
import 'lottie_drawable.dart';
import 'options.dart';
import 'render_lottie.dart';
/// A widget that displays a [LottieDrawable] directly.
@ -21,6 +23,7 @@ class RawLottie extends LeafRenderObjectWidget {
this.height,
this.fit,
AlignmentGeometry? alignment,
this.filterQuality,
}) : progress = progress ?? 0.0,
alignment = alignment ?? Alignment.center,
super(key: key);
@ -76,6 +79,8 @@ class RawLottie extends LeafRenderObjectWidget {
/// relative to text direction.
final AlignmentGeometry alignment;
final FilterQuality? filterQuality;
@override
RenderLottie createRenderObject(BuildContext context) {
return RenderLottie(
@ -88,17 +93,21 @@ class RawLottie extends LeafRenderObjectWidget {
height: height,
fit: fit,
alignment: alignment,
filterQuality: filterQuality,
);
}
@override
void updateRenderObject(BuildContext context, RenderLottie renderObject) {
renderObject
..setComposition(composition,
..setComposition(
composition,
progress: progress,
frameRate: frameRate,
delegates: delegates,
enableMergePaths: options?.enableMergePaths)
enableMergePaths: options?.enableMergePaths,
filterQuality: filterQuality,
)
..width = width
..height = height
..alignment = alignment

View File

@ -1,6 +1,7 @@
import 'package:flutter/rendering.dart';
import '../lottie.dart';
import 'composition.dart';
import 'frame_rate.dart';
import 'lottie_delegates.dart';
import 'lottie_drawable.dart';
/// A Lottie animation in the render tree.
@ -18,11 +19,14 @@ class RenderLottie extends RenderBox {
double? height,
BoxFit? fit,
AlignmentGeometry alignment = Alignment.center,
FilterQuality? filterQuality,
}) : assert(progress >= 0.0 && progress <= 1.0),
_drawable = composition != null
? (LottieDrawable(composition, enableMergePaths: enableMergePaths)
? (LottieDrawable(composition)
..setProgress(progress, frameRate: frameRate)
..delegates = delegates)
..delegates = delegates
..enableMergePaths = enableMergePaths ?? false
..filterQuality = filterQuality)
: null,
_width = width,
_height = height,
@ -32,11 +36,13 @@ class RenderLottie extends RenderBox {
/// The lottie composition to display.
LottieComposition? get composition => _drawable?.composition;
LottieDrawable? _drawable;
void setComposition(LottieComposition? composition,
{required double progress,
required FrameRate? frameRate,
required LottieDelegates? delegates,
bool? enableMergePaths}) {
bool? enableMergePaths,
FilterQuality? filterQuality}) {
var drawable = _drawable;
enableMergePaths ??= false;
@ -49,11 +55,8 @@ class RenderLottie extends RenderBox {
needsLayout = true;
}
} else {
if (drawable == null ||
drawable.composition != composition ||
drawable.enableMergePaths != enableMergePaths) {
drawable = _drawable =
LottieDrawable(composition, enableMergePaths: enableMergePaths);
if (drawable == null || drawable.composition != composition) {
drawable = _drawable = LottieDrawable(composition);
needsLayout = true;
needsPaint = true;
}
@ -64,6 +67,14 @@ class RenderLottie extends RenderBox {
drawable.delegates = delegates;
needsPaint = true;
}
if (enableMergePaths != drawable.enableMergePaths) {
drawable.enableMergePaths = enableMergePaths;
needsPaint = true;
}
if (filterQuality != drawable.filterQuality) {
drawable.filterQuality = filterQuality;
needsPaint = true;
}
}
if (needsPaint) {

View File

@ -1,5 +1,4 @@
import 'dart:math';
import 'dart:ui';
import 'package:flutter/rendering.dart';
import 'package:vector_math/vector_math_64.dart';

View File

@ -1,4 +1,3 @@
import 'dart:typed_data';
import 'dart:ui';
import 'package:flutter/foundation.dart';

View File

@ -33,7 +33,7 @@ class DropShadow {
}
@override
int get hashCode => hashValues(color, direction, distance, radius);
int get hashCode => Object.hash(color, direction, distance, radius);
@override
String toString() => 'DropShadow(color: $color, direction: $direction, '

View File

@ -1,4 +1,3 @@
import 'dart:ui';
import 'package:flutter/animation.dart';
import '../composition.dart';

View File

@ -254,6 +254,11 @@ class ValueDelegate<T> {
double? relative}) =>
_double(keyPath, LottieProperty.textSize, value, callback, relative);
static ValueDelegate<String> text(List<String> keyPath,
{String? value,
String Function(LottieFrameInfo<String>)? callback}) =>
ValueDelegate._(keyPath, LottieProperty.text, value, callback);
static ValueDelegate<ColorFilter> colorFilter(List<String> keyPath,
{ColorFilter? value,
ColorFilter Function(LottieFrameInfo<ColorFilter>)? callback}) =>

View File

@ -7,35 +7,35 @@ packages:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "26.0.0"
version: "48.0.0"
analyzer:
dependency: "direct dev"
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
version: "5.0.0"
archive:
dependency: "direct main"
description:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.2"
version: "3.3.1"
args:
dependency: transitive
description:
name: args
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
version: "2.3.1"
async:
dependency: transitive
description:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.1"
version: "2.9.0"
boolean_selector:
dependency: transitive
description:
@ -49,42 +49,42 @@ packages:
name: build
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "2.3.1"
build_config:
dependency: transitive
description:
name: build_config
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "1.1.0"
build_daemon:
dependency: transitive
description:
name: build_daemon
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
version: "3.1.0"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.4"
version: "2.0.10"
build_runner:
dependency: "direct dev"
description:
name: build_runner
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
version: "2.2.1"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
url: "https://pub.dartlang.org"
source: hosted
version: "7.1.0"
version: "7.2.4"
built_collection:
dependency: transitive
description:
@ -98,21 +98,14 @@ packages:
name: built_value
url: "https://pub.dartlang.org"
source: hosted
version: "8.1.2"
version: "8.4.1"
characters:
dependency: "direct main"
dependency: transitive
description:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.1"
version: "1.2.1"
checked_yaml:
dependency: transitive
description:
@ -120,76 +113,69 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
cli_util:
dependency: transitive
description:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.3"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.1.1"
code_builder:
dependency: transitive
description:
name: code_builder
url: "https://pub.dartlang.org"
source: hosted
version: "4.1.0"
version: "4.2.0"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.15.0"
version: "1.16.0"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
version: "3.0.2"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
version: "3.0.2"
dart_style:
dependency: "direct dev"
description:
name: dart_style
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "2.2.4"
fake_async:
dependency: transitive
description:
name: fake_async
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.1"
file:
dependency: transitive
description:
name: file
url: "https://pub.dartlang.org"
source: hosted
version: "6.1.2"
version: "6.1.4"
fixnum:
dependency: transitive
description:
name: fixnum
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "1.0.1"
flutter:
dependency: "direct main"
description: flutter
@ -201,7 +187,7 @@ packages:
name: flutter_lints
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.4"
version: "2.0.1"
flutter_test:
dependency: "direct dev"
description: flutter
@ -213,14 +199,14 @@ packages:
name: frontend_server_client
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.2"
version: "2.1.3"
glob:
dependency: transitive
description:
name: glob
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
version: "2.1.0"
graphs:
dependency: transitive
description:
@ -234,14 +220,14 @@ packages:
name: http_multi_server
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
version: "3.2.1"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.0"
version: "4.0.1"
io:
dependency: transitive
description:
@ -255,21 +241,21 @@ packages:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.3"
version: "0.6.4"
json_annotation:
dependency: transitive
description:
name: json_annotation
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.1"
version: "4.6.0"
lints:
dependency: transitive
description:
name: lints
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
version: "2.0.0"
logging:
dependency: transitive
description:
@ -283,77 +269,77 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.10"
version: "0.12.12"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.5"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.7.0"
version: "1.8.0"
mime:
dependency: transitive
description:
name: mime
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "1.0.2"
package_config:
dependency: transitive
description:
name: package_config
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.2"
version: "2.1.0"
path:
dependency: "direct main"
description:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.0"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.1"
version: "1.8.2"
pool:
dependency: transitive
description:
name: pool
url: "https://pub.dartlang.org"
source: hosted
version: "1.5.0"
version: "1.5.1"
pub_semver:
dependency: "direct dev"
description:
name: pub_semver
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "2.1.1"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "1.2.1"
shelf:
dependency: transitive
description:
name: shelf
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.3.2"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.1"
version: "1.0.2"
sky_engine:
dependency: transitive
description: flutter
@ -365,7 +351,7 @@ packages:
name: source_span
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
version: "1.9.0"
stack_trace:
dependency: transitive
description:
@ -393,21 +379,21 @@ packages:
name: string_scanner
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
version: "1.1.1"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.dartlang.org"
source: hosted
version: "1.2.0"
version: "1.2.1"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.2"
version: "0.4.12"
timing:
dependency: transitive
description:
@ -421,34 +407,35 @@ packages:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.3.0"
version: "1.3.1"
vector_math:
dependency: "direct main"
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "2.1.2"
watcher:
dependency: transitive
description:
name: watcher
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "1.0.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.0"
version: "2.2.0"
yaml:
dependency: "direct dev"
description:
name: yaml
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
version: "3.1.1"
sdks:
dart: ">=2.12.0 <3.0.0"
dart: ">=2.18.0 <3.0.0"
flutter: ">=3.3.0"

View File

@ -1,14 +1,14 @@
name: lottie
description: Render After Effects animations natively on Flutter. This package is a pure Dart implementation of a Lottie player.
version: 1.2.1
version: 1.4.3
homepage: https://github.com/xvrh/lottie-flutter
environment:
sdk: '>=2.12.0 <3.0.0'
sdk: '>=2.18.0 <3.0.0'
flutter: '>=3.3.0'
dependencies:
archive: ^3.0.0
characters: ^1.1.0
flutter:
sdk: flutter
path: ^1.8.0

View File

@ -1,7 +1,6 @@
import 'dart:io';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:lottie/lottie.dart';
import 'utils.dart';

View File

@ -2,27 +2,83 @@ import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:lottie/lottie.dart';
import 'utils.dart';
void main() {
testWidgets('Dynamic test', (tester) async {
var composition = await LottieComposition.fromBytes(
File('example/assets/Tests/DynamicText.json').readAsBytesSync());
late LottieComposition composition;
setUpAll(() async {
composition = await LottieComposition.fromBytes(
File('example/assets/Tests/DynamicText.json').readAsBytesSync());
});
void testGolden(String description, LottieDelegates delegates) async {
var screenshotName = description
.toLowerCase()
.replaceAll(RegExp('[^a-z0-9 ]'), '')
.replaceAll(' ', '_');
var size = const Size(500, 400);
testWidgets(description, (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Lottie(
composition: composition,
animate: false,
delegates: LottieDelegates(
text: (input) => '🔥c🔥👮🏿🔥',
textStyle: (font) => const TextStyle(
fontFamily: 'Roboto', fontFamilyFallback: ['Noto Emoji']),
values: const []),
home: FilmStrip(
composition,
delegates: delegates,
size: size,
),
),
);
await expectLater(
find.byType(Lottie), matchesGoldenFile('goldens/dynamic_text.png'));
await tester.pump();
await expectLater(find.byType(FilmStrip),
matchesGoldenFile('goldens/dynamic_text/$screenshotName.png'));
});
}
testGolden(
'Dynamic text delegate',
LottieDelegates(
text: (input) => '🔥c🔥👮🏿🔥',
textStyle: (font) => const TextStyle(
fontFamily: 'Roboto', fontFamilyFallback: ['Noto Emoji']),
values: const []));
testGolden(
'Dynamic Text ValueDelegate',
LottieDelegates(values: [
ValueDelegate.text(['NAME'], value: 'Text with ValueDelegate')
]),
);
testGolden(
'Dynamic Text ValueDelegate overallProgress',
LottieDelegates(values: [
ValueDelegate.text(['NAME'],
callback: (frame) => '${frame.overallProgress}')
]),
);
testGolden(
'Dynamic Text ValueDelegate startValue',
LottieDelegates(values: [
ValueDelegate.text(['NAME'], callback: (frame) => '${frame.startValue}!!')
]),
);
testGolden(
'Dynamic Text ValueDelegate endValue',
LottieDelegates(values: [
ValueDelegate.text(['NAME'], callback: (frame) => '${frame.endValue}!!')
]),
);
testGolden(
'Dynamic Text Emoji',
LottieDelegates(
textStyle: (font) => const TextStyle(
fontFamily: 'Roboto', fontFamilyFallback: ['Noto Emoji']),
values: [
ValueDelegate.text(['NAME'], value: '🔥💪💯'),
]),
);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 102 KiB

After

Width:  |  Height:  |  Size: 101 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Some files were not shown because too many files have changed in this diff Show More