Expose LottieDelegates to modify animation properties at runtime (#23)
6
.github/workflows/analyze-and-test.yaml
vendored
@ -11,7 +11,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
flutter: ['stable', 'dev']
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: subosito/flutter-action@v1
|
||||
@ -19,8 +19,12 @@ jobs:
|
||||
channel: ${{ matrix.flutter }}
|
||||
- run: flutter doctor
|
||||
- run: flutter --version
|
||||
- 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
|
||||
working-directory: example
|
||||
- run: flutter pub run tool/prepare_submit.dart
|
||||
- name: "check for uncommitted changes"
|
||||
run: |
|
||||
|
2
.gitignore
vendored
@ -4,6 +4,8 @@ _*
|
||||
!.gitignore
|
||||
!.github
|
||||
|
||||
**/failures/*.png
|
||||
|
||||
*.iml
|
||||
**/doc/api/
|
||||
build/
|
||||
|
@ -1,3 +1,8 @@
|
||||
## [0.3.0] - 2020-03-02
|
||||
- Add `LottieDelegates` a group of options to customize the lottie animation at runtime.
|
||||
ie: Dynamically modify color, position, size, text... of every elements of the animation.
|
||||
- Integrate latest changes from Lottie-android
|
||||
|
||||
## [0.2.2] - 2020-02-21
|
||||
- Add a [repeat] parameter to specify if the automatic animation should loop.
|
||||
- Add the [animate], [reverse], [repeat] properties on `LottieBuilder`
|
||||
|
50
README.md
@ -37,7 +37,7 @@ class MyApp extends StatelessWidget {
|
||||
'https://raw.githubusercontent.com/xvrh/lottie-flutter/master/example/assets/Mobilo/A.json'),
|
||||
|
||||
// Load an animation and its images from a zip file
|
||||
Lottie.asset('assets/lottiesfiles/angel.zip'),
|
||||
Lottie.asset('assets/lottiefiles/angel.zip'),
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -46,15 +46,8 @@ class MyApp extends StatelessWidget {
|
||||
}
|
||||
```
|
||||
|
||||
To load an animation from the assets folder, we need to add an `assets` section in the `pubspec.yaml`:
|
||||
```yaml
|
||||
flutter:
|
||||
assets:
|
||||
- assets/
|
||||
```
|
||||
|
||||
### Specify a custom `AnimationController`
|
||||
This example shows how to have full control over the animation by providing your own `AnimationController`.
|
||||
This example shows how to take full control over the animation by providing your own `AnimationController`.
|
||||
|
||||
```dart
|
||||
import 'package:flutter/material.dart';
|
||||
@ -201,7 +194,9 @@ class _Painter extends CustomPainter {
|
||||
var columns = 10;
|
||||
for (var i = 0; i < frameCount; i++) {
|
||||
var destRect = Offset(i % columns * 50.0, i ~/ 10 * 80.0) & (size / 5);
|
||||
drawable.draw(canvas, destRect, progress: i / frameCount);
|
||||
drawable
|
||||
..setProgress(i / frameCount)
|
||||
..draw(canvas, destRect);
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,14 +207,45 @@ class _Painter extends CustomPainter {
|
||||
}
|
||||
````
|
||||
|
||||
### Modify properties at runtime
|
||||
This example shows how to modify some properties of the animation at runtime. Here we change the text,
|
||||
the color, the opacity and the position of some layers.
|
||||
For each `ValueDelegate` we can either provide a static `value` or a `callback` to compute a value for a each frame.
|
||||
|
||||
````dart
|
||||
class _Animation extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Lottie.asset(
|
||||
'assets/Tests/Shapes.json',
|
||||
delegates: LottieDelegates(
|
||||
text: (initialText) => translate(initialText),
|
||||
values: [
|
||||
ValueDelegate.color(
|
||||
const ['Shape Layer 1', 'Rectangle', 'Fill 1'],
|
||||
value: Colors.red,
|
||||
),
|
||||
ValueDelegate.opacity(
|
||||
const ['Shape Layer 1', 'Rectangle'],
|
||||
callback: (frameInfo) =>
|
||||
(frameInfo.overallProgress * 100).round(),
|
||||
),
|
||||
ValueDelegate.position(
|
||||
const ['Shape Layer 1', 'Rectangle'],
|
||||
relative: Offset(100, 200),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
````
|
||||
|
||||
## Limitations
|
||||
This is a new library so usability, documentation and performance are still work in progress.
|
||||
|
||||
The following features are not yet implemented:
|
||||
- Dash path effects
|
||||
- Transforms on gradients (stroke and fills)
|
||||
- Expose `Value callback` to modify dynamically some properties of the animation
|
||||
- Text in animations has very basic support (unoptimized and buggy)
|
||||
|
||||
## Flutter Web
|
||||
Run the app with `flutter run -d Chrome --dart-define=FLUTTER_WEB_USE_SKIA=true --release`
|
||||
|
@ -20,15 +20,8 @@ The `Lottie` widget will load the json file and run the animation indefinitely.
|
||||
import 'example/lib/examples/main.dart';
|
||||
```
|
||||
|
||||
To load an animation from the assets folder, we need to add an `assets` section in the `pubspec.yaml`:
|
||||
```yaml
|
||||
flutter:
|
||||
assets:
|
||||
- assets/
|
||||
```
|
||||
|
||||
### Specify a custom `AnimationController`
|
||||
This example shows how to have full control over the animation by providing your own `AnimationController`.
|
||||
This example shows how to take full control over the animation by providing your own `AnimationController`.
|
||||
|
||||
```dart
|
||||
import 'example/lib/examples/animation_controller.dart';
|
||||
@ -69,14 +62,21 @@ a specific position and size.
|
||||
import 'example/lib/examples/custom_draw.dart#example';
|
||||
````
|
||||
|
||||
### Modify properties at runtime
|
||||
This example shows how to modify some properties of the animation at runtime. Here we change the text,
|
||||
the color, the opacity and the position of some layers.
|
||||
For each `ValueDelegate` we can either provide a static `value` or a `callback` to compute a value for a each frame.
|
||||
|
||||
````dart
|
||||
import 'example/lib/examples/simple_dynamic_properties.dart#example';
|
||||
````
|
||||
|
||||
## Limitations
|
||||
This is a new library so usability, documentation and performance are still work in progress.
|
||||
|
||||
The following features are not yet implemented:
|
||||
- Dash path effects
|
||||
- Transforms on gradients (stroke and fills)
|
||||
- Expose `Value callback` to modify dynamically some properties of the animation
|
||||
- Text in animations has very basic support (unoptimized and buggy)
|
||||
|
||||
## Flutter Web
|
||||
Run the app with `flutter run -d Chrome --dart-define=FLUTTER_WEB_USE_SKIA=true --release`
|
||||
|
BIN
example/assets/fonts/NotoEmoji-Regular.ttf
Normal file
@ -81,7 +81,9 @@ class _Painter extends CustomPainter {
|
||||
var columns = 10;
|
||||
for (var i = 0; i < frameCount; i++) {
|
||||
var destRect = Offset(i % columns * 50.0, i ~/ 10 * 80.0) & (size / 5);
|
||||
drawable.draw(canvas, destRect, progress: i / frameCount);
|
||||
drawable
|
||||
..setProgress(i / frameCount)
|
||||
..draw(canvas, destRect);
|
||||
}
|
||||
}
|
||||
|
||||
|
86
example/lib/examples/dynamic_properties.dart
Normal file
@ -0,0 +1,86 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
|
||||
void main() async {
|
||||
runApp(App());
|
||||
}
|
||||
|
||||
class App extends StatefulWidget {
|
||||
const App({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_AppState createState() => _AppState();
|
||||
}
|
||||
|
||||
class _AppState extends State<App> with TickerProviderStateMixin {
|
||||
Color _color = Colors.green;
|
||||
double _opacity = 0.5;
|
||||
bool _useDelegates = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var valueDelegates = [
|
||||
ValueDelegate.color(['Shape Layer 1', 'Rectangle', 'Fill 1'],
|
||||
value: _color),
|
||||
ValueDelegate.opacity(['Shape Layer 1', 'Rectangle', 'Fill 1'],
|
||||
callback: (_) => (_opacity * 100).round()),
|
||||
];
|
||||
|
||||
return MaterialApp(
|
||||
color: Colors.blue,
|
||||
home: Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
title: Text('Dynamic properties'),
|
||||
),
|
||||
body: ListView(
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
width: 300,
|
||||
height: 300,
|
||||
child: Lottie.asset(
|
||||
'assets/Tests/Shapes.json',
|
||||
delegates: LottieDelegates(
|
||||
values: _useDelegates ? valueDelegates : null),
|
||||
),
|
||||
),
|
||||
Checkbox(
|
||||
value: _useDelegates,
|
||||
onChanged: (newValue) {
|
||||
setState(() {
|
||||
_useDelegates = newValue;
|
||||
});
|
||||
},
|
||||
),
|
||||
Slider(
|
||||
value: _opacity,
|
||||
onChanged: (newOpacity) {
|
||||
setState(() {
|
||||
_opacity = newOpacity;
|
||||
});
|
||||
},
|
||||
),
|
||||
Center(
|
||||
child: Container(
|
||||
width: 500,
|
||||
child: ColorPicker(
|
||||
pickerColor: _color,
|
||||
onColorChanged: (newColor) {
|
||||
setState(() {
|
||||
_color = newColor;
|
||||
});
|
||||
},
|
||||
showLabel: false,
|
||||
enableAlpha: false,
|
||||
pickerAreaHeightPercent: 0.8,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
72
example/lib/examples/dynamic_text.dart
Normal file
@ -0,0 +1,72 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
|
||||
void main() async {
|
||||
runApp(App());
|
||||
}
|
||||
|
||||
class App extends StatefulWidget {
|
||||
const App({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_AppState createState() => _AppState();
|
||||
}
|
||||
|
||||
class _AppState extends State<App> with TickerProviderStateMixin {
|
||||
TextEditingController _textController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
_textController = TextEditingController(text: /*'🔥Fire🔥'*/ 'Fire');
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_textController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
color: Colors.blue,
|
||||
home: Scaffold(
|
||||
backgroundColor: Colors.white,
|
||||
appBar: AppBar(
|
||||
title: Text('Dynamic text'),
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
width: 300,
|
||||
height: 300,
|
||||
child: Lottie.asset(
|
||||
'assets/Tests/DynamicText.json',
|
||||
delegates: LottieDelegates(
|
||||
text: (animationText) => _textController.text,
|
||||
textStyle: (font) => TextStyle(
|
||||
fontFamily: font.fontFamily,
|
||||
fontStyle: FontStyle.italic),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
width: 300,
|
||||
child: CupertinoTextField(
|
||||
controller: _textController,
|
||||
onChanged: (newText) {
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -18,7 +18,7 @@ class MyApp extends StatelessWidget {
|
||||
'https://raw.githubusercontent.com/xvrh/lottie-flutter/master/example/assets/Mobilo/A.json'),
|
||||
|
||||
// Load an animation and its images from a zip file
|
||||
Lottie.asset('assets/lottiesfiles/angel.zip'),
|
||||
Lottie.asset('assets/lottiefiles/angel.zip'),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
47
example/lib/examples/simple_dynamic_properties.dart
Normal file
@ -0,0 +1,47 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
|
||||
void main() => runApp(MyApp());
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
home: Scaffold(
|
||||
body: ListView(
|
||||
children: [_Animation()],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String translate(String input) => '**$input**';
|
||||
|
||||
//--- example
|
||||
class _Animation extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Lottie.asset(
|
||||
'assets/Tests/Shapes.json',
|
||||
delegates: LottieDelegates(
|
||||
text: (initialText) => translate(initialText),
|
||||
values: [
|
||||
ValueDelegate.color(
|
||||
const ['Shape Layer 1', 'Rectangle', 'Fill 1'],
|
||||
value: Colors.red,
|
||||
),
|
||||
ValueDelegate.opacity(
|
||||
const ['Shape Layer 1', 'Rectangle'],
|
||||
callback: (frameInfo) =>
|
||||
(frameInfo.overallProgress * 100).round(),
|
||||
),
|
||||
ValueDelegate.position(
|
||||
const ['Shape Layer 1', 'Rectangle'],
|
||||
relative: Offset(100, 200),
|
||||
),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
//---
|
@ -66,7 +66,6 @@ class _Item extends StatelessWidget {
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white,
|
||||
//border: Border.all(color: Colors.black12),
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
|
@ -29,6 +29,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
charcode:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -62,11 +69,25 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_colorpicker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_colorpicker
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.3.2"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
golden_toolkit:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: golden_toolkit
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.2.1"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -87,7 +108,7 @@ packages:
|
||||
path: ".."
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.2.2"
|
||||
version: "0.3.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -200,3 +221,4 @@ packages:
|
||||
version: "3.5.0"
|
||||
sdks:
|
||||
dart: ">=2.7.0 <3.0.0"
|
||||
flutter: ">=1.5.4 <2.0.0"
|
||||
|
@ -10,10 +10,12 @@ dependencies:
|
||||
sdk: flutter
|
||||
lottie:
|
||||
path: ../
|
||||
flutter_colorpicker:
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
golden_toolkit:
|
||||
|
||||
dependency_overrides:
|
||||
pedantic: ^1.9.0
|
||||
@ -34,33 +36,25 @@ flutter:
|
||||
- assets/Images/
|
||||
- assets/Images/WeAccept/
|
||||
|
||||
# To add assets to your package, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
#
|
||||
# For details regarding assets in packages, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
#
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware.
|
||||
|
||||
# To add custom fonts to your package, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
# For details regarding fonts in packages, see
|
||||
# https://flutter.dev/custom-fonts/#from-packages
|
||||
fonts:
|
||||
- family: Comic Neue
|
||||
fonts:
|
||||
- asset: assets/fonts/Comic-Neue.ttf
|
||||
- family: Helvetica
|
||||
fonts:
|
||||
- asset: assets/fonts/Helvetica.ttf
|
||||
- family: Helvetica Neue
|
||||
fonts:
|
||||
- asset: assets/fonts/Helvetica-Neue.ttf
|
||||
- family: Open Sans
|
||||
fonts:
|
||||
- asset: assets/fonts/Open-Sans.ttf
|
||||
- family: PT Serif
|
||||
fonts:
|
||||
- asset: assets/fonts/PT-Serif.ttf
|
||||
- family: Roboto
|
||||
fonts:
|
||||
- asset: assets/fonts/Roboto.ttf
|
||||
- family: Noto Emoji
|
||||
fonts:
|
||||
- asset: assets/fonts/NotoEmoji-Regular.ttf
|
293
example/test/dynamic_properties_test.dart
Normal file
@ -0,0 +1,293 @@
|
||||
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';
|
||||
|
||||
void main() {
|
||||
LottieComposition composition;
|
||||
|
||||
setUpAll(() async {
|
||||
composition = await LottieComposition.fromBytes(
|
||||
File('assets/Tests/Shapes.json').readAsBytesSync());
|
||||
});
|
||||
|
||||
void testGolden(String description, ValueDelegate delegate,
|
||||
{double progress}) async {
|
||||
var screenshotName = description
|
||||
.toLowerCase()
|
||||
.replaceAll(RegExp('[^a-z0-9 ]'), '')
|
||||
.replaceAll(' ', '_');
|
||||
|
||||
testWidgets(description, (tester) async {
|
||||
var animation =
|
||||
AnimationController(vsync: tester, duration: composition.duration);
|
||||
if (progress != null) {
|
||||
animation.value = progress;
|
||||
}
|
||||
|
||||
await tester.pumpWidget(
|
||||
Lottie(
|
||||
composition: composition,
|
||||
controller: animation,
|
||||
delegates: LottieDelegates(values: [delegate]),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
await expectLater(find.byType(Lottie),
|
||||
matchesGoldenFile('goldens/dynamic/$screenshotName.png'));
|
||||
|
||||
if (progress == null || progress == 0) {
|
||||
await tester.pumpWidget(
|
||||
Lottie(
|
||||
composition: composition,
|
||||
controller: animation,
|
||||
delegates: LottieDelegates(values: []),
|
||||
),
|
||||
);
|
||||
await tester.pump();
|
||||
await expectLater(find.byType(Lottie),
|
||||
matchesGoldenFile('goldens/dynamic_without_delegate.png'));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
testGolden(
|
||||
'Fill color (Green)',
|
||||
ValueDelegate.color(['Shape Layer 1', 'Rectangle', 'Fill 1'],
|
||||
value: Colors.green),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Fill color (Yellow)',
|
||||
ValueDelegate.color(['Shape Layer 1', 'Rectangle', 'Fill 1'],
|
||||
value: Colors.yellow),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Fill opacity',
|
||||
ValueDelegate.opacity(['Shape Layer 1', 'Rectangle', 'Fill 1'], value: 50),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Stroke color',
|
||||
ValueDelegate.strokeColor(['Shape Layer 1', 'Rectangle', 'Stroke 1'],
|
||||
value: Colors.green),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Stroke width',
|
||||
ValueDelegate.strokeWidth(['Shape Layer 1', 'Rectangle', 'Stroke 1'],
|
||||
value: 50),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Stroke opacity',
|
||||
ValueDelegate.opacity(['Shape Layer 1', 'Rectangle', 'Stroke 1'],
|
||||
value: 50),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Transform anchor point',
|
||||
ValueDelegate.transformAnchorPoint(['Shape Layer 1', 'Rectangle'],
|
||||
value: Offset(20, 20)),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Transform position',
|
||||
ValueDelegate.transformPosition(['Shape Layer 1', 'Rectangle'],
|
||||
value: Offset(20, 20)),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Transform position (relative)',
|
||||
ValueDelegate.transformPosition(['Shape Layer 1', 'Rectangle'],
|
||||
relative: Offset(20, 20)),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Transform opacity',
|
||||
ValueDelegate.transformOpacity(['Shape Layer 1', 'Rectangle'], value: 50),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Transform rotation',
|
||||
ValueDelegate.transformRotation(['Shape Layer 1', 'Rectangle'], value: 45),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Transform scale',
|
||||
ValueDelegate.transformScale(['Shape Layer 1', 'Rectangle'],
|
||||
value: Offset(0.5, 0.5)),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Rectangle corner roundedness',
|
||||
ValueDelegate.cornerRadius(
|
||||
['Shape Layer 1', 'Rectangle', 'Rectangle Path 1'],
|
||||
value: 7),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Rectangle position',
|
||||
ValueDelegate.position(['Shape Layer 1', 'Rectangle', 'Rectangle Path 1'],
|
||||
relative: Offset(20, 20)),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Rectangle size',
|
||||
ValueDelegate.rectangleSize(
|
||||
['Shape Layer 1', 'Rectangle', 'Rectangle Path 1'],
|
||||
relative: Offset(30, 40)),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Ellipse position',
|
||||
ValueDelegate.position(['Shape Layer 1', 'Ellipse', 'Ellipse Path 1'],
|
||||
relative: Offset(20, 20)),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Ellipse size',
|
||||
ValueDelegate.ellipseSize(['Shape Layer 1', 'Ellipse', 'Ellipse Path 1'],
|
||||
relative: Offset(40, 60)),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Star points',
|
||||
ValueDelegate.polystarPoints(['Shape Layer 1', 'Star', 'Polystar Path 1'],
|
||||
value: 8),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Star rotation',
|
||||
ValueDelegate.polystarRotation(['Shape Layer 1', 'Star', 'Polystar Path 1'],
|
||||
value: 10),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Star position',
|
||||
ValueDelegate.position(['Shape Layer 1', 'Star', 'Polystar Path 1'],
|
||||
relative: Offset(20, 20)),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Star inner radius',
|
||||
ValueDelegate.polystarInnerRadius(
|
||||
['Shape Layer 1', 'Star', 'Polystar Path 1'],
|
||||
value: 10),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Star inner roundedness',
|
||||
ValueDelegate.polystarInnerRoundedness(
|
||||
['Shape Layer 1', 'Star', 'Polystar Path 1'],
|
||||
value: 100),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Star outer radius',
|
||||
ValueDelegate.polystarOuterRadius(
|
||||
['Shape Layer 1', 'Star', 'Polystar Path 1'],
|
||||
value: 60),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Star outer roundedness',
|
||||
ValueDelegate.polystarOuterRoundedness(
|
||||
['Shape Layer 1', 'Star', 'Polystar Path 1'],
|
||||
value: 100),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Polygon points',
|
||||
ValueDelegate.polystarPoints(['Shape Layer 1', 'Star', 'Polystar Path 1'],
|
||||
value: 8),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Polygon rotation',
|
||||
ValueDelegate.polystarRotation(['Shape Layer 1', 'Star', 'Polystar Path 1'],
|
||||
value: 10),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Polygon position',
|
||||
ValueDelegate.position(['Shape Layer 1', 'Star', 'Polystar Path 1'],
|
||||
relative: Offset(20, 20)),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Polygon radius',
|
||||
ValueDelegate.polystarOuterRadius(
|
||||
['Shape Layer 1', 'Star', 'Polystar Path 1'],
|
||||
relative: 60),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Polygon roundedness',
|
||||
ValueDelegate.polystarOuterRoundedness(
|
||||
['Shape Layer 1', 'Star', 'Polystar Path 1'],
|
||||
value: 100),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Repeater transform position',
|
||||
ValueDelegate.transformPosition(
|
||||
['Shape Layer 1', 'Repeater Shape', 'Repeater 1'],
|
||||
relative: Offset(100, 100)),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Repeater transform start opacity',
|
||||
ValueDelegate.transformStartOpacity(
|
||||
['Shape Layer 1', 'Repeater Shape', 'Repeater 1'],
|
||||
value: 25),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Repeater transform end opacity',
|
||||
ValueDelegate.transformEndOpacity(
|
||||
['Shape Layer 1', 'Repeater Shape', 'Repeater 1'],
|
||||
value: 25),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Repeater transform rotation',
|
||||
ValueDelegate.transformRotation(
|
||||
['Shape Layer 1', 'Repeater Shape', 'Repeater 1'],
|
||||
value: 45),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Repeater transform scale',
|
||||
ValueDelegate.transformScale(
|
||||
['Shape Layer 1', 'Repeater Shape', 'Repeater 1'],
|
||||
value: Offset(2, 2)),
|
||||
);
|
||||
|
||||
testGolden('Time remapping', ValueDelegate.timeRemap(['Circle 1'], value: 1),
|
||||
progress: 0.1);
|
||||
|
||||
testGolden(
|
||||
'Color Filter',
|
||||
ValueDelegate.colorFilter(['**'],
|
||||
value: ColorFilter.mode(Colors.green, BlendMode.srcATop)),
|
||||
);
|
||||
|
||||
testGolden(
|
||||
'Null Color Filter',
|
||||
ValueDelegate.colorFilter(['**'], value: null),
|
||||
);
|
||||
|
||||
for (var progress in [0.0, 0.5, 1.0]) {
|
||||
testGolden(
|
||||
'Opacity interpolation ($progress)',
|
||||
ValueDelegate.transformOpacity(['Shape Layer 1', 'Rectangle'],
|
||||
callback: (frameInfo) => lerpDouble(
|
||||
10, 100, Curves.linear.transform(frameInfo.overallProgress))
|
||||
.round()),
|
||||
progress: progress);
|
||||
}
|
||||
}
|
29
example/test/dynamic_text_test.dart
Normal file
@ -0,0 +1,29 @@
|
||||
import 'dart:io';
|
||||
import 'dart:ui';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lottie/lottie.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Dynamic test', (tester) async {
|
||||
var composition = await LottieComposition.fromBytes(
|
||||
File('assets/Tests/DynamicText.json').readAsBytesSync());
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Lottie(
|
||||
composition: composition,
|
||||
animate: false,
|
||||
delegates: LottieDelegates(
|
||||
text: (input) => '🔥c️🔥👮🏿🔥',
|
||||
textStyle: (font) => TextStyle(
|
||||
fontFamily: 'Roboto', fontFamilyFallback: ['Noto Emoji']),
|
||||
values: []),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
find.byType(Lottie), matchesGoldenFile('goldens/dynamic_text.png'));
|
||||
});
|
||||
}
|
7
example/test/flutter_test_config.dart
Normal file
@ -0,0 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:golden_toolkit/golden_toolkit.dart';
|
||||
|
||||
Future<void> main(FutureOr<void> Function() testMain) async {
|
||||
await loadAppFonts();
|
||||
return testMain();
|
||||
}
|
BIN
example/test/goldens/dynamic/color_filter.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
example/test/goldens/dynamic/ellipse_position.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/ellipse_size.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
example/test/goldens/dynamic/fill_color_green.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/fill_color_yellow.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/fill_opacity.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/null_color_filter.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/opacity_interpolation_00.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/opacity_interpolation_05.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/opacity_interpolation_10.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/polygon_points.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
example/test/goldens/dynamic/polygon_position.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/polygon_radius.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
example/test/goldens/dynamic/polygon_rotation.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/polygon_roundedness.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
example/test/goldens/dynamic/rectangle_corner_roundedness.png
Normal file
After Width: | Height: | Size: 42 KiB |
BIN
example/test/goldens/dynamic/rectangle_position.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/rectangle_size.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/repeater_transform_end_opacity.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/repeater_transform_position.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/repeater_transform_rotation.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/repeater_transform_scale.png
Normal file
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/star_inner_radius.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
example/test/goldens/dynamic/star_inner_roundedness.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/star_outer_radius.png
Normal file
After Width: | Height: | Size: 35 KiB |
BIN
example/test/goldens/dynamic/star_outer_roundedness.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
example/test/goldens/dynamic/star_points.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
example/test/goldens/dynamic/star_position.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/star_rotation.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/stroke_color.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/stroke_opacity.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/stroke_width.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/time_remapping.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/transform_anchor_point.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/transform_opacity.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/transform_position.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/transform_position_relative.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
example/test/goldens/dynamic/transform_rotation.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
example/test/goldens/dynamic/transform_scale.png
Normal file
After Width: | Height: | Size: 36 KiB |
BIN
example/test/goldens/dynamic_text.png
Normal file
After Width: | Height: | Size: 5.6 KiB |
BIN
example/test/goldens/dynamic_without_delegate.png
Normal file
After Width: | Height: | Size: 37 KiB |
@ -1,12 +1,9 @@
|
||||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility that Flutter provides. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:lottie_example/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {});
|
||||
testWidgets('Main sample', (tester) async {
|
||||
await tester.pumpWidget(App());
|
||||
await tester.pump();
|
||||
});
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
export 'src/composition.dart' show LottieComposition;
|
||||
export 'src/lottie.dart' show Lottie;
|
||||
export 'src/lottie_builder.dart' show LottieBuilder;
|
||||
export 'src/lottie_drawable.dart' show LottieDrawable;
|
||||
export 'src/lottie_delegates.dart' show LottieDelegates;
|
||||
export 'src/lottie_drawable.dart' show LottieDrawable, LottieFontStyle;
|
||||
export 'src/lottie_image_asset.dart' show LottieImageAsset;
|
||||
export 'src/raw_lottie.dart' show RawLottie;
|
||||
export 'src/value_delegate.dart' show ValueDelegate;
|
||||
|
@ -132,7 +132,7 @@ class GradientFillContent implements DrawingContent, KeyPathElementContent {
|
||||
}
|
||||
|
||||
LinearGradient _getLinearGradient() {
|
||||
var gradientHash = getGradientHash();
|
||||
var gradientHash = _getGradientHash();
|
||||
var gradient = _linearGradientCache[gradientHash];
|
||||
if (gradient != null) {
|
||||
return gradient;
|
||||
@ -152,7 +152,7 @@ class GradientFillContent implements DrawingContent, KeyPathElementContent {
|
||||
}
|
||||
|
||||
RadialGradient _getRadialGradient() {
|
||||
var gradientHash = getGradientHash();
|
||||
var gradientHash = _getGradientHash();
|
||||
var gradient = _radialGradientCache[gradientHash];
|
||||
if (gradient != null) {
|
||||
return gradient;
|
||||
@ -176,7 +176,7 @@ class GradientFillContent implements DrawingContent, KeyPathElementContent {
|
||||
return gradient;
|
||||
}
|
||||
|
||||
int getGradientHash() {
|
||||
int _getGradientHash() {
|
||||
var startPointProgress =
|
||||
(_startPointAnimation.progress * _cacheSteps).round();
|
||||
var endPointProgress = (_endPointAnimation.progress * _cacheSteps).round();
|
||||
|
@ -1,32 +0,0 @@
|
||||
import '../../value/keyframe.dart';
|
||||
import '../../value/scale_xy.dart';
|
||||
import 'keyframe_animation.dart';
|
||||
|
||||
class ScaleKeyframeAnimation extends KeyframeAnimation<ScaleXY> {
|
||||
ScaleKeyframeAnimation(List<Keyframe<ScaleXY>> keyframes) : super(keyframes);
|
||||
|
||||
@override
|
||||
ScaleXY getValue(Keyframe<ScaleXY> keyframe, double keyframeProgress) {
|
||||
if (keyframe.startValue == null || keyframe.endValue == null) {
|
||||
throw StateError('Missing values for keyframe.');
|
||||
}
|
||||
var startTransform = keyframe.startValue;
|
||||
var endTransform = keyframe.endValue;
|
||||
|
||||
if (valueCallback != null) {
|
||||
var value = valueCallback.getValueInternal(
|
||||
keyframe.startFrame,
|
||||
keyframe.endFrame,
|
||||
startTransform,
|
||||
endTransform,
|
||||
keyframeProgress,
|
||||
getLinearCurrentKeyframeProgress(),
|
||||
progress);
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
return ScaleXY.lerp(startTransform, endTransform, keyframeProgress);
|
||||
}
|
||||
}
|
@ -7,7 +7,6 @@ import '../../model/layer/base_layer.dart';
|
||||
import '../../utils.dart';
|
||||
import '../../value/keyframe.dart';
|
||||
import '../../value/lottie_value_callback.dart';
|
||||
import '../../value/scale_xy.dart';
|
||||
import 'base_keyframe_animation.dart';
|
||||
import 'double_keyframe_animation.dart';
|
||||
import 'value_callback_keyframe_animation.dart';
|
||||
@ -37,7 +36,7 @@ class TransformKeyframeAnimation {
|
||||
|
||||
BaseKeyframeAnimation<Offset, Offset> /*?*/ _anchorPoint;
|
||||
BaseKeyframeAnimation<dynamic, Offset> /*?*/ _position;
|
||||
BaseKeyframeAnimation<ScaleXY, ScaleXY> /*?*/ _scale;
|
||||
BaseKeyframeAnimation<Offset, Offset> /*?*/ _scale;
|
||||
BaseKeyframeAnimation<double, double> /*?*/ _rotation;
|
||||
DoubleKeyframeAnimation /*?*/ _skew;
|
||||
DoubleKeyframeAnimation /*?*/ _skewAngle;
|
||||
@ -141,8 +140,8 @@ class TransformKeyframeAnimation {
|
||||
|
||||
if (_scale != null) {
|
||||
final scale = _scale.value;
|
||||
if (scale.x != 1 || scale.y != 1) {
|
||||
_matrix.scale(scale.x, scale.y);
|
||||
if (scale.dx != 1 || scale.dy != 1) {
|
||||
_matrix.scale(scale.dx, scale.dy);
|
||||
}
|
||||
}
|
||||
|
||||
@ -169,7 +168,7 @@ class TransformKeyframeAnimation {
|
||||
}
|
||||
|
||||
if (scale != null) {
|
||||
_matrix.scale(scale.x, scale.y);
|
||||
_matrix.scale(scale.dx, scale.dy);
|
||||
}
|
||||
|
||||
if (rotation != null) {
|
||||
@ -200,9 +199,9 @@ class TransformKeyframeAnimation {
|
||||
} else if (property == LottieProperty.transformScale) {
|
||||
if (_scale == null) {
|
||||
_scale = ValueCallbackKeyframeAnimation(
|
||||
callback as LottieValueCallback<ScaleXY>, ScaleXY.one());
|
||||
callback as LottieValueCallback<Offset>, Offset(1, 1));
|
||||
} else {
|
||||
_scale.setValueCallback(callback as LottieValueCallback<ScaleXY>);
|
||||
_scale.setValueCallback(callback as LottieValueCallback<Offset>);
|
||||
}
|
||||
} else if (property == LottieProperty.transformRotation) {
|
||||
if (_rotation == null) {
|
||||
|
@ -34,7 +34,8 @@ class ValueCallbackKeyframeAnimation<K, A> extends BaseKeyframeAnimation<K, A> {
|
||||
@override
|
||||
A get value {
|
||||
return valueCallback.getValueInternal(0.0, 0.0, valueCallbackValue,
|
||||
valueCallbackValue, progress, progress, progress);
|
||||
valueCallbackValue, progress, progress, progress) ??
|
||||
valueCallbackValue;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -23,16 +23,19 @@ class Lottie extends StatefulWidget {
|
||||
bool animate,
|
||||
bool repeat,
|
||||
bool reverse,
|
||||
this.delegates,
|
||||
}) : animate = animate ?? true,
|
||||
reverse = reverse ?? false,
|
||||
repeat = repeat ?? true,
|
||||
super(key: key);
|
||||
|
||||
/// Creates a widget that displays an [LottieComposition] obtained from an [AssetBundle].
|
||||
static LottieBuilder asset(String name,
|
||||
{Animation<double> controller,
|
||||
bool animate,
|
||||
bool repeat,
|
||||
bool reverse,
|
||||
LottieDelegates delegates,
|
||||
void Function(LottieComposition) onLoaded,
|
||||
LottieImageProviderFactory imageProviderFactory,
|
||||
Key key,
|
||||
@ -49,6 +52,7 @@ class Lottie extends StatefulWidget {
|
||||
animate: animate,
|
||||
repeat: repeat,
|
||||
reverse: reverse,
|
||||
delegates: delegates,
|
||||
imageProviderFactory: imageProviderFactory,
|
||||
onLoaded: onLoaded,
|
||||
key: key,
|
||||
@ -61,12 +65,14 @@ class Lottie extends StatefulWidget {
|
||||
package: package,
|
||||
);
|
||||
|
||||
/// Creates a widget that displays an [LottieComposition] obtained from a [File].
|
||||
static LottieBuilder file(
|
||||
File file, {
|
||||
Animation<double> controller,
|
||||
bool animate,
|
||||
bool repeat,
|
||||
bool reverse,
|
||||
LottieDelegates delegates,
|
||||
LottieImageProviderFactory imageProviderFactory,
|
||||
void Function(LottieComposition) onLoaded,
|
||||
Key key,
|
||||
@ -82,6 +88,7 @@ class Lottie extends StatefulWidget {
|
||||
animate: animate,
|
||||
repeat: repeat,
|
||||
reverse: reverse,
|
||||
delegates: delegates,
|
||||
imageProviderFactory: imageProviderFactory,
|
||||
onLoaded: onLoaded,
|
||||
key: key,
|
||||
@ -92,12 +99,14 @@ class Lottie extends StatefulWidget {
|
||||
alignment: alignment,
|
||||
);
|
||||
|
||||
/// Creates a widget that displays an [LottieComposition] obtained from a [Uint8List].
|
||||
static LottieBuilder memory(
|
||||
Uint8List bytes, {
|
||||
Animation<double> controller,
|
||||
bool animate,
|
||||
bool repeat,
|
||||
bool reverse,
|
||||
LottieDelegates delegates,
|
||||
LottieImageProviderFactory imageProviderFactory,
|
||||
void Function(LottieComposition) onLoaded,
|
||||
Key key,
|
||||
@ -113,6 +122,7 @@ class Lottie extends StatefulWidget {
|
||||
animate: animate,
|
||||
repeat: repeat,
|
||||
reverse: reverse,
|
||||
delegates: delegates,
|
||||
imageProviderFactory: imageProviderFactory,
|
||||
onLoaded: onLoaded,
|
||||
key: key,
|
||||
@ -123,12 +133,14 @@ class Lottie extends StatefulWidget {
|
||||
alignment: alignment,
|
||||
);
|
||||
|
||||
/// Creates a widget that displays an [LottieComposition] obtained from the network.
|
||||
static LottieBuilder network(
|
||||
String url, {
|
||||
Animation<double> controller,
|
||||
bool animate,
|
||||
bool repeat,
|
||||
bool reverse,
|
||||
LottieDelegates delegates,
|
||||
LottieImageProviderFactory imageProviderFactory,
|
||||
void Function(LottieComposition) onLoaded,
|
||||
Key key,
|
||||
@ -144,6 +156,7 @@ class Lottie extends StatefulWidget {
|
||||
animate: animate,
|
||||
repeat: repeat,
|
||||
reverse: reverse,
|
||||
delegates: delegates,
|
||||
imageProviderFactory: imageProviderFactory,
|
||||
onLoaded: onLoaded,
|
||||
key: key,
|
||||
@ -180,13 +193,13 @@ class Lottie extends StatefulWidget {
|
||||
/// The property has no effect if [animate] is false, [repeat] is false or [controller] is not null.
|
||||
final bool reverse;
|
||||
|
||||
/// If non-null, require the Lottie composition to have this width.
|
||||
/// If non-null, requires the composition to have this width.
|
||||
///
|
||||
/// If null, the composition will pick a size that best preserves its intrinsic
|
||||
/// aspect ratio.
|
||||
final double width;
|
||||
|
||||
/// If non-null, require the Lottie composition to have this height.
|
||||
/// If non-null, require the composition to have this height.
|
||||
///
|
||||
/// If null, the composition will pick a size that best preserves its intrinsic
|
||||
/// aspect ratio.
|
||||
@ -215,6 +228,13 @@ class Lottie extends StatefulWidget {
|
||||
/// relative to text direction.
|
||||
final AlignmentGeometry alignment;
|
||||
|
||||
/// A group of options to further customize the lottie animation.
|
||||
/// - A [text] delegate to dynamically change some text displayed in the animation
|
||||
/// - A value callback to change the properties of the animation at runtime.
|
||||
/// - A text style factory to map between a font family specified in the animation
|
||||
/// and the font family in your assets.
|
||||
final LottieDelegates delegates;
|
||||
|
||||
@override
|
||||
_LottieState createState() => _LottieState();
|
||||
}
|
||||
@ -268,6 +288,7 @@ class _LottieState extends State<Lottie> with TickerProviderStateMixin {
|
||||
animation: _progressAnimation,
|
||||
builder: (context, _) => RawLottie(
|
||||
composition: widget.composition,
|
||||
delegates: widget.delegates,
|
||||
progress: _progressAnimation.value,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
|
@ -6,7 +6,6 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import '../lottie.dart';
|
||||
import 'lottie.dart';
|
||||
import 'lottie_drawable.dart';
|
||||
import 'providers/asset_provider.dart';
|
||||
import 'providers/file_provider.dart';
|
||||
import 'providers/load_image.dart';
|
||||
@ -40,6 +39,7 @@ class LottieBuilder extends StatefulWidget {
|
||||
this.animate,
|
||||
this.reverse,
|
||||
this.repeat,
|
||||
this.delegates,
|
||||
this.onLoaded,
|
||||
this.frameBuilder,
|
||||
this.width,
|
||||
@ -49,7 +49,7 @@ class LottieBuilder extends StatefulWidget {
|
||||
}) : assert(lottie != null),
|
||||
super(key: key);
|
||||
|
||||
/// Creates a widget that displays an [LottieStream] obtained from the network.
|
||||
/// Creates a widget that displays an [LottieComposition] obtained from the network.
|
||||
LottieBuilder.network(
|
||||
String src, {
|
||||
Map<String, String> headers,
|
||||
@ -57,6 +57,7 @@ class LottieBuilder extends StatefulWidget {
|
||||
this.animate,
|
||||
this.reverse,
|
||||
this.repeat,
|
||||
this.delegates,
|
||||
LottieImageProviderFactory imageProviderFactory,
|
||||
this.onLoaded,
|
||||
Key key,
|
||||
@ -69,13 +70,11 @@ class LottieBuilder extends StatefulWidget {
|
||||
headers: headers, imageProviderFactory: imageProviderFactory),
|
||||
super(key: key);
|
||||
|
||||
/// Creates a widget that displays an [ImageStream] obtained from a [File].
|
||||
///
|
||||
/// The [file], [scale], and [repeat] arguments must not be null.
|
||||
/// Creates a widget that displays an [LottieComposition] obtained from a [File].
|
||||
///
|
||||
/// Either the [width] and [height] arguments should be specified, or the
|
||||
/// widget should be placed in a context that sets tight layout constraints.
|
||||
/// Otherwise, the image dimensions will change as the image is loaded, which
|
||||
/// Otherwise, the image dimensions will change as the animation is loaded, which
|
||||
/// will result in ugly layout changes.
|
||||
///
|
||||
/// On Android, this may require the
|
||||
@ -87,6 +86,7 @@ class LottieBuilder extends StatefulWidget {
|
||||
this.animate,
|
||||
this.reverse,
|
||||
this.repeat,
|
||||
this.delegates,
|
||||
LottieImageProviderFactory imageProviderFactory,
|
||||
this.onLoaded,
|
||||
Key key,
|
||||
@ -98,12 +98,14 @@ class LottieBuilder extends StatefulWidget {
|
||||
}) : lottie = FileLottie(file, imageProviderFactory: imageProviderFactory),
|
||||
super(key: key);
|
||||
|
||||
/// Creates a widget that displays an [LottieComposition] obtained from an [AssetBundle].
|
||||
LottieBuilder.asset(
|
||||
String name, {
|
||||
this.controller,
|
||||
this.animate,
|
||||
this.reverse,
|
||||
this.repeat,
|
||||
this.delegates,
|
||||
LottieImageProviderFactory imageProviderFactory,
|
||||
this.onLoaded,
|
||||
Key key,
|
||||
@ -120,13 +122,14 @@ class LottieBuilder extends StatefulWidget {
|
||||
imageProviderFactory: imageProviderFactory),
|
||||
super(key: key);
|
||||
|
||||
/// Creates a widget that displays an [LottieDrawable] obtained from a [Uint8List].
|
||||
/// Creates a widget that displays an [LottieComposition] obtained from a [Uint8List].
|
||||
LottieBuilder.memory(
|
||||
Uint8List bytes, {
|
||||
this.controller,
|
||||
this.animate,
|
||||
this.reverse,
|
||||
this.repeat,
|
||||
this.delegates,
|
||||
LottieImageProviderFactory imageProviderFactory,
|
||||
this.onLoaded,
|
||||
Key key,
|
||||
@ -139,7 +142,7 @@ class LottieBuilder extends StatefulWidget {
|
||||
MemoryLottie(bytes, imageProviderFactory: imageProviderFactory),
|
||||
super(key: key);
|
||||
|
||||
/// The lottie animation to display.
|
||||
/// The lottie animation to load.
|
||||
/// Example of providers: [AssetLottie], [NetworkLottie], [FileLottie], [MemoryLottie]
|
||||
final LottieProvider lottie;
|
||||
|
||||
@ -170,6 +173,13 @@ class LottieBuilder extends StatefulWidget {
|
||||
/// The property has no effect if [animate] is false, [repeat] is false or [controller] is not null.
|
||||
final bool reverse;
|
||||
|
||||
/// A group of options to further customize the lottie animation.
|
||||
/// - A [text] delegate to dynamically change some text displayed in the animation
|
||||
/// - A value callback to change the properties of the animation at runtime.
|
||||
/// - A text style factory to map between a font family specified in the animation
|
||||
/// and the font family in your assets.
|
||||
final LottieDelegates delegates;
|
||||
|
||||
/// A builder function responsible for creating the widget that represents
|
||||
/// this lottie animation.
|
||||
///
|
||||
@ -368,6 +378,7 @@ class _LottieBuilderState extends State<LottieBuilder> {
|
||||
animate: widget.animate,
|
||||
reverse: widget.reverse,
|
||||
repeat: widget.repeat,
|
||||
delegates: widget.delegates,
|
||||
width: widget.width,
|
||||
height: widget.height,
|
||||
fit: widget.fit,
|
||||
|
64
lib/src/lottie_delegates.dart
Normal file
@ -0,0 +1,64 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'lottie_drawable.dart';
|
||||
import 'value_delegate.dart';
|
||||
|
||||
// TODO(xha): recognize style Bold, Medium, Regular, SemiBold, etc...
|
||||
TextStyle _defaultTextStyleDelegate(LottieFontStyle font) =>
|
||||
TextStyle(fontFamily: font.fontFamily);
|
||||
|
||||
@immutable
|
||||
class LottieDelegates {
|
||||
/// Specify a callback to dynamically changes the text displayed in the lottie
|
||||
/// animation.
|
||||
/// For instance, this is useful when you want to translate the text in the animation.
|
||||
final String Function(String) /*?*/ text;
|
||||
|
||||
/// A callback to map between a font family specified in the json animation
|
||||
/// with the font family in your assets.
|
||||
/// This is useful either if:
|
||||
/// - the name of the font in your asset doesn't match the one in the json file.
|
||||
/// - you want to use an other font than the one declared in the json
|
||||
///
|
||||
/// If the callback is null, the font family from the json is used as it.
|
||||
///
|
||||
/// Given an object containing the font family and style specified in the json
|
||||
/// return a configured `TextStyle` that will be used as the base style when
|
||||
/// painting the text.
|
||||
final TextStyle Function(LottieFontStyle) textStyle;
|
||||
|
||||
/// A list of value delegates to dynamically modify the animation
|
||||
/// properties at runtime.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// Lottie.asset(
|
||||
/// 'lottiefile.json',
|
||||
/// delegates: LottieDelegates(
|
||||
/// value: [
|
||||
/// ValueDelegate.color(['lake', 'fill'], value: Colors.blue),
|
||||
/// ValueDelegate.opacity(['**', 'fill'], callback: (frameInfo) => 0.5 * frameInfo.overallProgress),
|
||||
/// ],
|
||||
/// ),
|
||||
/// );
|
||||
/// ```
|
||||
final List<ValueDelegate> values;
|
||||
|
||||
//TODO(xha): imageDelegate to change the image to display?
|
||||
|
||||
LottieDelegates({
|
||||
this.text,
|
||||
TextStyle Function(LottieFontStyle) textStyle,
|
||||
this.values,
|
||||
}) : textStyle = textStyle ?? _defaultTextStyleDelegate;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is LottieDelegates &&
|
||||
text == other.text &&
|
||||
textStyle == other.textStyle &&
|
||||
values == other.values;
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(text, textStyle, values);
|
||||
}
|
@ -1,42 +1,65 @@
|
||||
import 'dart:ui' as ui;
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
import 'composition.dart';
|
||||
import 'lottie_delegates.dart';
|
||||
import 'model/key_path.dart';
|
||||
import 'model/layer/composition_layer.dart';
|
||||
import 'parser/layer_parser.dart';
|
||||
import 'text_delegate.dart';
|
||||
import 'value_delegate.dart';
|
||||
|
||||
class LottieDrawable {
|
||||
final LottieComposition composition;
|
||||
final _matrix = Matrix4.identity();
|
||||
CompositionLayer _compositionLayer;
|
||||
final Size size;
|
||||
TextDelegate /*?*/ textDelegate;
|
||||
LottieDelegates _delegates;
|
||||
bool _isDirty = true;
|
||||
|
||||
LottieDrawable(this.composition, {this.textDelegate})
|
||||
LottieDrawable(this.composition, {LottieDelegates delegates})
|
||||
: size = Size(composition.bounds.width.toDouble(),
|
||||
composition.bounds.height.toDouble()) {
|
||||
this.delegates = delegates;
|
||||
_compositionLayer = CompositionLayer(
|
||||
this, LayerParser.parse(composition), composition.layers, composition);
|
||||
}
|
||||
|
||||
CompositionLayer get compositionLayer => _compositionLayer;
|
||||
|
||||
/// Sets whether to apply opacity to the each layer instead of shape.
|
||||
///
|
||||
/// Opacity is normally applied directly to a shape. In cases where translucent shapes overlap, applying opacity to a layer will be more accurate
|
||||
/// at the expense of performance.
|
||||
/// Opacity is normally applied directly to a shape. In cases where translucent
|
||||
/// shapes overlap, applying opacity to a layer will be more accurate at the
|
||||
/// expense of performance.
|
||||
///
|
||||
/// The default value is false.
|
||||
///
|
||||
/// Note: This process is very expensive. The performance impact will be reduced when hardware acceleration is enabled.
|
||||
/// Note: This process is very expensive. The performance impact will be reduced
|
||||
/// when hardware acceleration is enabled.
|
||||
bool isApplyingOpacityToLayersEnabled = false;
|
||||
|
||||
void invalidateSelf() {}
|
||||
void invalidateSelf() {
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
double get progress => _progress;
|
||||
double _progress = 0.0;
|
||||
bool setProgress(double value) {
|
||||
_isDirty = false;
|
||||
_progress = value;
|
||||
_compositionLayer.setProgress(value);
|
||||
return _isDirty;
|
||||
}
|
||||
|
||||
LottieDelegates get delegates => _delegates;
|
||||
set delegates(LottieDelegates delegates) {
|
||||
delegates ??= LottieDelegates();
|
||||
if (_delegates != delegates) {
|
||||
_delegates = delegates;
|
||||
_updateValueDelegates(delegates.values);
|
||||
}
|
||||
}
|
||||
|
||||
bool get useTextGlyphs {
|
||||
return textDelegate == null && composition.characters.isNotEmpty;
|
||||
return delegates.text == null && composition.characters.isNotEmpty;
|
||||
}
|
||||
|
||||
ui.Image getImageAsset(String ref) {
|
||||
@ -49,13 +72,54 @@ class LottieDrawable {
|
||||
}
|
||||
|
||||
TextStyle getTextStyle(String font, String style) {
|
||||
//TODO(xha): allow the user to map Font in the animation with FontFamily loaded for flutter
|
||||
// Support to inherit TextStyle from DefaultTextStyle applied for the Lottie wiget
|
||||
return TextStyle(fontFamily: font);
|
||||
return _delegates
|
||||
.textStyle(LottieFontStyle(fontFamily: font, style: style));
|
||||
}
|
||||
|
||||
void draw(ui.Canvas canvas, ui.Rect rect,
|
||||
{@required double progress, BoxFit fit, Alignment alignment}) {
|
||||
List<ValueDelegate> _valueDelegates = <ValueDelegate>[];
|
||||
void _updateValueDelegates(List<ValueDelegate> newDelegates) {
|
||||
if (identical(_valueDelegates, newDelegates)) return;
|
||||
|
||||
newDelegates ??= const [];
|
||||
|
||||
var delegates = <ValueDelegate>[];
|
||||
|
||||
for (var newDelegate in newDelegates) {
|
||||
var existingDelegate = _valueDelegates
|
||||
.firstWhere((f) => f.isSameProperty(newDelegate), orElse: () => null);
|
||||
if (existingDelegate != null) {
|
||||
var resolved = internalResolved(existingDelegate);
|
||||
resolved.updateDelegate(newDelegate);
|
||||
delegates.add(existingDelegate);
|
||||
} else {
|
||||
var keyPaths = _resolveKeyPath(KeyPath(newDelegate.keyPath));
|
||||
var resolvedValueDelegate = internalResolve(newDelegate, keyPaths);
|
||||
resolvedValueDelegate.addValueCallback(this);
|
||||
delegates.add(newDelegate);
|
||||
}
|
||||
}
|
||||
for (var oldDelegate in _valueDelegates) {
|
||||
if (delegates.every((c) => !c.isSameProperty(oldDelegate))) {
|
||||
var resolved = internalResolved(oldDelegate);
|
||||
resolved.clear();
|
||||
}
|
||||
}
|
||||
_valueDelegates = delegates;
|
||||
}
|
||||
|
||||
/// Takes a {@link KeyPath}, potentially with wildcards or globstars and resolve it to a list of
|
||||
/// zero or more actual {@link KeyPath Keypaths} that exist in the current animation.
|
||||
/// <p>
|
||||
/// If you want to set value callbacks for any of these values, it is recommend to use the
|
||||
/// returned {@link KeyPath} objects because they will be internally resolved to their content
|
||||
/// and won't trigger a tree walk of the animation contents when applied.
|
||||
List<KeyPath> _resolveKeyPath(KeyPath keyPath) {
|
||||
var keyPaths = <KeyPath>[];
|
||||
_compositionLayer.resolveKeyPath(keyPath, 0, keyPaths, KeyPath([]));
|
||||
return keyPaths;
|
||||
}
|
||||
|
||||
void draw(ui.Canvas canvas, ui.Rect rect, {BoxFit fit, Alignment alignment}) {
|
||||
if (rect.isEmpty) {
|
||||
return;
|
||||
}
|
||||
@ -79,9 +143,12 @@ class LottieDrawable {
|
||||
_matrix.translate(destinationRect.left, destinationRect.top);
|
||||
_matrix.scale(destinationRect.size.width / sourceRect.width,
|
||||
destinationRect.size.height / sourceRect.height);
|
||||
progress ??= 0;
|
||||
_compositionLayer
|
||||
..setProgress(progress)
|
||||
..draw(canvas, rect.size, _matrix, parentAlpha: 255);
|
||||
_compositionLayer.draw(canvas, rect.size, _matrix, parentAlpha: 255);
|
||||
}
|
||||
}
|
||||
|
||||
class LottieFontStyle {
|
||||
final String fontFamily, style;
|
||||
|
||||
LottieFontStyle({this.fontFamily, this.style});
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'dart:ui';
|
||||
import 'value/scale_xy.dart';
|
||||
|
||||
/// Property values are the same type as the generic type of their corresponding
|
||||
/// {@link LottieValueCallback}. With this, we can use generics to maintain type safety
|
||||
@ -62,69 +61,69 @@ abstract class LottieProperty {
|
||||
static final int opacity = 4;
|
||||
|
||||
/// In Px */
|
||||
static final Offset transformAnchorPoint = Offset.zero;
|
||||
static final Offset transformAnchorPoint = Offset(5, 5);
|
||||
|
||||
/// In Px */
|
||||
static final Offset transformPosition = Offset.zero;
|
||||
static final Offset transformPosition = Offset(6, 6);
|
||||
|
||||
/// In Px */
|
||||
static final Offset ellipseSize = Offset.zero;
|
||||
static final Offset ellipseSize = Offset(7, 7);
|
||||
|
||||
/// In Px */
|
||||
static final Offset rectangleSize = Offset.zero;
|
||||
static final Offset rectangleSize = Offset(8, 8);
|
||||
|
||||
/// In degrees */
|
||||
static final double cornerRadius = 0.0;
|
||||
static final double cornerRadius = 9.0;
|
||||
|
||||
/// In Px */
|
||||
static final Offset position = Offset.zero;
|
||||
static final ScaleXY transformScale = ScaleXY.one();
|
||||
static final Offset position = Offset(10, 10);
|
||||
static final Offset transformScale = Offset(11, 11);
|
||||
|
||||
/// In degrees */
|
||||
static final double transformRotation = 1.0;
|
||||
static final double transformRotation = 12.0;
|
||||
|
||||
/// 0-85 */
|
||||
static final double transformSkew = 0.0;
|
||||
static final double transformSkew = 13.0;
|
||||
|
||||
/// In degrees */
|
||||
static final double transformSkewAngle = 0.0;
|
||||
static final double transformSkewAngle = 14.0;
|
||||
|
||||
/// In Px */
|
||||
static final double strokeWidth = 2.0;
|
||||
static final double textTracking = 3.0;
|
||||
static final double repeaterCopies = 4.0;
|
||||
static final double repeaterOffset = 5.0;
|
||||
static final double polystarPoints = 6.0;
|
||||
static final double strokeWidth = 15.0;
|
||||
static final double textTracking = 16.0;
|
||||
static final double repeaterCopies = 17.0;
|
||||
static final double repeaterOffset = 18.0;
|
||||
static final double polystarPoints = 19.0;
|
||||
|
||||
/// In degrees */
|
||||
static final double polystarRotation = 7.0;
|
||||
static final double polystarRotation = 20.0;
|
||||
|
||||
/// In Px */
|
||||
static final double polystarInnerRadius = 8.0;
|
||||
static final double polystarInnerRadius = 21.0;
|
||||
|
||||
/// In Px */
|
||||
static final double polystarOuterRadius = 9.0;
|
||||
static final double polystarOuterRadius = 22.0;
|
||||
|
||||
/// [0,100] */
|
||||
static final double polystarInnerRoundedness = 10.0;
|
||||
static final double polystarInnerRoundedness = 23.0;
|
||||
|
||||
/// [0,100] */
|
||||
static final double polystarOuterRoundedness = 11.0;
|
||||
static final double polystarOuterRoundedness = 24.0;
|
||||
|
||||
/// [0,100] */
|
||||
static final double transformStartOpacity = 12.0;
|
||||
static final double transformStartOpacity = 25.0;
|
||||
|
||||
/// [0,100] */
|
||||
static final double transformEndOpacity = 12.1;
|
||||
static final double transformEndOpacity = 26.0;
|
||||
|
||||
/// The time value in seconds */
|
||||
static final double timeRemap = 13.0;
|
||||
static final double timeRemap = 27.0;
|
||||
|
||||
/// In Dp */
|
||||
static final double textSize = 14.0;
|
||||
/// In Dp
|
||||
static final double textSize = 28.0;
|
||||
|
||||
static final ColorFilter colorFilter =
|
||||
ColorFilter.mode(Color(0xFF000000), BlendMode.dst);
|
||||
|
||||
static final List<Color> gradientColor = const [];
|
||||
static final List<Color> gradientColor = [];
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
import 'dart:ui';
|
||||
import '../../animation/keyframe/base_keyframe_animation.dart';
|
||||
import '../../animation/keyframe/scale_keyframe_animation.dart';
|
||||
import '../../animation/keyframe/point_keyframe_animation.dart';
|
||||
import '../../value/keyframe.dart';
|
||||
import '../../value/scale_xy.dart';
|
||||
import 'base_animatable_value.dart';
|
||||
|
||||
class AnimatableScaleValue extends BaseAnimatableValue<ScaleXY, ScaleXY> {
|
||||
AnimatableScaleValue.one() : this(ScaleXY.one());
|
||||
class AnimatableScaleValue extends BaseAnimatableValue<Offset, Offset> {
|
||||
AnimatableScaleValue.one() : this(Offset(1, 1));
|
||||
|
||||
AnimatableScaleValue(ScaleXY value) : super.fromValue(value);
|
||||
AnimatableScaleValue(Offset value) : super.fromValue(value);
|
||||
|
||||
AnimatableScaleValue.fromKeyframes(List<Keyframe<ScaleXY>> keyframes)
|
||||
AnimatableScaleValue.fromKeyframes(List<Keyframe<Offset>> keyframes)
|
||||
: super.fromKeyframes(keyframes);
|
||||
|
||||
@override
|
||||
BaseKeyframeAnimation<ScaleXY, ScaleXY> createAnimation() {
|
||||
return ScaleKeyframeAnimation(keyframes);
|
||||
BaseKeyframeAnimation<Offset, Offset> createAnimation() {
|
||||
return PointKeyframeAnimation(keyframes);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +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';
|
||||
@ -22,7 +23,6 @@ class TextLayer extends BaseLayer {
|
||||
final _matrix = Matrix4.identity();
|
||||
final _fillPaint = Paint()..style = PaintingStyle.fill;
|
||||
final _strokePaint = Paint()..style = PaintingStyle.stroke;
|
||||
TextStyle _textStyle;
|
||||
final _contentsForCharacter = <FontCharacter, List<ContentGroup>>{};
|
||||
final TextKeyframeAnimation _textAnimation;
|
||||
final LottieComposition _composition;
|
||||
@ -227,14 +227,14 @@ class TextLayer extends BaseLayer {
|
||||
void _drawTextWithFont(DocumentData documentData, Font font,
|
||||
Matrix4 parentMatrix, Canvas canvas) {
|
||||
var parentScale = parentMatrix.getScale();
|
||||
_textStyle = lottieDrawable.getTextStyle(font.family, font.style);
|
||||
if (_textStyle == null) {
|
||||
var textStyle = lottieDrawable.getTextStyle(font.family, font.style);
|
||||
if (textStyle == null) {
|
||||
return;
|
||||
}
|
||||
var text = documentData.text;
|
||||
var textDelegate = lottieDrawable.textDelegate;
|
||||
var textDelegate = lottieDrawable.delegates.text;
|
||||
if (textDelegate != null) {
|
||||
text = textDelegate.getTextInternal(text);
|
||||
text = textDelegate(text);
|
||||
}
|
||||
double textSize;
|
||||
if (_textSizeCallbackAnimation != null) {
|
||||
@ -244,8 +244,8 @@ class TextLayer extends BaseLayer {
|
||||
} else {
|
||||
textSize = documentData.size;
|
||||
}
|
||||
_textStyle =
|
||||
_textStyle.copyWith(fontSize: textSize * window.devicePixelRatio);
|
||||
textStyle =
|
||||
textStyle.copyWith(fontSize: textSize * window.devicePixelRatio);
|
||||
|
||||
// Line height
|
||||
var lineHeight = documentData.lineHeight * window.devicePixelRatio;
|
||||
@ -256,7 +256,7 @@ class TextLayer extends BaseLayer {
|
||||
for (var l = 0; l < textLineCount; l++) {
|
||||
var textLine = textLines[l];
|
||||
var textPainter = TextPainter(
|
||||
text: TextSpan(text: textLine, style: _textStyle),
|
||||
text: TextSpan(text: textLine, style: textStyle),
|
||||
textDirection: _textDirection);
|
||||
textPainter.layout();
|
||||
var textLineWidth = textPainter.width;
|
||||
@ -270,7 +270,7 @@ class TextLayer extends BaseLayer {
|
||||
canvas.translate(0, translateY);
|
||||
|
||||
// Draw each line
|
||||
_drawFontTextLine(textLine, documentData, canvas, parentScale);
|
||||
_drawFontTextLine(textLine, textStyle, documentData, canvas, parentScale);
|
||||
|
||||
// Reset canvas
|
||||
canvas.transform(parentMatrix.storage);
|
||||
@ -284,14 +284,13 @@ class TextLayer extends BaseLayer {
|
||||
return textLinesArray;
|
||||
}
|
||||
|
||||
void _drawFontTextLine(String text, DocumentData documentData, Canvas canvas,
|
||||
double parentScale) {
|
||||
for (var i = 0; i < text.length;) {
|
||||
var charString = _codePointToString(text, i);
|
||||
i += charString.length;
|
||||
_drawCharacterFromFont(charString, documentData, canvas);
|
||||
void _drawFontTextLine(String text, TextStyle textStyle,
|
||||
DocumentData documentData, Canvas canvas, double parentScale) {
|
||||
for (var char in text.characters) {
|
||||
var charString = char;
|
||||
_drawCharacterFromFont(charString, textStyle, documentData, canvas);
|
||||
var textPainter = TextPainter(
|
||||
text: TextSpan(text: charString, style: _textStyle),
|
||||
text: TextSpan(text: charString, style: textStyle),
|
||||
textDirection: _textDirection);
|
||||
textPainter.layout();
|
||||
var charWidth = textPainter.width;
|
||||
@ -369,18 +368,19 @@ class TextLayer extends BaseLayer {
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
void _drawCharacterFromFont(
|
||||
String character, DocumentData documentData, Canvas canvas) {
|
||||
void _drawCharacterFromFont(String character, TextStyle textStyle,
|
||||
DocumentData documentData, Canvas canvas) {
|
||||
if (documentData.strokeOverFill) {
|
||||
_drawCharacter(character, _fillPaint, canvas);
|
||||
_drawCharacter(character, _strokePaint, canvas);
|
||||
_drawCharacter(character, textStyle, _fillPaint, canvas);
|
||||
_drawCharacter(character, textStyle, _strokePaint, canvas);
|
||||
} else {
|
||||
_drawCharacter(character, _strokePaint, canvas);
|
||||
_drawCharacter(character, _fillPaint, canvas);
|
||||
_drawCharacter(character, textStyle, _strokePaint, canvas);
|
||||
_drawCharacter(character, textStyle, _fillPaint, canvas);
|
||||
}
|
||||
}
|
||||
|
||||
void _drawCharacter(String character, Paint paint, Canvas canvas) {
|
||||
void _drawCharacter(
|
||||
String character, TextStyle textStyle, Paint paint, Canvas canvas) {
|
||||
if (paint.color.alpha == 0) {
|
||||
return;
|
||||
}
|
||||
@ -388,18 +388,17 @@ class TextLayer extends BaseLayer {
|
||||
return;
|
||||
}
|
||||
|
||||
TextStyle textStyle;
|
||||
if (paint.style == PaintingStyle.fill) {
|
||||
textStyle = _textStyle.copyWith(foreground: paint);
|
||||
textStyle = textStyle.copyWith(foreground: paint);
|
||||
} else if (paint.style == PaintingStyle.stroke) {
|
||||
textStyle = _textStyle.copyWith(background: paint);
|
||||
textStyle = textStyle.copyWith(background: paint);
|
||||
}
|
||||
var painter = TextPainter(
|
||||
text: TextSpan(text: character, style: textStyle),
|
||||
textDirection: _textDirection,
|
||||
);
|
||||
painter.layout();
|
||||
painter.paint(canvas, Offset(0, -_textStyle.fontSize));
|
||||
painter.paint(canvas, Offset(0, -textStyle.fontSize));
|
||||
}
|
||||
|
||||
List<ContentGroup> _getContentsForCharacter(FontCharacter character) {
|
||||
@ -417,11 +416,6 @@ class TextLayer extends BaseLayer {
|
||||
return contents;
|
||||
}
|
||||
|
||||
//TODO(xha): use package:character to correctly iterate over visual glyphs
|
||||
String _codePointToString(String text, int startIndex) {
|
||||
return text[startIndex];
|
||||
}
|
||||
|
||||
@override
|
||||
void addValueCallback<T>(T property, LottieValueCallback<T> /*?*/ callback) {
|
||||
super.addValueCallback(property, callback);
|
||||
|
@ -170,7 +170,8 @@ class AnimatableTransformParser {
|
||||
|
||||
static bool isScaleIdentity(AnimatableScaleValue scale) {
|
||||
return scale == null ||
|
||||
(scale.isStatic && scale.keyframes.first.startValue.equals(1.0, 1.0));
|
||||
(scale.isStatic &&
|
||||
scale.keyframes.first.startValue == Offset(1.0, 1.0));
|
||||
}
|
||||
|
||||
static bool isSkewIdentity(AnimatableDoubleValue skew) {
|
||||
|
@ -14,7 +14,7 @@ Color colorParser(JsonReader reader, {double scale}) {
|
||||
reader.endArray();
|
||||
}
|
||||
|
||||
if (r <= 1 && g <= 1 && b <= 11) {
|
||||
if (r <= 1 && g <= 1 && b <= 1) {
|
||||
r *= 255;
|
||||
g *= 255;
|
||||
b *= 255;
|
||||
|
@ -1,7 +1,7 @@
|
||||
import '../value/scale_xy.dart';
|
||||
import 'dart:ui';
|
||||
import 'moshi/json_reader.dart';
|
||||
|
||||
ScaleXY scaleXYParser(JsonReader reader, {double scale}) {
|
||||
Offset scaleXYParser(JsonReader reader, {double scale}) {
|
||||
var isArray = reader.peek() == Token.beginArray;
|
||||
if (isArray) {
|
||||
reader.beginArray();
|
||||
@ -14,5 +14,5 @@ ScaleXY scaleXYParser(JsonReader reader, {double scale}) {
|
||||
if (isArray) {
|
||||
reader.endArray();
|
||||
}
|
||||
return ScaleXY(sx / 100.0 * scale, sy / 100.0 * scale);
|
||||
return Offset(sx / 100.0 * scale, sy / 100.0 * scale);
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ class RawLottie extends LeafRenderObjectWidget {
|
||||
const RawLottie({
|
||||
Key key,
|
||||
this.composition,
|
||||
this.delegates,
|
||||
double progress,
|
||||
this.width,
|
||||
this.height,
|
||||
@ -24,6 +25,9 @@ class RawLottie extends LeafRenderObjectWidget {
|
||||
/// The Lottie composition to display.
|
||||
final LottieComposition composition;
|
||||
|
||||
/// Allows to modify the Lottie animation at runtime
|
||||
final LottieDelegates delegates;
|
||||
|
||||
/// The progress of the Lottie animation (between 0.0 and 1.0).
|
||||
final double progress;
|
||||
|
||||
@ -66,6 +70,7 @@ class RawLottie extends LeafRenderObjectWidget {
|
||||
RenderLottie createRenderObject(BuildContext context) {
|
||||
return RenderLottie(
|
||||
composition: composition,
|
||||
delegates: delegates,
|
||||
progress: progress,
|
||||
width: width,
|
||||
height: height,
|
||||
@ -77,8 +82,7 @@ class RawLottie extends LeafRenderObjectWidget {
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, RenderLottie renderObject) {
|
||||
renderObject
|
||||
..composition = composition
|
||||
..progress = progress
|
||||
..setComposition(composition, progress: progress, delegates: delegates)
|
||||
..width = width
|
||||
..height = height
|
||||
..alignment = alignment
|
||||
|
@ -1,4 +1,5 @@
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import '../lottie.dart';
|
||||
import 'lottie_drawable.dart';
|
||||
|
||||
@ -9,6 +10,7 @@ import 'lottie_drawable.dart';
|
||||
class RenderLottie extends RenderBox {
|
||||
RenderLottie({
|
||||
LottieComposition composition,
|
||||
LottieDelegates delegates,
|
||||
double progress = 0.0,
|
||||
double width,
|
||||
double height,
|
||||
@ -16,37 +18,49 @@ class RenderLottie extends RenderBox {
|
||||
AlignmentGeometry alignment = Alignment.center,
|
||||
}) : assert(alignment != null),
|
||||
assert(progress != null && progress >= 0.0 && progress <= 1.0),
|
||||
_composition = composition,
|
||||
_progress = progress,
|
||||
_drawable = composition != null
|
||||
? (LottieDrawable(composition)
|
||||
..setProgress(progress)
|
||||
..delegates = delegates)
|
||||
: null,
|
||||
_width = width,
|
||||
_height = height,
|
||||
_fit = fit,
|
||||
_alignment = alignment;
|
||||
|
||||
/// The lottie composition to display.
|
||||
LottieComposition get composition => _composition;
|
||||
LottieComposition _composition;
|
||||
set composition(LottieComposition value) {
|
||||
if (value == _composition) {
|
||||
return;
|
||||
}
|
||||
_composition = value;
|
||||
LottieComposition get composition => _drawable?.composition;
|
||||
LottieDrawable _drawable;
|
||||
void setComposition(LottieComposition composition,
|
||||
{@required double progress, @required LottieDelegates delegates}) {
|
||||
var needsLayout = false;
|
||||
var needsPaint = false;
|
||||
if (composition == null) {
|
||||
_drawable = null;
|
||||
markNeedsPaint();
|
||||
if (_width == null || _height == null) {
|
||||
markNeedsLayout();
|
||||
needsPaint = true;
|
||||
needsLayout = true;
|
||||
} else {
|
||||
if (_drawable?.composition != composition) {
|
||||
_drawable = LottieDrawable(composition);
|
||||
needsLayout = true;
|
||||
needsPaint = true;
|
||||
}
|
||||
|
||||
needsPaint |= _drawable.setProgress(progress);
|
||||
|
||||
if (_drawable.delegates != delegates) {
|
||||
_drawable.delegates = delegates;
|
||||
needsPaint = true;
|
||||
}
|
||||
}
|
||||
|
||||
double get progress => _progress;
|
||||
double _progress;
|
||||
set progress(double value) {
|
||||
if (value == _progress) {
|
||||
return;
|
||||
if (needsPaint) {
|
||||
markNeedsPaint();
|
||||
}
|
||||
_progress = value;
|
||||
if (needsLayout && (_width == null || _height == null)) {
|
||||
markNeedsLayout();
|
||||
}
|
||||
}
|
||||
|
||||
/// If non-null, requires the composition to have this width.
|
||||
///
|
||||
@ -116,14 +130,12 @@ class RenderLottie extends RenderBox {
|
||||
height: _height,
|
||||
).enforce(constraints);
|
||||
|
||||
if (_composition == null) {
|
||||
if (_drawable == null) {
|
||||
return constraints.smallest;
|
||||
}
|
||||
|
||||
return constraints.constrainSizeAndAttemptToPreserveAspectRatio(Size(
|
||||
_composition.bounds.width.toDouble(),
|
||||
_composition.bounds.height.toDouble(),
|
||||
));
|
||||
return constraints
|
||||
.constrainSizeAndAttemptToPreserveAspectRatio(_drawable.size);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -168,17 +180,12 @@ class RenderLottie extends RenderBox {
|
||||
size = _sizeForConstraints(constraints);
|
||||
}
|
||||
|
||||
LottieDrawable _drawable;
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
if (_composition == null) return;
|
||||
|
||||
_drawable ??= LottieDrawable(_composition);
|
||||
if (_drawable == null) return;
|
||||
|
||||
_drawable.draw(context.canvas, offset & size,
|
||||
progress: _progress,
|
||||
fit: _fit,
|
||||
alignment: _alignment.resolve(TextDirection.ltr));
|
||||
fit: _fit, alignment: _alignment.resolve(TextDirection.ltr));
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1,60 +0,0 @@
|
||||
import 'lottie_drawable.dart';
|
||||
|
||||
/// Extend this class to replace animation text with custom text. This can be useful to handle
|
||||
/// translations.
|
||||
///
|
||||
/// The only method you should have to override is {@link #getText(String)}.
|
||||
class TextDelegate {
|
||||
final _stringMap = <String, String>{};
|
||||
final LottieDrawable /*?*/ drawable;
|
||||
bool _cacheText = true;
|
||||
|
||||
TextDelegate(this.drawable);
|
||||
|
||||
/// Override this to replace the animation text with something dynamic. This can be used for
|
||||
/// translations or custom data.
|
||||
String getText(String input) {
|
||||
return input;
|
||||
}
|
||||
|
||||
/// Update the text that will be rendered for the given input text.
|
||||
void setText(String input, String output) {
|
||||
_stringMap[input] = output;
|
||||
_invalidate();
|
||||
}
|
||||
|
||||
/// Sets whether or not {@link TextDelegate} will cache (memoize) the results of getText.
|
||||
/// If this isn't necessary then set it to false.
|
||||
void setCacheText(bool cacheText) {
|
||||
_cacheText = cacheText;
|
||||
}
|
||||
|
||||
/// Invalidates a cached string with the given input.
|
||||
void invalidateText(String input) {
|
||||
_stringMap.remove(input);
|
||||
_invalidate();
|
||||
}
|
||||
|
||||
/// Invalidates all cached strings
|
||||
void invalidateAllText() {
|
||||
_stringMap.clear();
|
||||
_invalidate();
|
||||
}
|
||||
|
||||
String getTextInternal(String input) {
|
||||
if (_cacheText && _stringMap.containsKey(input)) {
|
||||
return _stringMap[input];
|
||||
}
|
||||
var text = getText(input);
|
||||
if (_cacheText) {
|
||||
_stringMap[input] = text;
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
void _invalidate() {
|
||||
if (drawable != null) {
|
||||
drawable.invalidateSelf();
|
||||
}
|
||||
}
|
||||
}
|
12
lib/src/value/lottie_relative_double_value_callback.dart
Normal file
@ -0,0 +1,12 @@
|
||||
import 'dart:ui';
|
||||
import 'lottie_frame_info.dart';
|
||||
|
||||
double Function(LottieFrameInfo<double>) relativeDoubleValueCallback(
|
||||
double offset) {
|
||||
return (LottieFrameInfo<double> frameInfo) {
|
||||
var originalValue = lerpDouble(frameInfo.startValue, frameInfo.endValue,
|
||||
frameInfo.interpolatedKeyframeProgress);
|
||||
|
||||
return originalValue + offset;
|
||||
};
|
||||
}
|
11
lib/src/value/lottie_relative_integer_value_callback.dart
Normal file
@ -0,0 +1,11 @@
|
||||
import 'dart:ui';
|
||||
import 'lottie_frame_info.dart';
|
||||
|
||||
int Function(LottieFrameInfo<int>) relativeIntegerValueCallback(int offset) {
|
||||
return (LottieFrameInfo<int> frameInfo) {
|
||||
var originalValue = lerpDouble(frameInfo.startValue, frameInfo.endValue,
|
||||
frameInfo.interpolatedKeyframeProgress);
|
||||
|
||||
return (originalValue + offset).round();
|
||||
};
|
||||
}
|
12
lib/src/value/lottie_relative_point_value_callback.dart
Normal file
@ -0,0 +1,12 @@
|
||||
import 'dart:ui';
|
||||
import 'lottie_frame_info.dart';
|
||||
|
||||
Offset Function(LottieFrameInfo<Offset>) relativeOffsetValueCallback(
|
||||
Offset offset) {
|
||||
return (LottieFrameInfo<Offset> frameInfo) {
|
||||
var point = Offset.lerp(frameInfo.startValue, frameInfo.endValue,
|
||||
frameInfo.interpolatedKeyframeProgress);
|
||||
|
||||
return point.translate(offset.dx, offset.dy);
|
||||
};
|
||||
}
|
@ -4,26 +4,34 @@ import 'lottie_frame_info.dart';
|
||||
/// Allows you to set a callback on a resolved {@link com.airbnb.lottie.model.KeyPath} to modify
|
||||
/// its animation values at runtime.
|
||||
class LottieValueCallback<T> {
|
||||
BaseKeyframeAnimation /*?*/ animation;
|
||||
LottieValueCallback(this._value);
|
||||
|
||||
BaseKeyframeAnimation /*?*/ _animation;
|
||||
BaseKeyframeAnimation get animation => _animation;
|
||||
|
||||
/// This can be set with {@link #setValue(Object)} to use a value instead of deferring
|
||||
/// to the callback.
|
||||
///*/
|
||||
T /*?*/ value;
|
||||
T /*?*/ _value;
|
||||
T get value => _value;
|
||||
|
||||
LottieValueCallback(this.value);
|
||||
T Function(LottieFrameInfo<T>) callback;
|
||||
|
||||
/// Override this if you haven't set a static value in the constructor or with setValue.
|
||||
///
|
||||
/// Return null to resort to the default value.
|
||||
T getValue(LottieFrameInfo<T> frameInfo) {
|
||||
if (callback != null) {
|
||||
return callback(frameInfo);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
void setValue(T /*?*/ value) {
|
||||
this.value = value;
|
||||
if (animation != null) {
|
||||
animation.notifyListeners();
|
||||
_value = value;
|
||||
if (_animation != null) {
|
||||
_animation.notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@ -35,11 +43,17 @@ class LottieValueCallback<T> {
|
||||
double linearKeyframeProgress,
|
||||
double interpolatedKeyframeProgress,
|
||||
double overallProgress) {
|
||||
return getValue(LottieFrameInfo(startFrame, endFrame, startValue, endValue,
|
||||
linearKeyframeProgress, interpolatedKeyframeProgress, overallProgress));
|
||||
return getValue(LottieFrameInfo<T>(
|
||||
startFrame,
|
||||
endFrame,
|
||||
startValue,
|
||||
endValue,
|
||||
linearKeyframeProgress,
|
||||
interpolatedKeyframeProgress,
|
||||
overallProgress));
|
||||
}
|
||||
|
||||
void setAnimation(BaseKeyframeAnimation /*?*/ animation) {
|
||||
this.animation = animation;
|
||||
_animation = animation;
|
||||
}
|
||||
}
|
||||
|
@ -1,23 +0,0 @@
|
||||
import 'dart:ui';
|
||||
|
||||
//TODO(xha): delete and use Offset
|
||||
class ScaleXY {
|
||||
final double x;
|
||||
final double y;
|
||||
|
||||
ScaleXY(this.x, this.y);
|
||||
ScaleXY.one() : this(1, 1);
|
||||
|
||||
@override
|
||||
bool operator ==(other) {
|
||||
return other is ScaleXY && x == other.x && y == other.y;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(x, y);
|
||||
|
||||
bool equals(double x, double y) => this.x == x && this.y == y;
|
||||
|
||||
static ScaleXY lerp(ScaleXY a, ScaleXY b, double t) =>
|
||||
ScaleXY(lerpDouble(a.x, b.x, t), lerpDouble(a.y, b.y, t));
|
||||
}
|
331
lib/src/value_delegate.dart
Normal file
@ -0,0 +1,331 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'lottie_drawable.dart';
|
||||
import 'lottie_property.dart';
|
||||
import 'model/key_path.dart';
|
||||
import 'value/lottie_frame_info.dart';
|
||||
import 'value/lottie_relative_double_value_callback.dart';
|
||||
import 'value/lottie_relative_integer_value_callback.dart';
|
||||
import 'value/lottie_relative_point_value_callback.dart';
|
||||
import 'value/lottie_value_callback.dart';
|
||||
|
||||
class ValueDelegate<T> {
|
||||
final List<String> keyPath;
|
||||
final T property;
|
||||
final T value;
|
||||
final T Function(LottieFrameInfo<T>) callback;
|
||||
|
||||
ValueDelegate._(this.keyPath, this.property, this.value, this.callback)
|
||||
: assert(value == null || callback == null,
|
||||
"Value and callback can't be both specified.");
|
||||
|
||||
static ValueDelegate<Offset> _offset(
|
||||
List<String> keyPath,
|
||||
Offset property,
|
||||
Offset value,
|
||||
Offset Function(LottieFrameInfo<Offset>) callback,
|
||||
Offset relative) {
|
||||
if (relative != null) {
|
||||
assert(callback == null);
|
||||
callback = relativeOffsetValueCallback(relative);
|
||||
}
|
||||
return ValueDelegate<Offset>._(keyPath, property, value, callback);
|
||||
}
|
||||
|
||||
static ValueDelegate<double> _double(
|
||||
List<String> keyPath,
|
||||
double property,
|
||||
double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative) {
|
||||
if (relative != null) {
|
||||
assert(callback == null);
|
||||
callback = relativeDoubleValueCallback(relative);
|
||||
}
|
||||
return ValueDelegate<double>._(keyPath, property, value, callback);
|
||||
}
|
||||
|
||||
static ValueDelegate<int> _int(List<String> keyPath, int property, int value,
|
||||
int Function(LottieFrameInfo<int>) callback, int relative) {
|
||||
if (relative != null) {
|
||||
assert(callback == null);
|
||||
callback = relativeIntegerValueCallback(relative);
|
||||
}
|
||||
return ValueDelegate<int>._(keyPath, property, value, callback);
|
||||
}
|
||||
|
||||
static ValueDelegate<Color> color(List<String> keyPath,
|
||||
{Color value, Color Function(LottieFrameInfo<Color>) callback}) =>
|
||||
ValueDelegate._(keyPath, LottieProperty.color, value, callback);
|
||||
|
||||
static ValueDelegate<Color> strokeColor(List<String> keyPath,
|
||||
{Color value, Color Function(LottieFrameInfo<Color>) callback}) =>
|
||||
ValueDelegate._(keyPath, LottieProperty.strokeColor, value, callback);
|
||||
|
||||
/// Opacity value are 0-100 to match after effects
|
||||
static ValueDelegate<int> transformOpacity(List<String> keyPath,
|
||||
{int value,
|
||||
int Function(LottieFrameInfo<int>) callback,
|
||||
int relative}) =>
|
||||
_int(keyPath, LottieProperty.transformOpacity, value, callback, relative);
|
||||
|
||||
/// Opacity value are 0-100 to match after effects
|
||||
static ValueDelegate<int> opacity(List<String> keyPath,
|
||||
{int value,
|
||||
int Function(LottieFrameInfo<int>) callback,
|
||||
int relative}) =>
|
||||
_int(keyPath, LottieProperty.opacity, value, callback, relative);
|
||||
|
||||
static ValueDelegate<Offset> transformAnchorPoint(
|
||||
List<String> keyPath, {
|
||||
Offset value,
|
||||
Offset Function(LottieFrameInfo<Offset>) callback,
|
||||
Offset relative,
|
||||
}) {
|
||||
return _offset(keyPath, LottieProperty.transformAnchorPoint, value,
|
||||
callback, relative);
|
||||
}
|
||||
|
||||
static ValueDelegate<Offset> transformPosition(
|
||||
List<String> keyPath, {
|
||||
Offset value,
|
||||
Offset Function(LottieFrameInfo<Offset>) callback,
|
||||
Offset relative,
|
||||
}) =>
|
||||
_offset(
|
||||
keyPath, LottieProperty.transformPosition, value, callback, relative);
|
||||
|
||||
static ValueDelegate<Offset> ellipseSize(
|
||||
List<String> keyPath, {
|
||||
Offset value,
|
||||
Offset Function(LottieFrameInfo<Offset>) callback,
|
||||
Offset relative,
|
||||
}) =>
|
||||
_offset(keyPath, LottieProperty.ellipseSize, value, callback, relative);
|
||||
|
||||
static ValueDelegate<Offset> rectangleSize(
|
||||
List<String> keyPath, {
|
||||
Offset value,
|
||||
Offset Function(LottieFrameInfo<Offset>) callback,
|
||||
Offset relative,
|
||||
}) =>
|
||||
_offset(keyPath, LottieProperty.rectangleSize, value, callback, relative);
|
||||
|
||||
static ValueDelegate<double> cornerRadius(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(keyPath, LottieProperty.cornerRadius, value, callback, relative);
|
||||
|
||||
static ValueDelegate<Offset> position(
|
||||
List<String> keyPath, {
|
||||
Offset value,
|
||||
Offset Function(LottieFrameInfo<Offset>) callback,
|
||||
Offset relative,
|
||||
}) =>
|
||||
_offset(keyPath, LottieProperty.position, value, callback, relative);
|
||||
|
||||
static ValueDelegate<Offset> transformScale(
|
||||
List<String> keyPath, {
|
||||
Offset value,
|
||||
Offset Function(LottieFrameInfo<Offset>) callback,
|
||||
Offset relative,
|
||||
}) =>
|
||||
_offset(
|
||||
keyPath, LottieProperty.transformScale, value, callback, relative);
|
||||
|
||||
/// In degrees
|
||||
static ValueDelegate<double> transformRotation(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(
|
||||
keyPath, LottieProperty.transformRotation, value, callback, relative);
|
||||
|
||||
static ValueDelegate<double> transformSkew(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(keyPath, LottieProperty.transformSkew, value, callback, relative);
|
||||
|
||||
static ValueDelegate<double> transformSkewAngle(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(keyPath, LottieProperty.transformSkewAngle, value, callback,
|
||||
relative);
|
||||
|
||||
static ValueDelegate<double> strokeWidth(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(keyPath, LottieProperty.strokeWidth, value, callback, relative);
|
||||
|
||||
static ValueDelegate<double> textTracking(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(keyPath, LottieProperty.textTracking, value, callback, relative);
|
||||
|
||||
static ValueDelegate<double> repeaterCopies(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(
|
||||
keyPath, LottieProperty.repeaterCopies, value, callback, relative);
|
||||
|
||||
static ValueDelegate<double> repeaterOffset(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(
|
||||
keyPath, LottieProperty.repeaterOffset, value, callback, relative);
|
||||
|
||||
static ValueDelegate<double> polystarPoints(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(
|
||||
keyPath, LottieProperty.polystarPoints, value, callback, relative);
|
||||
|
||||
/// In degrees
|
||||
static ValueDelegate<double> polystarRotation(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(
|
||||
keyPath, LottieProperty.polystarRotation, value, callback, relative);
|
||||
|
||||
static ValueDelegate<double> polystarInnerRadius(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(keyPath, LottieProperty.polystarInnerRadius, value, callback,
|
||||
relative);
|
||||
|
||||
static ValueDelegate<double> polystarOuterRadius(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(keyPath, LottieProperty.polystarOuterRadius, value, callback,
|
||||
relative);
|
||||
|
||||
static ValueDelegate<double> polystarInnerRoundedness(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(keyPath, LottieProperty.polystarInnerRoundedness, value, callback,
|
||||
relative);
|
||||
|
||||
static ValueDelegate<double> polystarOuterRoundedness(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(keyPath, LottieProperty.polystarOuterRoundedness, value, callback,
|
||||
relative);
|
||||
|
||||
/// Opacity value are 0-100 to match after effects
|
||||
static ValueDelegate<double> transformStartOpacity(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(keyPath, LottieProperty.transformStartOpacity, value, callback,
|
||||
relative);
|
||||
|
||||
/// Opacity value are 0-100 to match after effects
|
||||
static ValueDelegate<double> transformEndOpacity(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(keyPath, LottieProperty.transformEndOpacity, value, callback,
|
||||
relative);
|
||||
|
||||
/// The time value in seconds
|
||||
static ValueDelegate<double> timeRemap(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(keyPath, LottieProperty.timeRemap, value, callback, relative);
|
||||
|
||||
static ValueDelegate<double> textSize(List<String> keyPath,
|
||||
{double value,
|
||||
double Function(LottieFrameInfo<double>) callback,
|
||||
double relative}) =>
|
||||
_double(keyPath, LottieProperty.textSize, value, callback, relative);
|
||||
|
||||
static ValueDelegate<ColorFilter> colorFilter(List<String> keyPath,
|
||||
{ColorFilter value,
|
||||
ColorFilter Function(LottieFrameInfo<ColorFilter>) callback}) =>
|
||||
ValueDelegate._(keyPath, LottieProperty.colorFilter, value, callback);
|
||||
|
||||
static ValueDelegate<List<Color>> gradientColor(List<String> keyPath,
|
||||
{List<Color> value,
|
||||
List<Color> Function(LottieFrameInfo<List<Color>>) callback}) =>
|
||||
ValueDelegate._(keyPath, LottieProperty.gradientColor, value, callback);
|
||||
|
||||
ResolvedValueDelegate<T> _resolved;
|
||||
ResolvedValueDelegate _resolve(List<KeyPath> resolvedPaths) {
|
||||
_resolved = ResolvedValueDelegate<T>(this, resolvedPaths);
|
||||
return _resolved;
|
||||
}
|
||||
|
||||
bool isSameProperty(ValueDelegate other) {
|
||||
if (identical(this, other)) return true;
|
||||
return other is ValueDelegate<T> &&
|
||||
const ListEquality().equals(other.keyPath, keyPath) &&
|
||||
other.property == property;
|
||||
}
|
||||
}
|
||||
|
||||
ResolvedValueDelegate internalResolved(ValueDelegate valueDelegate) {
|
||||
return valueDelegate._resolved;
|
||||
}
|
||||
|
||||
ResolvedValueDelegate internalResolve(
|
||||
ValueDelegate delegate, List<KeyPath> resolvedPaths) {
|
||||
return delegate._resolve(resolvedPaths);
|
||||
}
|
||||
|
||||
class ResolvedValueDelegate<T> {
|
||||
final ValueDelegate<T> valueDelegate;
|
||||
final List<KeyPath> keyPaths;
|
||||
final LottieValueCallback<T> valueCallback;
|
||||
|
||||
ResolvedValueDelegate(this.valueDelegate, this.keyPaths)
|
||||
: valueCallback = LottieValueCallback(valueDelegate.value)
|
||||
..callback = valueDelegate.callback;
|
||||
|
||||
T get property => valueDelegate.property;
|
||||
|
||||
void updateDelegate(ValueDelegate<T> delegate) {
|
||||
valueCallback
|
||||
..setValue(delegate.value)
|
||||
..callback = delegate.callback;
|
||||
}
|
||||
|
||||
void clear() {
|
||||
valueCallback
|
||||
..setValue(null)
|
||||
..callback = null;
|
||||
}
|
||||
|
||||
/// Add a property callback for the specified {@link KeyPath}. This {@link KeyPath} can resolve
|
||||
/// to multiple contents. In that case, the callbacks's value will apply to all of them.
|
||||
/// <p>
|
||||
/// Internally, this will check if the {@link KeyPath} has already been resolved with
|
||||
/// {@link #resolveKeyPath(KeyPath)} and will resolve it if it hasn't.
|
||||
void addValueCallback(LottieDrawable drawable) {
|
||||
for (var keyPath in keyPaths) {
|
||||
keyPath.resolvedElement.addValueCallback<T>(property, valueCallback);
|
||||
}
|
||||
if (keyPaths.isNotEmpty) {
|
||||
drawable.invalidateSelf();
|
||||
if (property == LottieProperty.timeRemap) {
|
||||
// Time remapping values are read in setProgress. In order for the new value
|
||||
// to apply, we have to re-set the progress with the current progress so that the
|
||||
// time remapping can be reapplied.
|
||||
drawable.setProgress(drawable.progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
pubspec.lock
@ -43,6 +43,13 @@ packages:
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
characters:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: characters
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
charcode:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -171,7 +178,7 @@ packages:
|
||||
name: package_config
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
version: "1.9.1"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -199,7 +206,7 @@ packages:
|
||||
name: pub_semver
|
||||
url: "https://pub.dartlang.org"
|
||||
source: hosted
|
||||
version: "1.4.2"
|
||||
version: "1.4.3"
|
||||
quiver:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -1,6 +1,6 @@
|
||||
name: lottie
|
||||
description: Render After Effects animations natively on Flutter. This package is a pure Dart implementation of a Lottie player.
|
||||
version: 0.2.2
|
||||
version: 0.3.0
|
||||
homepage: https://github.com/xvrh/lottie-flutter
|
||||
|
||||
environment:
|
||||
@ -10,6 +10,7 @@ dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
archive: ^2.0.0
|
||||
characters:
|
||||
charcode: ^1.0.0
|
||||
logging: ^0.11.0
|
||||
meta: ^1.1.8
|
||||
|
@ -21,8 +21,9 @@ void main() {
|
||||
var recorder = PictureRecorder();
|
||||
var canvas = Canvas(recorder);
|
||||
for (var progress = 0; progress <= 100; progress += 20) {
|
||||
drawable.draw(canvas, Rect.fromLTWH(0, 0, 200, 200),
|
||||
progress: progress / 100);
|
||||
drawable
|
||||
..setProgress(progress / 100)
|
||||
..draw(canvas, Rect.fromLTWH(0, 0, 200, 200));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
BIN
test/golden/dynamic_text/1.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
test/golden/dynamic_text/2.png
Normal file
After Width: | Height: | Size: 23 KiB |