Compare commits

..

1 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
34 changed files with 411 additions and 254 deletions

View File

@ -1,3 +1,14 @@
## [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

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.
```dart
import 'example/lib/examples/main.dart';
import 'example/lib/main.dart';
```
### 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: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());
}
void main() => runApp(MyApp());
class App extends StatelessWidget {
class MyApp 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(
body: ListView(
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();
}
});
},
),
],
),
),
// 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'),
],
),
),

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
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_macos (from `Flutter/ephemeral/.symlinks/plugins/path_provider_macos/macos`)
EXTERNAL SOURCES:
FlutterMacOS:
:path: Flutter/ephemeral/.symlinks/flutter/darwin-x64
:path: Flutter/ephemeral/.symlinks/flutter/darwin-x64-profile
path_provider:
:path: Flutter/ephemeral/.symlinks/plugins/path_provider/macos
path_provider_macos:

View File

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

View File

@ -33,6 +33,7 @@ void main() {
composition: composition,
controller: animation,
delegates: LottieDelegates(values: [delegate]),
addRepaintBoundary: false,
),
);
await tester.pump();
@ -45,6 +46,7 @@ void main() {
composition: composition,
controller: animation,
delegates: LottieDelegates(values: []),
addRepaintBoundary: false,
),
);
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;
drawable
..setProgress(progress)
..setProgress(progress, frameRate: FrameRate.max)
..draw(canvas, rect);
++index;

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import 'dart:typed_data';
import 'package:archive/archive.dart';
import 'package:flutter/widgets.dart';
import 'package:path/path.dart' as p;
import 'frame_rate.dart';
import 'logger.dart';
import 'lottie_image_asset.dart';
import 'model/font.dart';
@ -133,9 +134,11 @@ class LottieComposition {
Rectangle<int> get bounds => _bounds;
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 endFrame => _endFrame;
@ -176,6 +179,24 @@ class LottieComposition {
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
String toString() {
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

@ -2,6 +2,7 @@ import 'dart:typed_data';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import '../lottie.dart';
import 'frame_rate.dart';
import 'l.dart';
import 'lottie_builder.dart';
import 'options.dart';
@ -22,19 +23,24 @@ class Lottie extends StatefulWidget {
this.alignment,
this.fit,
bool animate,
this.frameRate,
bool repeat,
bool reverse,
this.delegates,
this.options,
bool addRepaintBoundary,
}) : animate = animate ?? true,
reverse = reverse ?? false,
repeat = repeat ?? true,
addRepaintBoundary = addRepaintBoundary ?? true,
super(key: key);
/// Creates a widget that displays an [LottieComposition] obtained from an [AssetBundle].
static LottieBuilder asset(String name,
{Animation<double> controller,
static LottieBuilder asset(
String name, {
Animation<double> controller,
bool animate,
FrameRate frameRate,
bool repeat,
bool reverse,
LottieDelegates delegates,
@ -48,10 +54,13 @@ class Lottie extends StatefulWidget {
double height,
BoxFit fit,
Alignment alignment,
String package}) =>
String package,
bool addRepaintBoundary,
}) =>
LottieBuilder.asset(
name,
controller: controller,
frameRate: frameRate,
animate: animate,
repeat: repeat,
reverse: reverse,
@ -67,12 +76,14 @@ class Lottie extends StatefulWidget {
fit: fit,
alignment: alignment,
package: package,
addRepaintBoundary: addRepaintBoundary,
);
/// Creates a widget that displays an [LottieComposition] obtained from a [File].
static LottieBuilder file(
Object /*io.File|html.File*/ file, {
Animation<double> controller,
FrameRate frameRate,
bool animate,
bool repeat,
bool reverse,
@ -86,10 +97,12 @@ class Lottie extends StatefulWidget {
double height,
BoxFit fit,
Alignment alignment,
bool addRepaintBoundary,
}) =>
LottieBuilder.file(
file,
controller: controller,
frameRate: frameRate,
animate: animate,
repeat: repeat,
reverse: reverse,
@ -103,12 +116,14 @@ class Lottie extends StatefulWidget {
height: height,
fit: fit,
alignment: alignment,
addRepaintBoundary: addRepaintBoundary,
);
/// Creates a widget that displays an [LottieComposition] obtained from a [Uint8List].
static LottieBuilder memory(
Uint8List bytes, {
Animation<double> controller,
FrameRate frameRate,
bool animate,
bool repeat,
bool reverse,
@ -122,10 +137,12 @@ class Lottie extends StatefulWidget {
double height,
BoxFit fit,
Alignment alignment,
bool addRepaintBoundary,
}) =>
LottieBuilder.memory(
bytes,
controller: controller,
frameRate: frameRate,
animate: animate,
repeat: repeat,
reverse: reverse,
@ -139,12 +156,14 @@ class Lottie extends StatefulWidget {
height: height,
fit: fit,
alignment: alignment,
addRepaintBoundary: addRepaintBoundary,
);
/// Creates a widget that displays an [LottieComposition] obtained from the network.
static LottieBuilder network(
String url, {
Animation<double> controller,
FrameRate frameRate,
bool animate,
bool repeat,
bool reverse,
@ -158,10 +177,12 @@ class Lottie extends StatefulWidget {
double height,
BoxFit fit,
Alignment alignment,
bool addRepaintBoundary,
}) =>
LottieBuilder.network(
url,
controller: controller,
frameRate: frameRate,
animate: animate,
repeat: repeat,
reverse: reverse,
@ -175,6 +196,7 @@ class Lottie extends StatefulWidget {
height: height,
fit: fit,
alignment: alignment,
addRepaintBoundary: addRepaintBoundary,
);
/// The Lottie composition to animate.
@ -186,6 +208,14 @@ class Lottie extends StatefulWidget {
/// with the properties [animate], [reverse]
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
/// Lottie animation should be played automatically (default to true).
/// If there is an animation controller specified, this property has no effect.
@ -249,6 +279,13 @@ class Lottie extends StatefulWidget {
/// - enableMergePaths: Enable merge path support
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 set traceEnabled(bool enabled) {
L.traceEnabled = enabled;
@ -303,18 +340,27 @@ class _LottieState extends State<Lottie> with TickerProviderStateMixin {
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
Widget child = AnimatedBuilder(
animation: _progressAnimation,
builder: (context, _) => RawLottie(
builder: (context, _) {
return RawLottie(
composition: widget.composition,
delegates: widget.delegates,
options: widget.options,
progress: _progressAnimation.value,
frameRate: widget.frameRate,
width: widget.width,
height: widget.height,
fit: widget.fit,
alignment: widget.alignment,
),
);
},
);
if (widget.addRepaintBoundary) {
child = RepaintBoundary(child: child);
}
return child;
}
}

View File

@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import '../lottie.dart';
import 'frame_rate.dart';
import 'lottie.dart';
import 'providers/asset_provider.dart';
import 'providers/file_provider.dart';
@ -35,6 +36,7 @@ class LottieBuilder extends StatefulWidget {
Key key,
@required this.lottie,
this.controller,
this.frameRate,
this.animate,
this.reverse,
this.repeat,
@ -46,6 +48,7 @@ class LottieBuilder extends StatefulWidget {
this.height,
this.fit,
this.alignment,
this.addRepaintBoundary,
}) : assert(lottie != null),
super(key: key);
@ -54,6 +57,7 @@ class LottieBuilder extends StatefulWidget {
String src, {
Map<String, String> headers,
this.controller,
this.frameRate,
this.animate,
this.reverse,
this.repeat,
@ -67,6 +71,7 @@ class LottieBuilder extends StatefulWidget {
this.height,
this.fit,
this.alignment,
this.addRepaintBoundary,
}) : lottie = NetworkLottie(src,
headers: headers, imageProviderFactory: imageProviderFactory),
super(key: key);
@ -84,6 +89,7 @@ class LottieBuilder extends StatefulWidget {
LottieBuilder.file(
Object /*io.File|html.File*/ file, {
this.controller,
this.frameRate,
this.animate,
this.reverse,
this.repeat,
@ -97,6 +103,7 @@ class LottieBuilder extends StatefulWidget {
this.height,
this.fit,
this.alignment,
this.addRepaintBoundary,
}) : lottie = FileLottie(file, imageProviderFactory: imageProviderFactory),
super(key: key);
@ -104,6 +111,7 @@ class LottieBuilder extends StatefulWidget {
LottieBuilder.asset(
String name, {
this.controller,
this.frameRate,
this.animate,
this.reverse,
this.repeat,
@ -119,6 +127,7 @@ class LottieBuilder extends StatefulWidget {
this.fit,
this.alignment,
String package,
this.addRepaintBoundary,
}) : lottie = AssetLottie(name,
bundle: bundle,
package: package,
@ -129,6 +138,7 @@ class LottieBuilder extends StatefulWidget {
LottieBuilder.memory(
Uint8List bytes, {
this.controller,
this.frameRate,
this.animate,
this.reverse,
this.repeat,
@ -142,6 +152,7 @@ class LottieBuilder extends StatefulWidget {
this.height,
this.fit,
this.alignment,
this.addRepaintBoundary,
}) : lottie =
MemoryLottie(bytes, imageProviderFactory: imageProviderFactory),
super(key: key);
@ -160,6 +171,11 @@ class LottieBuilder extends StatefulWidget {
/// Lottie animation.
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
/// Lottie animation should be played automatically (default to true).
/// If there is an animation controller specified, this property has no effect.
@ -320,6 +336,13 @@ class LottieBuilder extends StatefulWidget {
/// relative to text direction.
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
_LottieBuilderState createState() => _LottieBuilderState();
@ -383,6 +406,7 @@ class _LottieBuilderState extends State<LottieBuilder> {
Widget result = Lottie(
composition: composition,
controller: widget.controller,
frameRate: widget.frameRate,
animate: widget.animate,
reverse: widget.reverse,
repeat: widget.repeat,
@ -392,6 +416,7 @@ class _LottieBuilderState extends State<LottieBuilder> {
height: widget.height,
fit: widget.fit,
alignment: widget.alignment,
addRepaintBoundary: widget.addRepaintBoundary,
);
if (widget.frameBuilder != null) {

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import '../lottie.dart';
import 'frame_rate.dart';
import 'lottie_drawable.dart';
import 'render_lottie.dart';
@ -15,6 +16,7 @@ class RawLottie extends LeafRenderObjectWidget {
this.delegates,
this.options,
double progress,
this.frameRate,
this.width,
this.height,
this.fit,
@ -34,6 +36,11 @@ class RawLottie extends LeafRenderObjectWidget {
/// The progress of the Lottie animation (between 0.0 and 1.0).
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 null, the composition will pick a size that best preserves its intrinsic
@ -76,6 +83,7 @@ class RawLottie extends LeafRenderObjectWidget {
delegates: delegates,
enableMergePaths: options?.enableMergePaths,
progress: progress,
frameRate: frameRate,
width: width,
height: height,
fit: fit,
@ -88,6 +96,7 @@ class RawLottie extends LeafRenderObjectWidget {
renderObject
..setComposition(composition,
progress: progress,
frameRate: frameRate,
delegates: delegates,
enableMergePaths: options?.enableMergePaths)
..width = width

View File

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

View File

@ -7,14 +7,14 @@ packages:
name: _fe_analyzer_shared
url: "https://pub.dartlang.org"
source: hosted
version: "5.0.0"
version: "6.0.0"
analyzer:
dependency: "direct dev"
description:
name: analyzer
url: "https://pub.dartlang.org"
source: hosted
version: "0.39.12"
version: "0.39.15"
archive:
dependency: "direct main"
description:
@ -35,7 +35,7 @@ packages:
name: async
url: "https://pub.dartlang.org"
source: hosted
version: "2.4.1"
version: "2.4.2"
boolean_selector:
dependency: transitive
description:
@ -49,7 +49,7 @@ packages:
name: characters
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.0"
version: "1.1.0-nullsafety"
charcode:
dependency: "direct main"
description:
@ -57,6 +57,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.3"
cli_util:
dependency: transitive
description:
name: cli_util
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.4"
clock:
dependency: transitive
description:
@ -70,7 +77,7 @@ packages:
name: collection
url: "https://pub.dartlang.org"
source: hosted
version: "1.14.12"
version: "1.15.0-nullsafety"
convert:
dependency: transitive
description:
@ -91,7 +98,7 @@ packages:
name: csslib
url: "https://pub.dartlang.org"
source: hosted
version: "0.16.1"
version: "0.16.2"
dart_style:
dependency: "direct dev"
description:
@ -150,14 +157,14 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.6"
version: "0.12.8"
meta:
dependency: "direct main"
description:
name: meta
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.8"
version: "1.3.0-nullsafety"
mockito:
dependency: "direct dev"
description:
@ -199,7 +206,7 @@ packages:
name: pedantic
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.0"
version: "1.9.2"
pub_semver:
dependency: transitive
description:
@ -225,7 +232,7 @@ packages:
name: stack_trace
url: "https://pub.dartlang.org"
source: hosted
version: "1.9.3"
version: "1.9.5"
stream_channel:
dependency: transitive
description:
@ -253,21 +260,21 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.2.16"
version: "0.2.17"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.6"
version: "1.3.0-nullsafety"
vector_math:
dependency: "direct main"
description:
name: vector_math
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.8"
version: "2.1.0-nullsafety"
watcher:
dependency: transitive
description:
@ -283,4 +290,4 @@ packages:
source: hosted
version: "2.2.1"
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
description: Render After Effects animations natively on Flutter. This package is a pure Dart implementation of a Lottie player.
version: 0.5.1
version: 0.6.0
homepage: https://github.com/xvrh/lottie-flutter
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