Compare commits

..

2 Commits

Author SHA1 Message Date
548c77dc45 Add FrameRate and improve performance (#93)
- Run the animation at the exported frame rate
- Wrap the animation in a RepaintBoundary
- Don't paint during "static" periods
2020-08-04 22:02:02 +02:00
45a4c0b981 Remove direct dependency on dart:io (#90) 2020-07-26 23:09:58 +02:00
39 changed files with 480 additions and 290 deletions

View File

@ -1,3 +1,17 @@
## [0.6.0]
- Runs the animation at the frame rate specified in the json file (ie. An animation encoded with a 20 FPS will only
be paint 20 times per seconds even though the AnimationController will invalidate the widget 60 times per seconds).
A new property `frameRate` allows to opt-out this behavior and have the widget to repaint at the device frame rate
(`FrameRate.max`).
- Automatically add a `RepaintBoundary` around the widget. Since `Lottie` animations are generally complex to paint, a
`RepaintBoundary` will separate the animation with the rest of the app and improve performance. A new property `addRepaintBoundary`
allows to opt-out this behavior.
- Fix a bug where we would call `markNeedPaint` when the animation was not changing. This removes unnecessary paints in
animations with static periods.
## [0.5.1]
- Remove direct dependencies on dart:io to support Flutter Web
## [0.5.0] ## [0.5.0]
- Support loading animation from network in a web app - Support loading animation from network in a web app
- Fix a couple of bugs with the web dev compiler - Fix a couple of bugs with the web dev compiler

View File

@ -17,7 +17,7 @@ This example shows how to display a Lottie animation in the simplest way.
The `Lottie` widget will load the json file and run the animation indefinitely. The `Lottie` widget will load the json file and run the animation indefinitely.
```dart ```dart
import 'example/lib/examples/main.dart'; import 'example/lib/main.dart';
``` ```
### Specify a custom `AnimationController` ### Specify a custom `AnimationController`

View File

@ -1,27 +0,0 @@
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: [
// Load a Lottie file from your assets
Lottie.asset('assets/LottieLogo1.json'),
// Load a Lottie file from a remote url
Lottie.network(
'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/lottiefiles/angel.zip'),
],
),
),
);
}
}

View File

@ -1,157 +1,24 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:lottie/lottie.dart'; import 'package:lottie/lottie.dart';
import 'src/all_files.g.dart';
void main() { void main() => runApp(MyApp());
Logger.root
..level = Level.ALL
..onRecord.listen(print);
Lottie.traceEnabled = true;
runApp(App());
}
class App extends StatelessWidget { class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
//showPerformanceOverlay: true,
home: Scaffold( home: Scaffold(
appBar: AppBar( body: ListView(
title: Text('Lottie Flutter'),
),
body: GridView.builder(
itemCount: files.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
itemBuilder: (context, index) {
var assetName = files[index];
return GestureDetector(
child: _Item(
child: Lottie.asset(
assetName,
frameBuilder: (context, child, composition) {
return AnimatedOpacity(
child: child,
opacity: composition == null ? 0 : 1,
duration: const Duration(seconds: 1),
curve: Curves.easeOut,
);
},
),
),
onTap: () {
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (context) => Detail(assetName)));
},
);
},
),
),
);
}
}
class _Item extends StatelessWidget {
final Widget child;
const _Item({Key key, this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(10)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
offset: Offset(2, 2),
blurRadius: 5)
]),
child: child,
),
);
}
}
class Detail extends StatefulWidget {
final String assetName;
const Detail(this.assetName, {Key key}) : super(key: key);
@override
_DetailState createState() => _DetailState();
}
class _DetailState extends State<Detail> with TickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('${widget.assetName}'),
),
body: SingleChildScrollView(
child: Column(
children: [ children: [
Center( // Load a Lottie file from your assets
child: Lottie.asset( Lottie.asset('assets/LottieLogo1.json'),
widget.assetName,
controller: _controller, // Load a Lottie file from a remote url
onLoaded: (composition) { Lottie.network(
_controller.duration = composition.duration; 'https://raw.githubusercontent.com/xvrh/lottie-flutter/master/example/assets/Mobilo/A.json'),
_controller.repeat();
}, // Load an animation and its images from a zip file
), Lottie.asset('assets/lottiefiles/angel.zip'),
),
AnimatedBuilder(
animation: _controller,
builder: (context, _) => Row(
children: <Widget>[
Expanded(
child: Slider(
value: _controller.value,
onChanged: (newValue) {
_controller.value = newValue;
},
),
),
IconButton(
icon: Icon(_controller.isAnimating
? Icons.stop
: Icons.play_arrow),
onPressed: () {
setState(() {
if (_controller.isAnimating) {
_controller.stop();
} else {
_controller.repeat();
}
});
},
),
],
),
),
], ],
), ),
), ),

160
example/lib/main_app.dart Normal file
View File

@ -0,0 +1,160 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:lottie/lottie.dart';
import 'src/all_files.g.dart';
void main() {
Logger.root
..level = Level.ALL
..onRecord.listen(print);
Lottie.traceEnabled = true;
runApp(App());
}
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
//showPerformanceOverlay: true,
home: Scaffold(
appBar: AppBar(
title: Text('Lottie Flutter'),
),
body: GridView.builder(
itemCount: files.length,
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 4),
itemBuilder: (context, index) {
var assetName = files[index];
return GestureDetector(
child: _Item(
child: Lottie.asset(
assetName,
frameBuilder: (context, child, composition) {
return AnimatedOpacity(
child: child,
opacity: composition == null ? 0 : 1,
duration: const Duration(seconds: 1),
curve: Curves.easeOut,
);
},
),
),
onTap: () {
Navigator.of(context).push(MaterialPageRoute<void>(
builder: (context) => Detail(assetName)));
},
);
},
),
),
);
}
}
class _Item extends StatelessWidget {
final Widget child;
const _Item({Key key, this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(10)),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
offset: Offset(2, 2),
blurRadius: 5)
]),
child: child,
),
);
}
}
class Detail extends StatefulWidget {
final String assetName;
const Detail(this.assetName, {Key key}) : super(key: key);
@override
_DetailState createState() => _DetailState();
}
class _DetailState extends State<Detail> with TickerProviderStateMixin {
AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('${widget.assetName}'),
),
body: SingleChildScrollView(
child: Column(
children: [
Center(
child: Lottie.asset(
widget.assetName,
controller: _controller,
onLoaded: (composition) {
_controller.duration = composition.duration;
_controller.repeat();
},
),
),
AnimatedBuilder(
animation: _controller,
builder: (context, _) => Row(
children: <Widget>[
Expanded(
child: Slider(
value: _controller.value,
onChanged: (newValue) {
_controller.value = newValue;
},
),
),
IconButton(
icon: Icon(_controller.isAnimating
? Icons.stop
: Icons.play_arrow),
onPressed: () {
setState(() {
if (_controller.isAnimating) {
_controller.stop();
} else {
_controller.repeat();
}
});
},
),
],
),
),
],
),
),
);
}
}

View File

@ -5,13 +5,13 @@ PODS:
- FlutterMacOS - FlutterMacOS
DEPENDENCIES: DEPENDENCIES:
- FlutterMacOS (from `Flutter/ephemeral/.symlinks/flutter/darwin-x64`) - FlutterMacOS (from `Flutter/ephemeral/.symlinks/flutter/darwin-x64-profile`)
- path_provider (from `Flutter/ephemeral/.symlinks/plugins/path_provider/macos`) - path_provider (from `Flutter/ephemeral/.symlinks/plugins/path_provider/macos`)
- path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`) - path_provider_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
EXTERNAL SOURCES: EXTERNAL SOURCES:
FlutterMacOS: FlutterMacOS:
:path: Flutter/ephemeral/.symlinks/flutter/darwin-x64 :path: Flutter/ephemeral/.symlinks/flutter/darwin-x64-profile
path_provider: path_provider:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider/macos :path: Flutter/ephemeral/.symlinks/plugins/path_provider/macos
path_provider_macos: path_provider_macos:

View File

@ -21,7 +21,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.4.1" version: "2.4.2"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -35,7 +35,7 @@ packages:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.1.0-nullsafety"
charcode: charcode:
dependency: transitive dependency: transitive
description: description:
@ -56,7 +56,7 @@ packages:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.14.12" version: "1.15.0-nullsafety"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -143,21 +143,21 @@ packages:
path: ".." path: ".."
relative: true relative: true
source: path source: path
version: "0.4.1" version: "0.6.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.6" version: "0.12.8"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.8" version: "1.3.0-nullsafety"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -199,7 +199,7 @@ packages:
name: pedantic name: pedantic
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.0" version: "1.9.2"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -239,7 +239,7 @@ packages:
name: stack_trace name: stack_trace
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.3" version: "1.9.5"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
@ -267,21 +267,21 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.16" version: "0.2.17"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.6" version: "1.3.0-nullsafety"
vector_math: vector_math:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.8" version: "2.1.0-nullsafety"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -290,5 +290,5 @@ packages:
source: hosted source: hosted
version: "0.1.0" version: "0.1.0"
sdks: sdks:
dart: ">=2.7.0 <3.0.0" dart: ">=2.9.0-18.0 <2.9.0"
flutter: ">=1.12.13+hotfix.5 <2.0.0" flutter: ">=1.12.13+hotfix.5 <2.0.0"

View File

@ -33,6 +33,7 @@ void main() {
composition: composition, composition: composition,
controller: animation, controller: animation,
delegates: LottieDelegates(values: [delegate]), delegates: LottieDelegates(values: [delegate]),
addRepaintBoundary: false,
), ),
); );
await tester.pump(); await tester.pump();
@ -45,6 +46,7 @@ void main() {
composition: composition, composition: composition,
controller: animation, controller: animation,
delegates: LottieDelegates(values: []), delegates: LottieDelegates(values: []),
addRepaintBoundary: false,
), ),
); );
await tester.pump(); await tester.pump();

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 108 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -42,7 +42,7 @@ class _CustomerPainter extends CustomPainter {
thumbSize; thumbSize;
drawable drawable
..setProgress(progress) ..setProgress(progress, frameRate: FrameRate.max)
..draw(canvas, rect); ..draw(canvas, rect);
++index; ++index;

View File

@ -1,5 +1,5 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:lottie_example/main.dart'; import 'package:lottie_example/main_app.dart';
void main() { void main() {
testWidgets('Main sample', (tester) async { testWidgets('Main sample', (tester) async {

View File

@ -1,4 +1,5 @@
export 'src/composition.dart' show LottieComposition; export 'src/composition.dart' show LottieComposition;
export 'src/frame_rate.dart' show FrameRate;
export 'src/lottie.dart' show Lottie; export 'src/lottie.dart' show Lottie;
export 'src/lottie_builder.dart' show LottieBuilder; export 'src/lottie_builder.dart' show LottieBuilder;
export 'src/lottie_delegates.dart' show LottieDelegates; export 'src/lottie_delegates.dart' show LottieDelegates;

View File

@ -3,6 +3,7 @@ import 'dart:typed_data';
import 'package:archive/archive.dart'; import 'package:archive/archive.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import 'frame_rate.dart';
import 'logger.dart'; import 'logger.dart';
import 'lottie_image_asset.dart'; import 'lottie_image_asset.dart';
import 'model/font.dart'; import 'model/font.dart';
@ -133,9 +134,11 @@ class LottieComposition {
Rectangle<int> get bounds => _bounds; Rectangle<int> get bounds => _bounds;
Duration get duration { Duration get duration {
return Duration(milliseconds: (durationFrames / _frameRate * 1000).round()); return Duration(milliseconds: (seconds * 1000).round());
} }
double get seconds => durationFrames / _frameRate;
double get startFrame => _startFrame; double get startFrame => _startFrame;
double get endFrame => _endFrame; double get endFrame => _endFrame;
@ -176,6 +179,24 @@ class LottieComposition {
return _endFrame - _startFrame; return _endFrame - _startFrame;
} }
/// Returns a "rounded" progress value according to the frameRate
double roundProgress(double progress, {@required FrameRate frameRate}) {
num fps;
if (frameRate == FrameRate.max) {
return progress;
} else if (frameRate == FrameRate.composition) {
fps = this.frameRate;
}
fps ??= frameRate.framesPerSecond;
var totalFrameCount = seconds * fps;
var frameIndex = (totalFrameCount * progress).toInt();
var roundedProgress = frameIndex / totalFrameCount;
assert(roundedProgress >= 0 && roundedProgress <= 1,
'Progress is $roundedProgress');
return roundedProgress;
}
@override @override
String toString() { String toString() {
final sb = StringBuffer('LottieComposition:\n'); final sb = StringBuffer('LottieComposition:\n');

9
lib/src/frame_rate.dart Normal file
View File

@ -0,0 +1,9 @@
class FrameRate {
static final max = FrameRate._special(0);
static final composition = FrameRate._special(-1);
final double framesPerSecond;
FrameRate(this.framesPerSecond) : assert(framesPerSecond > 0);
FrameRate._special(this.framesPerSecond);
}

View File

@ -1,8 +1,8 @@
import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import '../lottie.dart'; import '../lottie.dart';
import 'frame_rate.dart';
import 'l.dart'; import 'l.dart';
import 'lottie_builder.dart'; import 'lottie_builder.dart';
import 'options.dart'; import 'options.dart';
@ -23,36 +23,44 @@ class Lottie extends StatefulWidget {
this.alignment, this.alignment,
this.fit, this.fit,
bool animate, bool animate,
this.frameRate,
bool repeat, bool repeat,
bool reverse, bool reverse,
this.delegates, this.delegates,
this.options, this.options,
bool addRepaintBoundary,
}) : animate = animate ?? true, }) : animate = animate ?? true,
reverse = reverse ?? false, reverse = reverse ?? false,
repeat = repeat ?? true, repeat = repeat ?? true,
addRepaintBoundary = addRepaintBoundary ?? true,
super(key: key); super(key: key);
/// Creates a widget that displays an [LottieComposition] obtained from an [AssetBundle]. /// Creates a widget that displays an [LottieComposition] obtained from an [AssetBundle].
static LottieBuilder asset(String name, static LottieBuilder asset(
{Animation<double> controller, String name, {
bool animate, Animation<double> controller,
bool repeat, bool animate,
bool reverse, FrameRate frameRate,
LottieDelegates delegates, bool repeat,
LottieOptions options, bool reverse,
void Function(LottieComposition) onLoaded, LottieDelegates delegates,
LottieImageProviderFactory imageProviderFactory, LottieOptions options,
Key key, void Function(LottieComposition) onLoaded,
AssetBundle bundle, LottieImageProviderFactory imageProviderFactory,
LottieFrameBuilder frameBuilder, Key key,
double width, AssetBundle bundle,
double height, LottieFrameBuilder frameBuilder,
BoxFit fit, double width,
Alignment alignment, double height,
String package}) => BoxFit fit,
Alignment alignment,
String package,
bool addRepaintBoundary,
}) =>
LottieBuilder.asset( LottieBuilder.asset(
name, name,
controller: controller, controller: controller,
frameRate: frameRate,
animate: animate, animate: animate,
repeat: repeat, repeat: repeat,
reverse: reverse, reverse: reverse,
@ -68,12 +76,14 @@ class Lottie extends StatefulWidget {
fit: fit, fit: fit,
alignment: alignment, alignment: alignment,
package: package, package: package,
addRepaintBoundary: addRepaintBoundary,
); );
/// Creates a widget that displays an [LottieComposition] obtained from a [File]. /// Creates a widget that displays an [LottieComposition] obtained from a [File].
static LottieBuilder file( static LottieBuilder file(
File file, { Object /*io.File|html.File*/ file, {
Animation<double> controller, Animation<double> controller,
FrameRate frameRate,
bool animate, bool animate,
bool repeat, bool repeat,
bool reverse, bool reverse,
@ -87,10 +97,12 @@ class Lottie extends StatefulWidget {
double height, double height,
BoxFit fit, BoxFit fit,
Alignment alignment, Alignment alignment,
bool addRepaintBoundary,
}) => }) =>
LottieBuilder.file( LottieBuilder.file(
file, file,
controller: controller, controller: controller,
frameRate: frameRate,
animate: animate, animate: animate,
repeat: repeat, repeat: repeat,
reverse: reverse, reverse: reverse,
@ -104,12 +116,14 @@ class Lottie extends StatefulWidget {
height: height, height: height,
fit: fit, fit: fit,
alignment: alignment, alignment: alignment,
addRepaintBoundary: addRepaintBoundary,
); );
/// Creates a widget that displays an [LottieComposition] obtained from a [Uint8List]. /// Creates a widget that displays an [LottieComposition] obtained from a [Uint8List].
static LottieBuilder memory( static LottieBuilder memory(
Uint8List bytes, { Uint8List bytes, {
Animation<double> controller, Animation<double> controller,
FrameRate frameRate,
bool animate, bool animate,
bool repeat, bool repeat,
bool reverse, bool reverse,
@ -123,10 +137,12 @@ class Lottie extends StatefulWidget {
double height, double height,
BoxFit fit, BoxFit fit,
Alignment alignment, Alignment alignment,
bool addRepaintBoundary,
}) => }) =>
LottieBuilder.memory( LottieBuilder.memory(
bytes, bytes,
controller: controller, controller: controller,
frameRate: frameRate,
animate: animate, animate: animate,
repeat: repeat, repeat: repeat,
reverse: reverse, reverse: reverse,
@ -140,12 +156,14 @@ class Lottie extends StatefulWidget {
height: height, height: height,
fit: fit, fit: fit,
alignment: alignment, alignment: alignment,
addRepaintBoundary: addRepaintBoundary,
); );
/// Creates a widget that displays an [LottieComposition] obtained from the network. /// Creates a widget that displays an [LottieComposition] obtained from the network.
static LottieBuilder network( static LottieBuilder network(
String url, { String url, {
Animation<double> controller, Animation<double> controller,
FrameRate frameRate,
bool animate, bool animate,
bool repeat, bool repeat,
bool reverse, bool reverse,
@ -159,10 +177,12 @@ class Lottie extends StatefulWidget {
double height, double height,
BoxFit fit, BoxFit fit,
Alignment alignment, Alignment alignment,
bool addRepaintBoundary,
}) => }) =>
LottieBuilder.network( LottieBuilder.network(
url, url,
controller: controller, controller: controller,
frameRate: frameRate,
animate: animate, animate: animate,
repeat: repeat, repeat: repeat,
reverse: reverse, reverse: reverse,
@ -176,6 +196,7 @@ class Lottie extends StatefulWidget {
height: height, height: height,
fit: fit, fit: fit,
alignment: alignment, alignment: alignment,
addRepaintBoundary: addRepaintBoundary,
); );
/// The Lottie composition to animate. /// The Lottie composition to animate.
@ -187,6 +208,14 @@ class Lottie extends StatefulWidget {
/// with the properties [animate], [reverse] /// with the properties [animate], [reverse]
final Animation<double> controller; final Animation<double> controller;
/// The number of frames per second to render.
/// Use `FrameRate.composition` to use the original frame rate of the Lottie composition (default)
/// Use `FrameRate.max` to advance the animation progression at every frame.
///
/// The advantage of using a low frame rate is to preserve the device battery
/// by doing less rendering work.
final FrameRate frameRate;
/// If no controller is specified, this value indicate whether or not the /// If no controller is specified, this value indicate whether or not the
/// Lottie animation should be played automatically (default to true). /// Lottie animation should be played automatically (default to true).
/// If there is an animation controller specified, this property has no effect. /// If there is an animation controller specified, this property has no effect.
@ -250,6 +279,13 @@ class Lottie extends StatefulWidget {
/// - enableMergePaths: Enable merge path support /// - enableMergePaths: Enable merge path support
final LottieOptions options; final LottieOptions options;
/// Indicate to automatically add a `RepaintBoundary` widget around the animation.
/// This allows to optimize the app performance by isolating the animation in its
/// own `Layer`.
///
/// This property is `true` by default.
final bool addRepaintBoundary;
static bool get traceEnabled => L.traceEnabled; static bool get traceEnabled => L.traceEnabled;
static set traceEnabled(bool enabled) { static set traceEnabled(bool enabled) {
L.traceEnabled = enabled; L.traceEnabled = enabled;
@ -304,18 +340,27 @@ class _LottieState extends State<Lottie> with TickerProviderStateMixin {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return AnimatedBuilder( Widget child = AnimatedBuilder(
animation: _progressAnimation, animation: _progressAnimation,
builder: (context, _) => RawLottie( builder: (context, _) {
composition: widget.composition, return RawLottie(
delegates: widget.delegates, composition: widget.composition,
options: widget.options, delegates: widget.delegates,
progress: _progressAnimation.value, options: widget.options,
width: widget.width, progress: _progressAnimation.value,
height: widget.height, frameRate: widget.frameRate,
fit: widget.fit, width: widget.width,
alignment: widget.alignment, height: widget.height,
), fit: widget.fit,
alignment: widget.alignment,
);
},
); );
if (widget.addRepaintBoundary) {
child = RepaintBoundary(child: child);
}
return child;
} }
} }

View File

@ -1,10 +1,10 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import '../lottie.dart'; import '../lottie.dart';
import 'frame_rate.dart';
import 'lottie.dart'; import 'lottie.dart';
import 'providers/asset_provider.dart'; import 'providers/asset_provider.dart';
import 'providers/file_provider.dart'; import 'providers/file_provider.dart';
@ -36,6 +36,7 @@ class LottieBuilder extends StatefulWidget {
Key key, Key key,
@required this.lottie, @required this.lottie,
this.controller, this.controller,
this.frameRate,
this.animate, this.animate,
this.reverse, this.reverse,
this.repeat, this.repeat,
@ -47,6 +48,7 @@ class LottieBuilder extends StatefulWidget {
this.height, this.height,
this.fit, this.fit,
this.alignment, this.alignment,
this.addRepaintBoundary,
}) : assert(lottie != null), }) : assert(lottie != null),
super(key: key); super(key: key);
@ -55,6 +57,7 @@ class LottieBuilder extends StatefulWidget {
String src, { String src, {
Map<String, String> headers, Map<String, String> headers,
this.controller, this.controller,
this.frameRate,
this.animate, this.animate,
this.reverse, this.reverse,
this.repeat, this.repeat,
@ -68,6 +71,7 @@ class LottieBuilder extends StatefulWidget {
this.height, this.height,
this.fit, this.fit,
this.alignment, this.alignment,
this.addRepaintBoundary,
}) : lottie = NetworkLottie(src, }) : lottie = NetworkLottie(src,
headers: headers, imageProviderFactory: imageProviderFactory), headers: headers, imageProviderFactory: imageProviderFactory),
super(key: key); super(key: key);
@ -83,8 +87,9 @@ class LottieBuilder extends StatefulWidget {
/// `android.permission.READ_EXTERNAL_STORAGE` permission. /// `android.permission.READ_EXTERNAL_STORAGE` permission.
/// ///
LottieBuilder.file( LottieBuilder.file(
File file, { Object /*io.File|html.File*/ file, {
this.controller, this.controller,
this.frameRate,
this.animate, this.animate,
this.reverse, this.reverse,
this.repeat, this.repeat,
@ -98,6 +103,7 @@ class LottieBuilder extends StatefulWidget {
this.height, this.height,
this.fit, this.fit,
this.alignment, this.alignment,
this.addRepaintBoundary,
}) : lottie = FileLottie(file, imageProviderFactory: imageProviderFactory), }) : lottie = FileLottie(file, imageProviderFactory: imageProviderFactory),
super(key: key); super(key: key);
@ -105,6 +111,7 @@ class LottieBuilder extends StatefulWidget {
LottieBuilder.asset( LottieBuilder.asset(
String name, { String name, {
this.controller, this.controller,
this.frameRate,
this.animate, this.animate,
this.reverse, this.reverse,
this.repeat, this.repeat,
@ -120,6 +127,7 @@ class LottieBuilder extends StatefulWidget {
this.fit, this.fit,
this.alignment, this.alignment,
String package, String package,
this.addRepaintBoundary,
}) : lottie = AssetLottie(name, }) : lottie = AssetLottie(name,
bundle: bundle, bundle: bundle,
package: package, package: package,
@ -130,6 +138,7 @@ class LottieBuilder extends StatefulWidget {
LottieBuilder.memory( LottieBuilder.memory(
Uint8List bytes, { Uint8List bytes, {
this.controller, this.controller,
this.frameRate,
this.animate, this.animate,
this.reverse, this.reverse,
this.repeat, this.repeat,
@ -143,6 +152,7 @@ class LottieBuilder extends StatefulWidget {
this.height, this.height,
this.fit, this.fit,
this.alignment, this.alignment,
this.addRepaintBoundary,
}) : lottie = }) : lottie =
MemoryLottie(bytes, imageProviderFactory: imageProviderFactory), MemoryLottie(bytes, imageProviderFactory: imageProviderFactory),
super(key: key); super(key: key);
@ -161,6 +171,11 @@ class LottieBuilder extends StatefulWidget {
/// Lottie animation. /// Lottie animation.
final Animation<double> controller; final Animation<double> controller;
/// The number of frames per second to render.
/// Use `FrameRate.composition` to use the original frame rate of the Lottie composition (default)
/// Use `FrameRate.max` to advance the animation progression at every frame.
final FrameRate frameRate;
/// If no controller is specified, this value indicate whether or not the /// If no controller is specified, this value indicate whether or not the
/// Lottie animation should be played automatically (default to true). /// Lottie animation should be played automatically (default to true).
/// If there is an animation controller specified, this property has no effect. /// If there is an animation controller specified, this property has no effect.
@ -321,6 +336,13 @@ class LottieBuilder extends StatefulWidget {
/// relative to text direction. /// relative to text direction.
final AlignmentGeometry alignment; final AlignmentGeometry alignment;
/// Indicate to automatically add a `RepaintBoundary` widget around the animation.
/// This allows to optimize the app performance by isolating the animation in its
/// own `Layer`.
///
/// This property is `true` by default.
final bool addRepaintBoundary;
@override @override
_LottieBuilderState createState() => _LottieBuilderState(); _LottieBuilderState createState() => _LottieBuilderState();
@ -384,6 +406,7 @@ class _LottieBuilderState extends State<LottieBuilder> {
Widget result = Lottie( Widget result = Lottie(
composition: composition, composition: composition,
controller: widget.controller, controller: widget.controller,
frameRate: widget.frameRate,
animate: widget.animate, animate: widget.animate,
reverse: widget.reverse, reverse: widget.reverse,
repeat: widget.repeat, repeat: widget.repeat,
@ -393,6 +416,7 @@ class _LottieBuilderState extends State<LottieBuilder> {
height: widget.height, height: widget.height,
fit: widget.fit, fit: widget.fit,
alignment: widget.alignment, alignment: widget.alignment,
addRepaintBoundary: widget.addRepaintBoundary,
); );
if (widget.frameBuilder != null) { if (widget.frameBuilder != null) {

View File

@ -3,7 +3,7 @@ import 'lottie_drawable.dart';
import 'value_delegate.dart'; import 'value_delegate.dart';
// TODO(xha): recognize style Bold, Medium, Regular, SemiBold, etc... // TODO(xha): recognize style Bold, Medium, Regular, SemiBold, etc...
TextStyle _defaultTextStyleDelegate(LottieFontStyle font) => TextStyle defaultTextStyleDelegate(LottieFontStyle font) =>
TextStyle(fontFamily: font.fontFamily); TextStyle(fontFamily: font.fontFamily);
@immutable @immutable
@ -49,7 +49,7 @@ class LottieDelegates {
this.text, this.text,
TextStyle Function(LottieFontStyle) textStyle, TextStyle Function(LottieFontStyle) textStyle,
this.values, this.values,
}) : textStyle = textStyle ?? _defaultTextStyleDelegate; }) : textStyle = textStyle;
@override @override
bool operator ==(Object other) => bool operator ==(Object other) =>

View File

@ -2,6 +2,7 @@ import 'dart:ui' as ui;
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:vector_math/vector_math_64.dart'; import 'package:vector_math/vector_math_64.dart';
import 'composition.dart'; import 'composition.dart';
import 'frame_rate.dart';
import 'lottie_delegates.dart'; import 'lottie_delegates.dart';
import 'model/key_path.dart'; import 'model/key_path.dart';
import 'model/layer/composition_layer.dart'; import 'model/layer/composition_layer.dart';
@ -43,26 +44,32 @@ class LottieDrawable {
_isDirty = true; _isDirty = true;
} }
double get progress => _progress; double get progress => _progress ?? 0.0;
double _progress = 0.0; double _progress;
bool setProgress(double value) { bool setProgress(double value, {FrameRate frameRate}) {
_isDirty = false; frameRate ??= FrameRate.composition;
_progress = value; var roundedProgress =
_compositionLayer.setProgress(value); composition.roundProgress(value, frameRate: frameRate);
return _isDirty; if (roundedProgress != _progress) {
_isDirty = false;
_progress = roundedProgress;
_compositionLayer.setProgress(roundedProgress);
return _isDirty;
} else {
return false;
}
} }
LottieDelegates get delegates => _delegates; LottieDelegates get delegates => _delegates;
set delegates(LottieDelegates delegates) { set delegates(LottieDelegates delegates) {
delegates ??= LottieDelegates();
if (_delegates != delegates) { if (_delegates != delegates) {
_delegates = delegates; _delegates = delegates;
_updateValueDelegates(delegates.values); _updateValueDelegates(delegates?.values);
} }
} }
bool get useTextGlyphs { bool get useTextGlyphs {
return delegates.text == null && composition.characters.isNotEmpty; return delegates?.text == null && composition.characters.isNotEmpty;
} }
ui.Image getImageAsset(String ref) { ui.Image getImageAsset(String ref) {
@ -75,8 +82,8 @@ class LottieDrawable {
} }
TextStyle getTextStyle(String font, String style) { TextStyle getTextStyle(String font, String style) {
return _delegates return (_delegates?.textStyle ?? defaultTextStyleDelegate)(
.textStyle(LottieFontStyle(fontFamily: font, style: style)); LottieFontStyle(fontFamily: font, style: style));
} }
List<ValueDelegate> _valueDelegates = <ValueDelegate>[]; List<ValueDelegate> _valueDelegates = <ValueDelegate>[];

View File

@ -232,7 +232,7 @@ class TextLayer extends BaseLayer {
return; return;
} }
var text = documentData.text; var text = documentData.text;
var textDelegate = lottieDrawable.delegates.text; var textDelegate = lottieDrawable.delegates?.text;
if (textDelegate != null) { if (textDelegate != null) {
text = textDelegate(text); text = textDelegate(text);
} }

View File

@ -1,25 +1,24 @@
import 'dart:io';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
import '../composition.dart'; import '../composition.dart';
import '../lottie_image_asset.dart'; import '../lottie_image_asset.dart';
import 'load_image.dart'; import 'load_image.dart';
import 'lottie_provider.dart'; import 'lottie_provider.dart';
import 'provider_io.dart' if (dart.library.html) 'provider_web.dart' as io;
class FileLottie extends LottieProvider { class FileLottie extends LottieProvider {
FileLottie(this.file, {LottieImageProviderFactory imageProviderFactory}) FileLottie(this.file, {LottieImageProviderFactory imageProviderFactory})
: super(imageProviderFactory: imageProviderFactory); : super(imageProviderFactory: imageProviderFactory);
final File file; final Object /*io.File|html.File*/ file;
@override @override
Future<LottieComposition> load() async { Future<LottieComposition> load() async {
var cacheKey = 'file-${file.path}'; var cacheKey = 'file-${io.filePath(file)}';
return sharedLottieCache.putIfAbsent(cacheKey, () async { return sharedLottieCache.putIfAbsent(cacheKey, () async {
var bytes = await file.readAsBytes(); var bytes = await io.loadFile(file);
var composition = await LottieComposition.fromBytes(bytes, var composition = await LottieComposition.fromBytes(bytes,
name: p.basenameWithoutExtension(file.path)); name: p.basenameWithoutExtension(io.filePath(file)));
for (var image in composition.images.values) { for (var image in composition.images.values) {
image.loadedImage ??= await _loadImage(composition, image); image.loadedImage ??= await _loadImage(composition, image);
@ -33,11 +32,7 @@ class FileLottie extends LottieProvider {
LottieComposition composition, LottieImageAsset lottieImage) { LottieComposition composition, LottieImageAsset lottieImage) {
var imageProvider = getImageProvider(lottieImage); var imageProvider = getImageProvider(lottieImage);
if (imageProvider == null) { imageProvider ??= io.loadImageForFile(file, lottieImage);
var imagePath = p.url.join(
p.dirname(file.path), lottieImage.dirName, lottieImage.fileName);
imageProvider = FileImage(File(imagePath));
}
return loadImage(composition, lottieImage, imageProvider); return loadImage(composition, lottieImage, imageProvider);
} }
@ -52,5 +47,5 @@ class FileLottie extends LottieProvider {
int get hashCode => file.hashCode; int get hashCode => file.hashCode;
@override @override
String toString() => '$runtimeType(file: ${file.path})'; String toString() => '$runtimeType(file: ${io.filePath(file)})';
} }

View File

@ -6,8 +6,7 @@ import '../composition.dart';
import '../lottie_image_asset.dart'; import '../lottie_image_asset.dart';
import 'load_image.dart'; import 'load_image.dart';
import 'lottie_provider.dart'; import 'lottie_provider.dart';
import 'network_provider_io.dart' import 'provider_io.dart' if (dart.library.html) 'provider_web.dart' as network;
if (dart.library.html) 'network_provider_web.dart' as network;
class NetworkLottie extends LottieProvider { class NetworkLottie extends LottieProvider {
NetworkLottie(this.url, NetworkLottie(this.url,
@ -22,7 +21,7 @@ class NetworkLottie extends LottieProvider {
var cacheKey = 'network-$url'; var cacheKey = 'network-$url';
return sharedLottieCache.putIfAbsent(cacheKey, () async { return sharedLottieCache.putIfAbsent(cacheKey, () async {
var resolved = Uri.base.resolve(url); var resolved = Uri.base.resolve(url);
var bytes = await network.load(resolved, headers: headers); var bytes = await network.loadHttp(resolved, headers: headers);
var composition = await LottieComposition.fromBytes(bytes, var composition = await LottieComposition.fromBytes(bytes,
name: p.url.basenameWithoutExtension(url)); name: p.url.basenameWithoutExtension(url));

View File

@ -1,16 +0,0 @@
import 'dart:html';
import 'dart:typed_data';
Future<Uint8List> load(Uri uri, {Map<String, String> headers}) async {
var request = await HttpRequest.request(uri.toString(),
requestHeaders: headers, responseType: 'blob');
var reader = FileReader();
reader.readAsArrayBuffer(request.response as Blob);
await reader.onLoadEnd.first;
if (reader.readyState != FileReader.DONE) {
throw Exception('Error while reading $uri');
}
return reader.result as Uint8List;
}

View File

@ -1,10 +1,13 @@
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:path/path.dart' as p;
import '../lottie_image_asset.dart';
final HttpClient _sharedHttpClient = HttpClient()..autoUncompress = false; final HttpClient _sharedHttpClient = HttpClient()..autoUncompress = false;
Future<Uint8List> load(Uri uri, {Map<String, String> headers}) async { Future<Uint8List> loadHttp(Uri uri, {Map<String, String> headers}) async {
var request = await _sharedHttpClient.getUrl(uri); var request = await _sharedHttpClient.getUrl(uri);
headers?.forEach((String name, String value) { headers?.forEach((String name, String value) {
request.headers.add(name, value); request.headers.add(name, value);
@ -21,3 +24,19 @@ Future<Uint8List> load(Uri uri, {Map<String, String> headers}) async {
return bytes; return bytes;
} }
Future<Uint8List> loadFile(Object file) {
return (file as File).readAsBytes();
}
String filePath(Object file) {
return (file as File).path;
}
ImageProvider loadImageForFile(Object file, LottieImageAsset lottieImage) {
var fileIo = file as File;
var imagePath = p.url
.join(p.dirname(fileIo.path), lottieImage.dirName, lottieImage.fileName);
return FileImage(File(imagePath));
}

View File

@ -0,0 +1,35 @@
import 'dart:html';
import 'dart:typed_data';
import 'package:flutter/rendering.dart';
import '../lottie_image_asset.dart';
Future<Uint8List> loadHttp(Uri uri, {Map<String, String> headers}) async {
var request = await HttpRequest.request(uri.toString(),
requestHeaders: headers, responseType: 'blob');
return _loadBlob(request.response as Blob);
}
Future<Uint8List> loadFile(Object file) {
return _loadBlob(file as File);
}
Future<Uint8List> _loadBlob(Blob file) async {
var reader = FileReader();
reader.readAsArrayBuffer(file);
await reader.onLoadEnd.first;
if (reader.readyState != FileReader.DONE) {
throw Exception('Error while reading blob');
}
return reader.result as Uint8List;
}
String filePath(Object file) {
return (file as File).relativePath;
}
ImageProvider loadImageForFile(Object file, LottieImageAsset lottieImage) {
return null;
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import '../lottie.dart'; import '../lottie.dart';
import 'frame_rate.dart';
import 'lottie_drawable.dart'; import 'lottie_drawable.dart';
import 'render_lottie.dart'; import 'render_lottie.dart';
@ -15,6 +16,7 @@ class RawLottie extends LeafRenderObjectWidget {
this.delegates, this.delegates,
this.options, this.options,
double progress, double progress,
this.frameRate,
this.width, this.width,
this.height, this.height,
this.fit, this.fit,
@ -34,6 +36,11 @@ class RawLottie extends LeafRenderObjectWidget {
/// The progress of the Lottie animation (between 0.0 and 1.0). /// The progress of the Lottie animation (between 0.0 and 1.0).
final double progress; final double progress;
/// The number of frames per second to render.
/// Use `FrameRate.composition` to use the original frame rate of the Lottie composition (default)
/// Use `FrameRate.max` to advance the animation progression at every frame.
final FrameRate frameRate;
/// If non-null, require the Lottie composition to have this width. /// If non-null, require the Lottie composition to have this width.
/// ///
/// If null, the composition will pick a size that best preserves its intrinsic /// If null, the composition will pick a size that best preserves its intrinsic
@ -76,6 +83,7 @@ class RawLottie extends LeafRenderObjectWidget {
delegates: delegates, delegates: delegates,
enableMergePaths: options?.enableMergePaths, enableMergePaths: options?.enableMergePaths,
progress: progress, progress: progress,
frameRate: frameRate,
width: width, width: width,
height: height, height: height,
fit: fit, fit: fit,
@ -88,6 +96,7 @@ class RawLottie extends LeafRenderObjectWidget {
renderObject renderObject
..setComposition(composition, ..setComposition(composition,
progress: progress, progress: progress,
frameRate: frameRate,
delegates: delegates, delegates: delegates,
enableMergePaths: options?.enableMergePaths) enableMergePaths: options?.enableMergePaths)
..width = width ..width = width

View File

@ -1,6 +1,7 @@
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import '../lottie.dart'; import '../lottie.dart';
import 'frame_rate.dart';
import 'lottie_drawable.dart'; import 'lottie_drawable.dart';
/// A Lottie animation in the render tree. /// A Lottie animation in the render tree.
@ -9,10 +10,11 @@ import 'lottie_drawable.dart';
/// constraints and preserves the composition's intrinsic aspect ratio. /// constraints and preserves the composition's intrinsic aspect ratio.
class RenderLottie extends RenderBox { class RenderLottie extends RenderBox {
RenderLottie({ RenderLottie({
LottieComposition composition, @required LottieComposition composition,
LottieDelegates delegates, LottieDelegates delegates,
bool enableMergePaths, bool enableMergePaths,
double progress = 0.0, double progress = 0.0,
FrameRate frameRate,
double width, double width,
double height, double height,
BoxFit fit, BoxFit fit,
@ -21,7 +23,7 @@ class RenderLottie extends RenderBox {
assert(progress != null && progress >= 0.0 && progress <= 1.0), assert(progress != null && progress >= 0.0 && progress <= 1.0),
_drawable = composition != null _drawable = composition != null
? (LottieDrawable(composition, enableMergePaths: enableMergePaths) ? (LottieDrawable(composition, enableMergePaths: enableMergePaths)
..setProgress(progress) ..setProgress(progress, frameRate: frameRate)
..delegates = delegates) ..delegates = delegates)
: null, : null,
_width = width, _width = width,
@ -34,6 +36,7 @@ class RenderLottie extends RenderBox {
LottieDrawable _drawable; LottieDrawable _drawable;
void setComposition(LottieComposition composition, void setComposition(LottieComposition composition,
{@required double progress, {@required double progress,
@required FrameRate frameRate,
@required LottieDelegates delegates, @required LottieDelegates delegates,
bool enableMergePaths}) { bool enableMergePaths}) {
enableMergePaths ??= false; enableMergePaths ??= false;
@ -41,9 +44,11 @@ class RenderLottie extends RenderBox {
var needsLayout = false; var needsLayout = false;
var needsPaint = false; var needsPaint = false;
if (composition == null) { if (composition == null) {
_drawable = null; if (_drawable != null) {
needsPaint = true; _drawable = null;
needsLayout = true; needsPaint = true;
needsLayout = true;
}
} else { } else {
if (_drawable == null || if (_drawable == null ||
_drawable.composition != composition || _drawable.composition != composition ||
@ -54,7 +59,7 @@ class RenderLottie extends RenderBox {
needsPaint = true; needsPaint = true;
} }
needsPaint |= _drawable.setProgress(progress); needsPaint |= _drawable.setProgress(progress, frameRate: frameRate);
if (_drawable.delegates != delegates) { if (_drawable.delegates != delegates) {
_drawable.delegates = delegates; _drawable.delegates = delegates;

View File

@ -7,14 +7,14 @@ packages:
name: _fe_analyzer_shared name: _fe_analyzer_shared
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.0.0" version: "6.0.0"
analyzer: analyzer:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: analyzer name: analyzer
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.39.12" version: "0.39.15"
archive: archive:
dependency: "direct main" dependency: "direct main"
description: description:
@ -35,7 +35,7 @@ packages:
name: async name: async
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.4.1" version: "2.4.2"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -49,7 +49,7 @@ packages:
name: characters name: characters
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.0" version: "1.1.0-nullsafety"
charcode: charcode:
dependency: "direct main" dependency: "direct main"
description: description:
@ -57,6 +57,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.3" version: "1.1.3"
cli_util:
dependency: transitive
description:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.4"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -70,7 +77,7 @@ packages:
name: collection name: collection
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.14.12" version: "1.15.0-nullsafety"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@ -91,7 +98,7 @@ packages:
name: csslib name: csslib
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.16.1" version: "0.16.2"
dart_style: dart_style:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -150,14 +157,14 @@ packages:
name: matcher name: matcher
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.12.6" version: "0.12.8"
meta: meta:
dependency: "direct main" dependency: "direct main"
description: description:
name: meta name: meta
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.8" version: "1.3.0-nullsafety"
mockito: mockito:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -199,7 +206,7 @@ packages:
name: pedantic name: pedantic
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.0" version: "1.9.2"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
@ -225,7 +232,7 @@ packages:
name: stack_trace name: stack_trace
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.3" version: "1.9.5"
stream_channel: stream_channel:
dependency: transitive dependency: transitive
description: description:
@ -253,21 +260,21 @@ packages:
name: test_api name: test_api
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.2.16" version: "0.2.17"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.1.6" version: "1.3.0-nullsafety"
vector_math: vector_math:
dependency: "direct main" dependency: "direct main"
description: description:
name: vector_math name: vector_math
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.8" version: "2.1.0-nullsafety"
watcher: watcher:
dependency: transitive dependency: transitive
description: description:
@ -283,4 +290,4 @@ packages:
source: hosted source: hosted
version: "2.2.1" version: "2.2.1"
sdks: sdks:
dart: ">=2.7.0 <3.0.0" dart: ">=2.9.0-18.0 <2.9.0"

View File

@ -1,6 +1,6 @@
name: lottie name: lottie
description: Render After Effects animations natively on Flutter. This package is a pure Dart implementation of a Lottie player. description: Render After Effects animations natively on Flutter. This package is a pure Dart implementation of a Lottie player.
version: 0.5.0 version: 0.6.0
homepage: https://github.com/xvrh/lottie-flutter homepage: https://github.com/xvrh/lottie-flutter
environment: environment:

14
test/frame_rate_test.dart Normal file
View File

@ -0,0 +1,14 @@
import 'dart:io';
import 'package:flutter_test/flutter_test.dart';
import 'package:lottie/lottie.dart';
void main() {
test('Frame rate round', () async {
var composition = await LottieComposition.fromBytes(
File('example/assets/LottieLogo1.json').readAsBytesSync());
expect(composition.roundProgress(0, frameRate: FrameRate.composition), 0);
expect(
composition.roundProgress(0.0001, frameRate: FrameRate.composition), 0);
expect(composition.roundProgress(0.0001, frameRate: FrameRate.max), 0.0001);
});
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 31 KiB