Compare commits

...

5 Commits

Author SHA1 Message Date
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
92 changed files with 9822 additions and 182 deletions

View File

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

View File

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

View File

@ -1,3 +1,34 @@
## [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] ## [1.2.2]
- Internal maintenance: fix lints for Flutter 2.10 - Internal maintenance: fix lints for Flutter 2.10

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,7 +10,7 @@ class App extends StatefulWidget {
const App({Key? key}) : super(key: key); const App({Key? key}) : super(key: key);
@override @override
_AppState createState() => _AppState(); State<App> createState() => _AppState();
} }
class _AppState extends State<App> with TickerProviderStateMixin { class _AppState extends State<App> with TickerProviderStateMixin {
@ -71,7 +71,7 @@ class _AppState extends State<App> with TickerProviderStateMixin {
_color = newColor; _color = newColor;
}); });
}, },
showLabel: false, labelTypes: const [],
enableAlpha: false, enableAlpha: false,
pickerAreaHeightPercent: 0.8, pickerAreaHeightPercent: 0.8,
), ),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -92,7 +92,7 @@ class Detail extends StatefulWidget {
const Detail(this.assetName, {Key? key}) : super(key: key); const Detail(this.assetName, {Key? key}) : super(key: key);
@override @override
_DetailState createState() => _DetailState(); State<Detail> createState() => _DetailState();
} }
class _DetailState extends State<Detail> with TickerProviderStateMixin { class _DetailState extends State<Detail> with TickerProviderStateMixin {

View File

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

View File

@ -7,7 +7,7 @@ packages:
name: archive name: archive
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.2" version: "3.3.1"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -49,28 +49,28 @@ packages:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.15.0" version: "1.16.0"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
name: crypto name: crypto
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.3.0"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.2" version: "2.0.1"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -89,14 +89,14 @@ packages:
name: flutter_colorpicker name: flutter_colorpicker
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.5.0" version: "1.0.3"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: flutter_lints name: flutter_lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.3" version: "2.0.1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -108,42 +108,42 @@ packages:
name: golden_toolkit name: golden_toolkit
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.9.0" version: "0.13.0"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.13.3" version: "0.13.4"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
name: http_parser name: http_parser
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.0" version: "4.0.1"
lints: lints:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "2.0.0"
logging: logging:
dependency: "direct main" dependency: "direct main"
description: description:
name: logging name: logging
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "1.0.2"
lottie: lottie:
dependency: "direct main" dependency: "direct main"
description: description:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "1.2.2" version: "1.4.2"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -157,7 +157,7 @@ packages:
name: material_color_utilities name: material_color_utilities
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.3" version: "0.1.4"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -166,75 +166,82 @@ packages:
source: hosted source: hosted
version: "1.7.0" version: "1.7.0"
path: path:
dependency: transitive dependency: "direct main"
description: description:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0" version: "1.8.1"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted 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.16"
path_provider_ios:
dependency: transitive
description:
name: path_provider_ios
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.10"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
name: path_provider_linux name: path_provider_linux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.0" version: "2.1.7"
path_provider_macos: path_provider_macos:
dependency: transitive dependency: transitive
description: description:
name: path_provider_macos name: path_provider_macos
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.0" version: "2.0.6"
path_provider_platform_interface: path_provider_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: path_provider_platform_interface name: path_provider_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.4"
path_provider_windows: path_provider_windows:
dependency: transitive dependency: transitive
description: description:
name: path_provider_windows name: path_provider_windows
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.1.0"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.1"
platform: platform:
dependency: transitive dependency: transitive
description: description:
name: platform name: platform
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.0" version: "3.1.0"
plugin_platform_interface: plugin_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: plugin_platform_interface name: plugin_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.0" version: "2.1.2"
process: process:
dependency: transitive dependency: transitive
description: description:
name: process name: process
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.2.1" version: "4.2.4"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -246,7 +253,7 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.1" version: "1.8.2"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -281,35 +288,35 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.8" version: "0.4.9"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.3.1"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.5" version: "2.7.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
name: xdg_directories name: xdg_directories
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.0" version: "0.2.0+1"
sdks: sdks:
dart: ">=2.14.0 <3.0.0" dart: ">=2.17.0 <3.0.0"
flutter: ">=1.20.0" flutter: ">=3.0.0"

View File

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

View File

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

View File

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

View File

@ -15,6 +15,7 @@ import 'compound_trim_path_content.dart';
import 'content.dart'; import 'content.dart';
import 'key_path_element_content.dart'; import 'key_path_element_content.dart';
import 'path_content.dart'; import 'path_content.dart';
import 'rounded_corners_content.dart';
import 'trim_path_content.dart'; import 'trim_path_content.dart';
class RectangleContent implements KeyPathElementContent, PathContent { class RectangleContent implements KeyPathElementContent, PathContent {
@ -29,6 +30,9 @@ class RectangleContent implements KeyPathElementContent, PathContent {
final BaseKeyframeAnimation<Object, double> _cornerRadiusAnimation; final BaseKeyframeAnimation<Object, double> _cornerRadiusAnimation;
final CompoundTrimPathContent _trimPaths = CompoundTrimPathContent(); 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; bool _isPathValid = false;
RectangleContent( RectangleContent(
@ -61,6 +65,8 @@ class RectangleContent implements KeyPathElementContent, PathContent {
var trimPath = content; var trimPath = content;
_trimPaths.addTrimPath(trimPath); _trimPaths.addTrimPath(trimPath);
trimPath.addListener(invalidate); 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 halfWidth = size.dx / 2.0;
var halfHeight = size.dy / 2.0; var halfHeight = size.dy / 2.0;
var radius = _cornerRadiusAnimation.value; var radius = _cornerRadiusAnimation.value;
var roundedCornersAnimation = _roundedCornersAnimation;
if (radius == 0 && roundedCornersAnimation != null) {
radius = min(roundedCornersAnimation.value, min(halfWidth, halfHeight));
}
var maxRadius = min(halfWidth, halfHeight); var maxRadius = min(halfWidth, halfHeight);
if (radius > maxRadius) { if (radius > maxRadius) {
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 '../../model/layer/base_layer.dart';
import '../../utils.dart'; import '../../utils.dart';
import '../../utils/path_factory.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 'compound_trim_path_content.dart';
import 'content.dart'; import 'content.dart';
import 'path_content.dart'; import 'path_content.dart';
import 'shape_modifier_content.dart';
import 'trim_path_content.dart'; import 'trim_path_content.dart';
class ShapeContent implements PathContent { class ShapeContent implements PathContent {
@ -17,7 +18,7 @@ class ShapeContent implements PathContent {
final ShapePath _shape; final ShapePath _shape;
final LottieDrawable lottieDrawable; final LottieDrawable lottieDrawable;
final BaseKeyframeAnimation<Object, Path> _shapeAnimation; final ShapeKeyframeAnimation _shapeAnimation;
bool _isPathValid = false; bool _isPathValid = false;
final _trimPaths = CompoundTrimPathContent(); final _trimPaths = CompoundTrimPathContent();
@ -35,6 +36,7 @@ class ShapeContent implements PathContent {
@override @override
void setContents(List<Content> contentsBefore, List<Content> contentsAfter) { void setContents(List<Content> contentsBefore, List<Content> contentsAfter) {
List<ShapeModifierContent>? shapeModifierContents;
for (var i = 0; i < contentsBefore.length; i++) { for (var i = 0; i < contentsBefore.length; i++) {
var content = contentsBefore[i]; var content = contentsBefore[i];
if (content is TrimPathContent && if (content is TrimPathContent &&
@ -43,8 +45,12 @@ class ShapeContent implements PathContent {
var trimPath = content; var trimPath = content;
_trimPaths.addTrimPath(trimPath); _trimPaths.addTrimPath(trimPath);
trimPath.addListener(_invalidate); trimPath.addListener(_invalidate);
} else if (content is ShapeModifierContent) {
shapeModifierContents ??= [];
shapeModifierContents.add(content);
} }
} }
_shapeAnimation.setShapeModifiers(shapeModifierContents);
} }
@override @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

@ -3,11 +3,13 @@ import '../../model/content/shape_data.dart';
import '../../utils/misc.dart'; import '../../utils/misc.dart';
import '../../utils/path_factory.dart'; import '../../utils/path_factory.dart';
import '../../value/keyframe.dart'; import '../../value/keyframe.dart';
import '../content/shape_modifier_content.dart';
import 'base_keyframe_animation.dart'; import 'base_keyframe_animation.dart';
class ShapeKeyframeAnimation extends BaseKeyframeAnimation<ShapeData, Path> { class ShapeKeyframeAnimation extends BaseKeyframeAnimation<ShapeData, Path> {
final ShapeData _tempShapeData = ShapeData.empty(); final ShapeData _tempShapeData = ShapeData.empty();
final Path _tempPath = PathFactory.create(); final Path _tempPath = PathFactory.create();
List<ShapeModifierContent>? _shapeModifiers;
ShapeKeyframeAnimation(List<Keyframe<ShapeData>> keyframes) ShapeKeyframeAnimation(List<Keyframe<ShapeData>> keyframes)
: super(keyframes); : super(keyframes);
@ -19,7 +21,18 @@ class ShapeKeyframeAnimation extends BaseKeyframeAnimation<ShapeData, Path> {
_tempShapeData.interpolateBetween( _tempShapeData.interpolateBetween(
startShapeData, endShapeData, keyframeProgress); 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; return _tempPath;
} }
void setShapeModifiers(List<ShapeModifierContent>? shapeModifiers) {
_shapeModifiers = shapeModifiers;
}
} }

View File

@ -1,5 +1,7 @@
import '../../model/document_data.dart'; import '../../model/document_data.dart';
import '../../value/keyframe.dart'; import '../../value/keyframe.dart';
import '../../value/lottie_frame_info.dart';
import '../../value/lottie_value_callback.dart';
import 'keyframe_animation.dart'; import 'keyframe_animation.dart';
class TextKeyframeAnimation extends KeyframeAnimation<DocumentData> { class TextKeyframeAnimation extends KeyframeAnimation<DocumentData> {
@ -9,10 +11,58 @@ class TextKeyframeAnimation extends KeyframeAnimation<DocumentData> {
@override @override
DocumentData getValue( DocumentData getValue(
Keyframe<DocumentData> keyframe, double keyframeProgress) { 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!; return keyframe.startValue!;
} else { } else {
return keyframe.endValue!; 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

@ -26,6 +26,7 @@ class Lottie extends StatefulWidget {
this.delegates, this.delegates,
this.options, this.options,
bool? addRepaintBoundary, bool? addRepaintBoundary,
this.filterQuality,
}) : animate = animate ?? true, }) : animate = animate ?? true,
reverse = reverse ?? false, reverse = reverse ?? false,
repeat = repeat ?? true, repeat = repeat ?? true,
@ -51,9 +52,10 @@ class Lottie extends StatefulWidget {
double? width, double? width,
double? height, double? height,
BoxFit? fit, BoxFit? fit,
Alignment? alignment, AlignmentGeometry? alignment,
String? package, String? package,
bool? addRepaintBoundary, bool? addRepaintBoundary,
FilterQuality? filterQuality,
WarningCallback? onWarning, WarningCallback? onWarning,
}) => }) =>
LottieBuilder.asset( LottieBuilder.asset(
@ -77,6 +79,7 @@ class Lottie extends StatefulWidget {
alignment: alignment, alignment: alignment,
package: package, package: package,
addRepaintBoundary: addRepaintBoundary, addRepaintBoundary: addRepaintBoundary,
filterQuality: filterQuality,
onWarning: onWarning, onWarning: onWarning,
); );
@ -98,8 +101,9 @@ class Lottie extends StatefulWidget {
double? width, double? width,
double? height, double? height,
BoxFit? fit, BoxFit? fit,
Alignment? alignment, AlignmentGeometry? alignment,
bool? addRepaintBoundary, bool? addRepaintBoundary,
FilterQuality? filterQuality,
WarningCallback? onWarning, WarningCallback? onWarning,
}) => }) =>
LottieBuilder.file( LottieBuilder.file(
@ -121,6 +125,7 @@ class Lottie extends StatefulWidget {
fit: fit, fit: fit,
alignment: alignment, alignment: alignment,
addRepaintBoundary: addRepaintBoundary, addRepaintBoundary: addRepaintBoundary,
filterQuality: filterQuality,
onWarning: onWarning, onWarning: onWarning,
); );
@ -142,8 +147,9 @@ class Lottie extends StatefulWidget {
double? width, double? width,
double? height, double? height,
BoxFit? fit, BoxFit? fit,
Alignment? alignment, AlignmentGeometry? alignment,
bool? addRepaintBoundary, bool? addRepaintBoundary,
FilterQuality? filterQuality,
WarningCallback? onWarning, WarningCallback? onWarning,
}) => }) =>
LottieBuilder.memory( LottieBuilder.memory(
@ -165,6 +171,7 @@ class Lottie extends StatefulWidget {
fit: fit, fit: fit,
alignment: alignment, alignment: alignment,
addRepaintBoundary: addRepaintBoundary, addRepaintBoundary: addRepaintBoundary,
filterQuality: filterQuality,
onWarning: onWarning, onWarning: onWarning,
); );
@ -186,8 +193,9 @@ class Lottie extends StatefulWidget {
double? width, double? width,
double? height, double? height,
BoxFit? fit, BoxFit? fit,
Alignment? alignment, AlignmentGeometry? alignment,
bool? addRepaintBoundary, bool? addRepaintBoundary,
FilterQuality? filterQuality,
WarningCallback? onWarning, WarningCallback? onWarning,
}) => }) =>
LottieBuilder.network( LottieBuilder.network(
@ -209,6 +217,7 @@ class Lottie extends StatefulWidget {
fit: fit, fit: fit,
alignment: alignment, alignment: alignment,
addRepaintBoundary: addRepaintBoundary, addRepaintBoundary: addRepaintBoundary,
filterQuality: filterQuality,
onWarning: onWarning, onWarning: onWarning,
); );
@ -299,13 +308,19 @@ class Lottie extends StatefulWidget {
/// This property is `true` by default. /// This property is `true` by default.
final bool addRepaintBoundary; 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 bool get traceEnabled => L.traceEnabled;
static set traceEnabled(bool enabled) { static set traceEnabled(bool enabled) {
L.traceEnabled = enabled; L.traceEnabled = enabled;
} }
@override @override
_LottieState createState() => _LottieState(); State<Lottie> createState() => _LottieState();
} }
class _LottieState extends State<Lottie> with TickerProviderStateMixin { class _LottieState extends State<Lottie> with TickerProviderStateMixin {
@ -366,6 +381,7 @@ class _LottieState extends State<Lottie> with TickerProviderStateMixin {
height: widget.height, height: widget.height,
fit: widget.fit, fit: widget.fit,
alignment: widget.alignment, alignment: widget.alignment,
filterQuality: widget.filterQuality,
); );
}, },
); );

View File

@ -59,6 +59,7 @@ class LottieBuilder extends StatefulWidget {
this.fit, this.fit,
this.alignment, this.alignment,
this.addRepaintBoundary, this.addRepaintBoundary,
this.filterQuality,
this.onWarning, this.onWarning,
}) : super(key: key); }) : super(key: key);
@ -83,6 +84,7 @@ class LottieBuilder extends StatefulWidget {
this.fit, this.fit,
this.alignment, this.alignment,
this.addRepaintBoundary, this.addRepaintBoundary,
this.filterQuality,
this.onWarning, this.onWarning,
}) : lottie = NetworkLottie(src, }) : lottie = NetworkLottie(src,
headers: headers, imageProviderFactory: imageProviderFactory), headers: headers, imageProviderFactory: imageProviderFactory),
@ -117,6 +119,7 @@ class LottieBuilder extends StatefulWidget {
this.fit, this.fit,
this.alignment, this.alignment,
this.addRepaintBoundary, this.addRepaintBoundary,
this.filterQuality,
this.onWarning, this.onWarning,
}) : lottie = FileLottie(file, imageProviderFactory: imageProviderFactory), }) : lottie = FileLottie(file, imageProviderFactory: imageProviderFactory),
super(key: key); super(key: key);
@ -143,6 +146,7 @@ class LottieBuilder extends StatefulWidget {
this.alignment, this.alignment,
String? package, String? package,
this.addRepaintBoundary, this.addRepaintBoundary,
this.filterQuality,
this.onWarning, this.onWarning,
}) : lottie = AssetLottie(name, }) : lottie = AssetLottie(name,
bundle: bundle, bundle: bundle,
@ -170,6 +174,7 @@ class LottieBuilder extends StatefulWidget {
this.fit, this.fit,
this.alignment, this.alignment,
this.addRepaintBoundary, this.addRepaintBoundary,
this.filterQuality,
this.onWarning, this.onWarning,
}) : lottie = }) : lottie =
MemoryLottie(bytes, imageProviderFactory: imageProviderFactory), MemoryLottie(bytes, imageProviderFactory: imageProviderFactory),
@ -361,6 +366,12 @@ class LottieBuilder extends StatefulWidget {
/// This property is `true` by default. /// This property is `true` by default.
final bool? addRepaintBoundary; 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 /// A callback called when there is a warning during the loading or painting
/// of the animation. /// of the animation.
final WarningCallback? onWarning; final WarningCallback? onWarning;
@ -398,7 +409,7 @@ class LottieBuilder extends StatefulWidget {
final ImageErrorWidgetBuilder? errorBuilder; final ImageErrorWidgetBuilder? errorBuilder;
@override @override
_LottieBuilderState createState() => _LottieBuilderState(); State<LottieBuilder> createState() => _LottieBuilderState();
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
@ -482,6 +493,7 @@ class _LottieBuilderState extends State<LottieBuilder> {
fit: widget.fit, fit: widget.fit,
alignment: widget.alignment, alignment: widget.alignment,
addRepaintBoundary: widget.addRepaintBoundary, addRepaintBoundary: widget.addRepaintBoundary,
filterQuality: widget.filterQuality,
); );
if (widget.frameBuilder != null) { if (widget.frameBuilder != null) {

View File

@ -16,16 +16,15 @@ class LottieDrawable {
final Size size; final Size size;
LottieDelegates? _delegates; LottieDelegates? _delegates;
bool _isDirty = true; 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. /// Gives a suggestion whether to paint with anti-aliasing, or not. Default is true.
bool antiAliasingSuggested = true; bool antiAliasingSuggested = true;
LottieDrawable(this.composition, LottieDrawable(this.composition, {LottieDelegates? delegates})
{LottieDelegates? delegates, bool? enableMergePaths})
: size = Size(composition.bounds.width.toDouble(), : size = Size(composition.bounds.width.toDouble(),
composition.bounds.height.toDouble()), composition.bounds.height.toDouble()) {
enableMergePaths = enableMergePaths ?? false {
this.delegates = delegates; this.delegates = delegates;
_compositionLayer = CompositionLayer( _compositionLayer = CompositionLayer(
this, LayerParser.parse(composition), composition.layers, composition); this, LayerParser.parse(composition), composition.layers, composition);

View File

@ -129,8 +129,12 @@ abstract class LottieProperty {
static const dropShadow = DropShadow( static const dropShadow = DropShadow(
color: Color(0x00000000), direction: 0, distance: 0, radius: 0); 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 = static const ColorFilter colorFilter =
ColorFilter.mode(Color(0xFF000000), BlendMode.dst); ColorFilter.mode(Color(0xFF000000), BlendMode.dst);
static final List<Color> gradientColor = []; 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 'dart:ui';
import '../../animation/keyframe/base_keyframe_animation.dart';
import '../../animation/keyframe/shape_keyframe_animation.dart'; import '../../animation/keyframe/shape_keyframe_animation.dart';
import '../../value/keyframe.dart'; import '../../value/keyframe.dart';
import '../content/shape_data.dart'; import '../content/shape_data.dart';
@ -10,7 +9,7 @@ class AnimatableShapeValue extends BaseAnimatableValue<ShapeData, Path> {
: super.fromKeyframes(keyframes); : super.fromKeyframes(keyframes);
@override @override
BaseKeyframeAnimation<ShapeData, Path> createAnimation() { ShapeKeyframeAnimation createAnimation() {
return ShapeKeyframeAnimation(keyframes); return ShapeKeyframeAnimation(keyframes);
} }
} }

View File

@ -37,6 +37,7 @@ class PolystarShape implements ContentModel {
final AnimatableDoubleValue? innerRoundedness; final AnimatableDoubleValue? innerRoundedness;
final AnimatableDoubleValue outerRoundedness; final AnimatableDoubleValue outerRoundedness;
final bool hidden; final bool hidden;
final bool isReversed;
PolystarShape({ PolystarShape({
this.name, this.name,
@ -49,6 +50,7 @@ class PolystarShape implements ContentModel {
this.innerRoundedness, this.innerRoundedness,
required this.outerRoundedness, required this.outerRoundedness,
required this.hidden, required this.hidden,
required this.isReversed,
}); });
@override @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; return _initialPoint;
} }
void setClosed(bool closed) {
_closed = closed;
}
bool get isClosed { bool get isClosed {
return _closed; return _closed;
} }

View File

@ -1,7 +1,44 @@
import 'dart:ui'; 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 { class CubicCurveData {
Offset controlPoint1 = Offset.zero; Offset controlPoint1 = Offset.zero;
Offset controlPoint2 = Offset.zero; Offset controlPoint2 = Offset.zero;
Offset vertex = 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

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

View File

@ -484,6 +484,11 @@ class TextLayer extends BaseLayer {
..addUpdateListener(invalidateSelf); ..addUpdateListener(invalidateSelf);
addAnimation(_textSizeCallbackAnimation); 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 'polysar_shape_parser.dart';
import 'rectangle_shape_parser.dart'; import 'rectangle_shape_parser.dart';
import 'repeat_parser.dart'; import 'repeat_parser.dart';
import 'rounded_corners_parser.dart';
import 'shape_fill_parser.dart'; import 'shape_fill_parser.dart';
import 'shape_group_parser.dart'; import 'shape_group_parser.dart';
import 'shape_path_parser.dart'; import 'shape_path_parser.dart';
@ -80,7 +81,7 @@ class ContentModelParser {
model = ShapeTrimPathParser.parse(reader, composition); model = ShapeTrimPathParser.parse(reader, composition);
break; break;
case 'sr': case 'sr':
model = PolystarShapeParser.parse(reader, composition); model = PolystarShapeParser.parse(reader, composition, d: d);
break; break;
case 'mm': case 'mm':
model = MergePathsParser.parse(reader); model = MergePathsParser.parse(reader);
@ -88,6 +89,9 @@ class ContentModelParser {
case 'rp': case 'rp':
model = RepeaterParser.parse(reader, composition); model = RepeaterParser.parse(reader, composition);
break; break;
case 'rd':
model = RoundedCornersParser.parse(reader, composition);
break;
default: default:
composition.addWarning('Unknown shape type $type'); composition.addWarning('Unknown shape type $type');
} }

View File

@ -3,8 +3,19 @@ import '../model/document_data.dart';
import 'json_utils.dart'; import 'json_utils.dart';
import 'moshi/json_reader.dart'; import 'moshi/json_reader.dart';
final JsonReaderOptions _names = JsonReaderOptions.of( final JsonReaderOptions _names = JsonReaderOptions.of([
['t', 'f', 's', 'j', 'tr', 'lh', 'ls', 'fc', 'sc', 'sw', '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}) { DocumentData documentDataParser(JsonReader reader, {required double scale}) {
String? text; String? text;

View File

@ -37,6 +37,17 @@ class GradientColorParser {
while (reader.hasNext()) { while (reader.hasNext()) {
array.add(reader.nextDouble()); 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) { if (isArray) {
reader.endArray(); reader.endArray();
} }

View File

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

@ -23,6 +23,7 @@ class RawLottie extends LeafRenderObjectWidget {
this.height, this.height,
this.fit, this.fit,
AlignmentGeometry? alignment, AlignmentGeometry? alignment,
this.filterQuality,
}) : progress = progress ?? 0.0, }) : progress = progress ?? 0.0,
alignment = alignment ?? Alignment.center, alignment = alignment ?? Alignment.center,
super(key: key); super(key: key);
@ -78,6 +79,8 @@ class RawLottie extends LeafRenderObjectWidget {
/// relative to text direction. /// relative to text direction.
final AlignmentGeometry alignment; final AlignmentGeometry alignment;
final FilterQuality? filterQuality;
@override @override
RenderLottie createRenderObject(BuildContext context) { RenderLottie createRenderObject(BuildContext context) {
return RenderLottie( return RenderLottie(
@ -90,17 +93,21 @@ class RawLottie extends LeafRenderObjectWidget {
height: height, height: height,
fit: fit, fit: fit,
alignment: alignment, alignment: alignment,
filterQuality: filterQuality,
); );
} }
@override @override
void updateRenderObject(BuildContext context, RenderLottie renderObject) { void updateRenderObject(BuildContext context, RenderLottie renderObject) {
renderObject renderObject
..setComposition(composition, ..setComposition(
composition,
progress: progress, progress: progress,
frameRate: frameRate, frameRate: frameRate,
delegates: delegates, delegates: delegates,
enableMergePaths: options?.enableMergePaths) enableMergePaths: options?.enableMergePaths,
filterQuality: filterQuality,
)
..width = width ..width = width
..height = height ..height = height
..alignment = alignment ..alignment = alignment

View File

@ -19,11 +19,14 @@ class RenderLottie extends RenderBox {
double? height, double? height,
BoxFit? fit, BoxFit? fit,
AlignmentGeometry alignment = Alignment.center, AlignmentGeometry alignment = Alignment.center,
FilterQuality? filterQuality,
}) : assert(progress >= 0.0 && progress <= 1.0), }) : assert(progress >= 0.0 && progress <= 1.0),
_drawable = composition != null _drawable = composition != null
? (LottieDrawable(composition, enableMergePaths: enableMergePaths) ? (LottieDrawable(composition)
..setProgress(progress, frameRate: frameRate) ..setProgress(progress, frameRate: frameRate)
..delegates = delegates) ..delegates = delegates
..enableMergePaths = enableMergePaths ?? false
..filterQuality = filterQuality)
: null, : null,
_width = width, _width = width,
_height = height, _height = height,
@ -33,11 +36,13 @@ class RenderLottie extends RenderBox {
/// The lottie composition to display. /// The lottie composition to display.
LottieComposition? get composition => _drawable?.composition; LottieComposition? get composition => _drawable?.composition;
LottieDrawable? _drawable; LottieDrawable? _drawable;
void setComposition(LottieComposition? composition, void setComposition(LottieComposition? composition,
{required double progress, {required double progress,
required FrameRate? frameRate, required FrameRate? frameRate,
required LottieDelegates? delegates, required LottieDelegates? delegates,
bool? enableMergePaths}) { bool? enableMergePaths,
FilterQuality? filterQuality}) {
var drawable = _drawable; var drawable = _drawable;
enableMergePaths ??= false; enableMergePaths ??= false;
@ -50,11 +55,8 @@ class RenderLottie extends RenderBox {
needsLayout = true; needsLayout = true;
} }
} else { } else {
if (drawable == null || if (drawable == null || drawable.composition != composition) {
drawable.composition != composition || drawable = _drawable = LottieDrawable(composition);
drawable.enableMergePaths != enableMergePaths) {
drawable = _drawable =
LottieDrawable(composition, enableMergePaths: enableMergePaths);
needsLayout = true; needsLayout = true;
needsPaint = true; needsPaint = true;
} }
@ -65,6 +67,14 @@ class RenderLottie extends RenderBox {
drawable.delegates = delegates; drawable.delegates = delegates;
needsPaint = true; needsPaint = true;
} }
if (enableMergePaths != drawable.enableMergePaths) {
drawable.enableMergePaths = enableMergePaths;
needsPaint = true;
}
if (filterQuality != drawable.filterQuality) {
drawable.filterQuality = filterQuality;
needsPaint = true;
}
} }
if (needsPaint) { if (needsPaint) {

View File

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

View File

@ -7,28 +7,28 @@ packages:
name: _fe_analyzer_shared name: _fe_analyzer_shared
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "26.0.0" version: "43.0.0"
analyzer: analyzer:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: analyzer name: analyzer
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.0" version: "4.3.1"
archive: archive:
dependency: "direct main" dependency: "direct main"
description: description:
name: archive name: archive
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.2" version: "3.3.1"
args: args:
dependency: transitive dependency: transitive
description: description:
name: args name: args
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.3.0" version: "2.3.1"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -49,42 +49,42 @@ packages:
name: build name: build
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.3.0"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
name: build_config name: build_config
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.1.0"
build_daemon: build_daemon:
dependency: transitive dependency: transitive
description: description:
name: build_daemon name: build_daemon
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.0" version: "3.1.0"
build_resolvers: build_resolvers:
dependency: transitive dependency: transitive
description: description:
name: build_resolvers name: build_resolvers
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.4" version: "2.0.9"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.2" version: "2.2.0"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "7.1.0" version: "7.2.3"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -98,7 +98,7 @@ packages:
name: built_value name: built_value
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "8.1.2" version: "8.4.0"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -120,13 +120,6 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.0.1"
cli_util:
dependency: transitive
description:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.3"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -147,35 +140,35 @@ packages:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.15.0" version: "1.16.0"
convert: convert:
dependency: transitive dependency: transitive
description: description:
name: convert name: convert
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2"
crypto: crypto:
dependency: transitive dependency: transitive
description: description:
name: crypto name: crypto
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2"
dart_style: dart_style:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: dart_style name: dart_style
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.2.3"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.3.0"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -189,7 +182,7 @@ packages:
name: fixnum name: fixnum
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.0.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -201,7 +194,7 @@ packages:
name: flutter_lints name: flutter_lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "2.0.1"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -213,14 +206,14 @@ packages:
name: frontend_server_client name: frontend_server_client
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.2" version: "2.1.3"
glob: glob:
dependency: transitive dependency: transitive
description: description:
name: glob name: glob
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" version: "2.1.0"
graphs: graphs:
dependency: transitive dependency: transitive
description: description:
@ -234,14 +227,14 @@ packages:
name: http_multi_server name: http_multi_server
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.1" version: "3.2.1"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
name: http_parser name: http_parser
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.0" version: "4.0.1"
io: io:
dependency: transitive dependency: transitive
description: description:
@ -255,21 +248,21 @@ packages:
name: js name: js
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.3" version: "0.6.4"
json_annotation: json_annotation:
dependency: transitive dependency: transitive
description: description:
name: json_annotation name: json_annotation
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "4.0.1" version: "4.6.0"
lints: lints:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "2.0.0"
logging: logging:
dependency: transitive dependency: transitive
description: description:
@ -290,7 +283,7 @@ packages:
name: material_color_utilities name: material_color_utilities
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.3" version: "0.1.4"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@ -304,63 +297,56 @@ packages:
name: mime name: mime
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.0.2"
package_config: package_config:
dependency: transitive dependency: transitive
description: description:
name: package_config name: package_config
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.2" version: "2.1.0"
path: path:
dependency: "direct main" dependency: "direct main"
description: description:
name: path name: path
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.0" version: "1.8.1"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.1"
pool: pool:
dependency: transitive dependency: transitive
description: description:
name: pool name: pool
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.5.0" version: "1.5.1"
pub_semver: pub_semver:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: pub_semver name: pub_semver
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
pubspec_parse: pubspec_parse:
dependency: transitive dependency: transitive
description: description:
name: pubspec_parse name: pubspec_parse
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.2.0"
shelf: shelf:
dependency: transitive dependency: transitive
description: description:
name: shelf name: shelf
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.0" version: "1.3.1"
shelf_web_socket: shelf_web_socket:
dependency: transitive dependency: transitive
description: description:
name: shelf_web_socket name: shelf_web_socket
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "1.0.2"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -372,7 +358,7 @@ packages:
name: source_span name: source_span
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.1" version: "1.8.2"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -414,7 +400,7 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.4.8" version: "0.4.9"
timing: timing:
dependency: transitive dependency: transitive
description: description:
@ -428,34 +414,35 @@ packages:
name: typed_data name: typed_data
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.0" version: "1.3.1"
vector_math: vector_math:
dependency: "direct main" dependency: "direct main"
description: description:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
watcher: watcher:
dependency: transitive dependency: transitive
description: description:
name: watcher name: watcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.0.1"
web_socket_channel: web_socket_channel:
dependency: transitive dependency: transitive
description: description:
name: web_socket_channel name: web_socket_channel
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" version: "2.2.0"
yaml: yaml:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: yaml name: yaml
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.0" version: "3.1.1"
sdks: sdks:
dart: ">=2.14.0 <3.0.0" dart: ">=2.17.0 <3.0.0"
flutter: ">=2.10.0"

View File

@ -1,10 +1,11 @@
name: lottie name: lottie
description: Render After Effects animations natively on Flutter. This package is a pure Dart implementation of a Lottie player. description: Render After Effects animations natively on Flutter. This package is a pure Dart implementation of a Lottie player.
version: 1.2.2 version: 1.4.2
homepage: https://github.com/xvrh/lottie-flutter homepage: https://github.com/xvrh/lottie-flutter
environment: environment:
sdk: '>=2.12.0 <3.0.0' sdk: '>=2.12.0 <3.0.0'
flutter: '>=2.10.0'
dependencies: dependencies:
archive: ^3.0.0 archive: ^3.0.0

View File

@ -2,27 +2,83 @@ import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:lottie/lottie.dart'; import 'package:lottie/lottie.dart';
import 'utils.dart';
void main() { void main() {
testWidgets('Dynamic test', (tester) async { late LottieComposition composition;
var composition = await LottieComposition.fromBytes(
File('example/assets/Tests/DynamicText.json').readAsBytesSync());
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( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Lottie( home: FilmStrip(
composition: composition, composition,
animate: false, delegates: delegates,
delegates: LottieDelegates( size: size,
text: (input) => '🔥c🔥👮🏿🔥',
textStyle: (font) => const TextStyle(
fontFamily: 'Roboto', fontFamilyFallback: ['Noto Emoji']),
values: const []),
), ),
), ),
); );
await tester.pump();
await expectLater( await expectLater(find.byType(FilmStrip),
find.byType(Lottie), matchesGoldenFile('goldens/dynamic_text.png')); 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 12 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: 4.8 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB