update lottie
|
|
@ -9,7 +9,7 @@ hashlib: bc9c2f8dd7bbc72f47ccab0ce1111d40259c49bc
|
||||||
hashlib_codecs: 2a966c37c3b9b1f5541ae88e99ab34acf3fc968b
|
hashlib_codecs: 2a966c37c3b9b1f5541ae88e99ab34acf3fc968b
|
||||||
introduction_screen: 4a90e557630b28834479ed9c64a9d2d0185d8e48
|
introduction_screen: 4a90e557630b28834479ed9c64a9d2d0185d8e48
|
||||||
libsignal_protocol_dart: c95a1586057022acdbb9c76b1692d94cc549bcc7
|
libsignal_protocol_dart: c95a1586057022acdbb9c76b1692d94cc549bcc7
|
||||||
lottie: 4f1a5a52bdf1e1c1e12fa97c96174dcb05419e19
|
lottie: 127bc29f2c6bd8b32ec4064a09e54e6b31cd0a88
|
||||||
mutex: 84ca903a3ac863735e3228c75a212133621f680f
|
mutex: 84ca903a3ac863735e3228c75a212133621f680f
|
||||||
no_screenshot: fbfa2ed7ec4db782797fa6a7de8f207a2cba00bb
|
no_screenshot: fbfa2ed7ec4db782797fa6a7de8f207a2cba00bb
|
||||||
optional: 71c638891ce4f2aff35c7387727989f31f9d877d
|
optional: 71c638891ce4f2aff35c7387727989f31f9d877d
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ abstract class BaseStrokeContent
|
||||||
var alpha = ((parentAlpha / 255.0 * _opacityAnimation.value / 100.0) * 255)
|
var alpha = ((parentAlpha / 255.0 * _opacityAnimation.value / 100.0) * 255)
|
||||||
.round();
|
.round();
|
||||||
paint.setAlpha(alpha.clamp(0, 255));
|
paint.setAlpha(alpha.clamp(0, 255));
|
||||||
paint.strokeWidth = _widthAnimation.value * parentMatrix.getScale();
|
paint.strokeWidth = _widthAnimation.value;
|
||||||
if (paint.strokeWidth <= 0) {
|
if (paint.strokeWidth <= 0) {
|
||||||
// Android draws a hairline stroke for 0, After Effects doesn't.
|
// Android draws a hairline stroke for 0, After Effects doesn't.
|
||||||
L.endSection('StrokeContent#draw');
|
L.endSection('StrokeContent#draw');
|
||||||
|
|
@ -172,20 +172,19 @@ abstract class BaseStrokeContent
|
||||||
_blurMaskFilterRadius = blurRadius;
|
_blurMaskFilterRadius = blurRadius;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Concat the parent matrix instead of scaling the stroke width, so skew affects the stroke.
|
||||||
|
canvas.save();
|
||||||
|
canvas.transform(parentMatrix.storage);
|
||||||
for (var i = 0; i < _pathGroups.length; i++) {
|
for (var i = 0; i < _pathGroups.length; i++) {
|
||||||
var pathGroup = _pathGroups[i];
|
var pathGroup = _pathGroups[i];
|
||||||
|
|
||||||
if (pathGroup.trimPath != null) {
|
if (pathGroup.trimPath != null) {
|
||||||
_applyTrimPath(canvas, pathGroup, parentMatrix);
|
_applyTrimPath(canvas, pathGroup);
|
||||||
} else {
|
} else {
|
||||||
L.beginSection('StrokeContent#buildPath');
|
L.beginSection('StrokeContent#buildPath');
|
||||||
_path.reset();
|
_path.reset();
|
||||||
for (var j = pathGroup.paths.length - 1; j >= 0; j--) {
|
for (var j = pathGroup.paths.length - 1; j >= 0; j--) {
|
||||||
_path.addPath(
|
_path.addPath(pathGroup.paths[j].getPath(), Offset.zero);
|
||||||
pathGroup.paths[j].getPath(),
|
|
||||||
Offset.zero,
|
|
||||||
matrix4: parentMatrix.storage,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
L.endSection('StrokeContent#buildPath');
|
L.endSection('StrokeContent#buildPath');
|
||||||
L.beginSection('StrokeContent#drawPath');
|
L.beginSection('StrokeContent#drawPath');
|
||||||
|
|
@ -193,18 +192,15 @@ abstract class BaseStrokeContent
|
||||||
if (dropShadow != null) {
|
if (dropShadow != null) {
|
||||||
dropShadow.draw(canvas, _path);
|
dropShadow.draw(canvas, _path);
|
||||||
}
|
}
|
||||||
canvas.drawPath(_withDashPattern(_path, parentMatrix), paint);
|
canvas.drawPath(_withDashPattern(_path), paint);
|
||||||
L.endSection('StrokeContent#drawPath');
|
L.endSection('StrokeContent#drawPath');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
canvas.restore();
|
||||||
L.endSection('StrokeContent#draw');
|
L.endSection('StrokeContent#draw');
|
||||||
}
|
}
|
||||||
|
|
||||||
void _applyTrimPath(
|
void _applyTrimPath(Canvas canvas, _PathGroup pathGroup) {
|
||||||
Canvas canvas,
|
|
||||||
_PathGroup pathGroup,
|
|
||||||
Matrix4 parentMatrix,
|
|
||||||
) {
|
|
||||||
L.beginSection('StrokeContent#applyTrimPath');
|
L.beginSection('StrokeContent#applyTrimPath');
|
||||||
var trimPath = pathGroup.trimPath;
|
var trimPath = pathGroup.trimPath;
|
||||||
if (trimPath == null) {
|
if (trimPath == null) {
|
||||||
|
|
@ -213,11 +209,7 @@ abstract class BaseStrokeContent
|
||||||
}
|
}
|
||||||
_path.reset();
|
_path.reset();
|
||||||
for (var j = pathGroup.paths.length - 1; j >= 0; j--) {
|
for (var j = pathGroup.paths.length - 1; j >= 0; j--) {
|
||||||
_path.addPath(
|
_path.addPath(pathGroup.paths[j].getPath(), Offset.zero);
|
||||||
pathGroup.paths[j].getPath(),
|
|
||||||
Offset.zero,
|
|
||||||
matrix4: parentMatrix.storage,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
var animStartValue = trimPath.start.value / 100;
|
var animStartValue = trimPath.start.value / 100;
|
||||||
var animEndValue = trimPath.end.value / 100;
|
var animEndValue = trimPath.end.value / 100;
|
||||||
|
|
@ -242,9 +234,7 @@ abstract class BaseStrokeContent
|
||||||
|
|
||||||
var currentLength = 0.0;
|
var currentLength = 0.0;
|
||||||
for (var j = pathGroup.paths.length - 1; j >= 0; j--) {
|
for (var j = pathGroup.paths.length - 1; j >= 0; j--) {
|
||||||
_trimPathPath.set(
|
_trimPathPath.set(pathGroup.paths[j].getPath());
|
||||||
pathGroup.paths[j].getPath().transform(parentMatrix.storage),
|
|
||||||
);
|
|
||||||
var pathMetrics = _trimPathPath.computeMetrics().toList();
|
var pathMetrics = _trimPathPath.computeMetrics().toList();
|
||||||
var length = pathMetrics.isNotEmpty ? pathMetrics.first.length : 0;
|
var length = pathMetrics.isNotEmpty ? pathMetrics.first.length : 0;
|
||||||
if (endLength > totalLength &&
|
if (endLength > totalLength &&
|
||||||
|
|
@ -260,13 +250,13 @@ abstract class BaseStrokeContent
|
||||||
}
|
}
|
||||||
var endValue = min((endLength - totalLength) / length, 1).toDouble();
|
var endValue = min((endLength - totalLength) / length, 1).toDouble();
|
||||||
Utils.applyTrimPathIfNeeded(_trimPathPath, startValue, endValue, 0.0);
|
Utils.applyTrimPathIfNeeded(_trimPathPath, startValue, endValue, 0.0);
|
||||||
canvas.drawPath(_withDashPattern(_trimPathPath, parentMatrix), paint);
|
canvas.drawPath(_withDashPattern(_trimPathPath), paint);
|
||||||
} else if (currentLength + length < startLength ||
|
} else if (currentLength + length < startLength ||
|
||||||
currentLength > endLength) {
|
currentLength > endLength) {
|
||||||
// Do nothing
|
// Do nothing
|
||||||
} else if (currentLength + length <= endLength &&
|
} else if (currentLength + length <= endLength &&
|
||||||
startLength < currentLength) {
|
startLength < currentLength) {
|
||||||
canvas.drawPath(_withDashPattern(_trimPathPath, parentMatrix), paint);
|
canvas.drawPath(_withDashPattern(_trimPathPath), paint);
|
||||||
} else {
|
} else {
|
||||||
double startValue;
|
double startValue;
|
||||||
if (startLength < currentLength) {
|
if (startLength < currentLength) {
|
||||||
|
|
@ -281,7 +271,7 @@ abstract class BaseStrokeContent
|
||||||
endValue = (endLength - currentLength) / length;
|
endValue = (endLength - currentLength) / length;
|
||||||
}
|
}
|
||||||
Utils.applyTrimPathIfNeeded(_trimPathPath, startValue, endValue, 0);
|
Utils.applyTrimPathIfNeeded(_trimPathPath, startValue, endValue, 0);
|
||||||
canvas.drawPath(_withDashPattern(_trimPathPath, parentMatrix), paint);
|
canvas.drawPath(_withDashPattern(_trimPathPath), paint);
|
||||||
}
|
}
|
||||||
currentLength += length;
|
currentLength += length;
|
||||||
}
|
}
|
||||||
|
|
@ -312,14 +302,13 @@ abstract class BaseStrokeContent
|
||||||
return bounds;
|
return bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
Path _withDashPattern(Path path, Matrix4 parentMatrix) {
|
Path _withDashPattern(Path path) {
|
||||||
L.beginSection('StrokeContent#applyDashPattern');
|
L.beginSection('StrokeContent#applyDashPattern');
|
||||||
if (_dashPatternAnimations.isEmpty) {
|
if (_dashPatternAnimations.isEmpty) {
|
||||||
L.endSection('StrokeContent#applyDashPattern');
|
L.endSection('StrokeContent#applyDashPattern');
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
var scale = parentMatrix.getScale();
|
|
||||||
for (var i = 0; i < _dashPatternAnimations.length; i++) {
|
for (var i = 0; i < _dashPatternAnimations.length; i++) {
|
||||||
_dashPatternValues[i] = _dashPatternAnimations[i].value;
|
_dashPatternValues[i] = _dashPatternAnimations[i].value;
|
||||||
// If the value of the dash pattern or gap is too small, the number of individual sections
|
// If the value of the dash pattern or gap is too small, the number of individual sections
|
||||||
|
|
@ -335,12 +324,11 @@ abstract class BaseStrokeContent
|
||||||
_dashPatternValues[i] = 0.1;
|
_dashPatternValues[i] = 0.1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_dashPatternValues[i] *= scale;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var offset = _dashPatternOffsetAnimation == null
|
var offset = _dashPatternOffsetAnimation == null
|
||||||
? 0.0
|
? 0.0
|
||||||
: _dashPatternOffsetAnimation.value * scale;
|
: _dashPatternOffsetAnimation.value;
|
||||||
var newPath = dashPath(path, intervals: _dashPatternValues, phase: offset);
|
var newPath = dashPath(path, intervals: _dashPatternValues, phase: offset);
|
||||||
L.endSection('StrokeContent#applyDashPattern');
|
L.endSection('StrokeContent#applyDashPattern');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,8 +73,8 @@ class ContentGroup implements DrawingContent, PathContent, KeyPathElement {
|
||||||
this.name,
|
this.name,
|
||||||
this._contents,
|
this._contents,
|
||||||
AnimatableTransform? transform, {
|
AnimatableTransform? transform, {
|
||||||
required bool hidden,
|
required this._hidden,
|
||||||
}) : _hidden = hidden {
|
}) {
|
||||||
if (transform != null) {
|
if (transform != null) {
|
||||||
_transformAnimation = transform.createAnimation()
|
_transformAnimation = transform.createAnimation()
|
||||||
..addAnimationsToLayer(layer)
|
..addAnimationsToLayer(layer)
|
||||||
|
|
|
||||||
|
|
@ -22,9 +22,7 @@ import 'path_content.dart';
|
||||||
class FillContent implements DrawingContent, KeyPathElementContent {
|
class FillContent implements DrawingContent, KeyPathElementContent {
|
||||||
final Path _path = Path();
|
final Path _path = Path();
|
||||||
final BaseLayer layer;
|
final BaseLayer layer;
|
||||||
@override
|
final ShapeFill _fill;
|
||||||
final String? name;
|
|
||||||
final bool _hidden;
|
|
||||||
final List<PathContent> _paths = <PathContent>[];
|
final List<PathContent> _paths = <PathContent>[];
|
||||||
late final BaseKeyframeAnimation<Color, Color> _colorAnimation;
|
late final BaseKeyframeAnimation<Color, Color> _colorAnimation;
|
||||||
late final BaseKeyframeAnimation<int, int> _opacityAnimation;
|
late final BaseKeyframeAnimation<int, int> _opacityAnimation;
|
||||||
|
|
@ -34,9 +32,10 @@ class FillContent implements DrawingContent, KeyPathElementContent {
|
||||||
double _blurMaskFilterRadius = 0;
|
double _blurMaskFilterRadius = 0;
|
||||||
DropShadowKeyframeAnimation? dropShadowAnimation;
|
DropShadowKeyframeAnimation? dropShadowAnimation;
|
||||||
|
|
||||||
FillContent(this.lottieDrawable, this.layer, ShapeFill fill)
|
@override
|
||||||
: name = fill.name,
|
String? get name => _fill.name;
|
||||||
_hidden = fill.hidden {
|
|
||||||
|
FillContent(this.lottieDrawable, this.layer, this._fill) {
|
||||||
var blurEffect = layer.blurEffect;
|
var blurEffect = layer.blurEffect;
|
||||||
if (blurEffect != null) {
|
if (blurEffect != null) {
|
||||||
_blurAnimation = blurEffect.blurriness.createAnimation()
|
_blurAnimation = blurEffect.blurriness.createAnimation()
|
||||||
|
|
@ -52,16 +51,14 @@ class FillContent implements DrawingContent, KeyPathElementContent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fill.color == null || fill.opacity == null) {
|
if (_fill.color == null || _fill.opacity == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_path.fillType = fill.fillType;
|
_colorAnimation = _fill.color!.createAnimation();
|
||||||
|
|
||||||
_colorAnimation = fill.color!.createAnimation();
|
|
||||||
_colorAnimation.addUpdateListener(onValueChanged);
|
_colorAnimation.addUpdateListener(onValueChanged);
|
||||||
layer.addAnimation(_colorAnimation);
|
layer.addAnimation(_colorAnimation);
|
||||||
_opacityAnimation = fill.opacity!.createAnimation();
|
_opacityAnimation = _fill.opacity!.createAnimation();
|
||||||
_opacityAnimation.addUpdateListener(onValueChanged);
|
_opacityAnimation.addUpdateListener(onValueChanged);
|
||||||
layer.addAnimation(_opacityAnimation);
|
layer.addAnimation(_opacityAnimation);
|
||||||
}
|
}
|
||||||
|
|
@ -82,7 +79,7 @@ class FillContent implements DrawingContent, KeyPathElementContent {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void draw(Canvas canvas, Matrix4 parentMatrix, {required int parentAlpha}) {
|
void draw(Canvas canvas, Matrix4 parentMatrix, {required int parentAlpha}) {
|
||||||
if (_hidden) {
|
if (_fill.hidden) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
L.beginSection('FillContent#draw');
|
L.beginSection('FillContent#draw');
|
||||||
|
|
@ -112,6 +109,7 @@ class FillContent implements DrawingContent, KeyPathElementContent {
|
||||||
}
|
}
|
||||||
|
|
||||||
_path.reset();
|
_path.reset();
|
||||||
|
_path.fillType = _fill.fillType;
|
||||||
for (var i = 0; i < _paths.length; i++) {
|
for (var i = 0; i < _paths.length; i++) {
|
||||||
_path.addPath(_paths[i].getPath(), Offset.zero);
|
_path.addPath(_paths[i].getPath(), Offset.zero);
|
||||||
}
|
}
|
||||||
|
|
@ -131,6 +129,7 @@ class FillContent implements DrawingContent, KeyPathElementContent {
|
||||||
@override
|
@override
|
||||||
Rect getBounds(Matrix4 parentMatrix, {required bool applyParents}) {
|
Rect getBounds(Matrix4 parentMatrix, {required bool applyParents}) {
|
||||||
_path.reset();
|
_path.reset();
|
||||||
|
_path.fillType = _fill.fillType;
|
||||||
for (var i = 0; i < _paths.length; i++) {
|
for (var i = 0; i < _paths.length; i++) {
|
||||||
_path.addPath(
|
_path.addPath(
|
||||||
_paths[i].getPath(),
|
_paths[i].getPath(),
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ class GradientFillContent implements DrawingContent, KeyPathElementContent {
|
||||||
_opacityAnimation = _fill.opacity.createAnimation(),
|
_opacityAnimation = _fill.opacity.createAnimation(),
|
||||||
_startPointAnimation = _fill.startPoint.createAnimation(),
|
_startPointAnimation = _fill.startPoint.createAnimation(),
|
||||||
_endPointAnimation = _fill.endPoint.createAnimation() {
|
_endPointAnimation = _fill.endPoint.createAnimation() {
|
||||||
_path.fillType = _fill.fillType;
|
|
||||||
_colorAnimation.addUpdateListener(invalidate);
|
_colorAnimation.addUpdateListener(invalidate);
|
||||||
layer.addAnimation(_colorAnimation);
|
layer.addAnimation(_colorAnimation);
|
||||||
|
|
||||||
|
|
@ -105,6 +104,7 @@ class GradientFillContent implements DrawingContent, KeyPathElementContent {
|
||||||
}
|
}
|
||||||
L.beginSection('GradientFillContent#draw');
|
L.beginSection('GradientFillContent#draw');
|
||||||
_path.reset();
|
_path.reset();
|
||||||
|
_path.fillType = _fill.fillType;
|
||||||
for (var i = 0; i < _paths.length; i++) {
|
for (var i = 0; i < _paths.length; i++) {
|
||||||
_path.addPath(_paths[i].getPath(), Offset.zero);
|
_path.addPath(_paths[i].getPath(), Offset.zero);
|
||||||
}
|
}
|
||||||
|
|
@ -155,6 +155,7 @@ class GradientFillContent implements DrawingContent, KeyPathElementContent {
|
||||||
@override
|
@override
|
||||||
Rect getBounds(Matrix4 parentMatrix, {required bool applyParents}) {
|
Rect getBounds(Matrix4 parentMatrix, {required bool applyParents}) {
|
||||||
_path.reset();
|
_path.reset();
|
||||||
|
_path.fillType = _fill.fillType;
|
||||||
for (var i = 0; i < _paths.length; i++) {
|
for (var i = 0; i < _paths.length; i++) {
|
||||||
_path.addPath(
|
_path.addPath(
|
||||||
_paths[i].getPath(),
|
_paths[i].getPath(),
|
||||||
|
|
|
||||||
|
|
@ -73,9 +73,9 @@ class GradientStrokeContent extends BaseStrokeContent {
|
||||||
|
|
||||||
Gradient gradient;
|
Gradient gradient;
|
||||||
if (_type == GradientType.linear) {
|
if (_type == GradientType.linear) {
|
||||||
gradient = _getLinearGradient(parentMatrix);
|
gradient = _getLinearGradient();
|
||||||
} else {
|
} else {
|
||||||
gradient = _getRadialGradient(parentMatrix);
|
gradient = _getRadialGradient();
|
||||||
}
|
}
|
||||||
|
|
||||||
paint.shader = gradient;
|
paint.shader = gradient;
|
||||||
|
|
@ -83,8 +83,9 @@ class GradientStrokeContent extends BaseStrokeContent {
|
||||||
super.draw(canvas, parentMatrix, parentAlpha: parentAlpha);
|
super.draw(canvas, parentMatrix, parentAlpha: parentAlpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
Gradient _getLinearGradient(Matrix4 parentMatrix) {
|
// Built in local space; BaseStrokeContent.draw concats the parent matrix, so don't reapply it.
|
||||||
var gradientHash = _getGradientHash(parentMatrix);
|
Gradient _getLinearGradient() {
|
||||||
|
var gradientHash = _getGradientHash();
|
||||||
var gradient = _linearGradientCache[gradientHash];
|
var gradient = _linearGradientCache[gradientHash];
|
||||||
if (gradient != null) {
|
if (gradient != null) {
|
||||||
return gradient;
|
return gradient;
|
||||||
|
|
@ -95,22 +96,15 @@ class GradientStrokeContent extends BaseStrokeContent {
|
||||||
var colors = _applyDynamicColorsIfNeeded(gradientColor.colors);
|
var colors = _applyDynamicColorsIfNeeded(gradientColor.colors);
|
||||||
var positions = gradientColor.positions;
|
var positions = gradientColor.positions;
|
||||||
|
|
||||||
gradient = Gradient.linear(
|
gradient = Gradient.linear(startPoint, endPoint, colors, positions);
|
||||||
startPoint,
|
|
||||||
endPoint,
|
|
||||||
colors,
|
|
||||||
positions,
|
|
||||||
TileMode.clamp,
|
|
||||||
parentMatrix.storage,
|
|
||||||
);
|
|
||||||
if (gradientHash != null) {
|
if (gradientHash != null) {
|
||||||
_linearGradientCache[gradientHash] = gradient;
|
_linearGradientCache[gradientHash] = gradient;
|
||||||
}
|
}
|
||||||
return gradient;
|
return gradient;
|
||||||
}
|
}
|
||||||
|
|
||||||
Gradient _getRadialGradient(Matrix4 parentMatrix) {
|
Gradient _getRadialGradient() {
|
||||||
var gradientHash = _getGradientHash(parentMatrix);
|
var gradientHash = _getGradientHash();
|
||||||
var gradient = _radialGradientCache[gradientHash];
|
var gradient = _radialGradientCache[gradientHash];
|
||||||
if (gradient != null) {
|
if (gradient != null) {
|
||||||
return gradient;
|
return gradient;
|
||||||
|
|
@ -125,14 +119,7 @@ class GradientStrokeContent extends BaseStrokeContent {
|
||||||
var x1 = endPoint.dx;
|
var x1 = endPoint.dx;
|
||||||
var y1 = endPoint.dy;
|
var y1 = endPoint.dy;
|
||||||
var radius = hypot(x1 - x0, y1 - y0).toDouble();
|
var radius = hypot(x1 - x0, y1 - y0).toDouble();
|
||||||
gradient = Gradient.radial(
|
gradient = Gradient.radial(startPoint, radius, colors, positions);
|
||||||
startPoint,
|
|
||||||
radius,
|
|
||||||
colors,
|
|
||||||
positions,
|
|
||||||
TileMode.clamp,
|
|
||||||
parentMatrix.storage,
|
|
||||||
);
|
|
||||||
if (gradientHash != null) {
|
if (gradientHash != null) {
|
||||||
_radialGradientCache[gradientHash] = gradient;
|
_radialGradientCache[gradientHash] = gradient;
|
||||||
}
|
}
|
||||||
|
|
@ -142,7 +129,7 @@ class GradientStrokeContent extends BaseStrokeContent {
|
||||||
//TODO(xha): cache the shader based on the input parameters and not the animation
|
//TODO(xha): cache the shader based on the input parameters and not the animation
|
||||||
// progress.
|
// progress.
|
||||||
// At first, log when there is too many cache miss.
|
// At first, log when there is too many cache miss.
|
||||||
int? _getGradientHash(Matrix4 parentMatrix) {
|
int? _getGradientHash() {
|
||||||
// Don't cache gradient if ValueDelegate.gradient is used
|
// Don't cache gradient if ValueDelegate.gradient is used
|
||||||
if (_colorCallbackAnimation != null) return null;
|
if (_colorCallbackAnimation != null) return null;
|
||||||
|
|
||||||
|
|
@ -160,7 +147,6 @@ class GradientStrokeContent extends BaseStrokeContent {
|
||||||
if (colorProgress != 0) {
|
if (colorProgress != 0) {
|
||||||
hash = hash * 31 * colorProgress;
|
hash = hash * 31 * colorProgress;
|
||||||
}
|
}
|
||||||
hash *= 31 * parentMatrix.hashCode;
|
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ class PolystarContent implements PathContent, KeyPathElementContent {
|
||||||
static const _polystarMagicNumber = .47829;
|
static const _polystarMagicNumber = .47829;
|
||||||
static const _polygonMagicNumber = .25;
|
static const _polygonMagicNumber = .25;
|
||||||
final _path = Path();
|
final _path = Path();
|
||||||
|
final _lastSegmentPath = Path();
|
||||||
|
|
||||||
final LottieDrawable lottieDrawable;
|
final LottieDrawable lottieDrawable;
|
||||||
final PolystarShape _polystarShape;
|
final PolystarShape _polystarShape;
|
||||||
|
|
@ -275,6 +276,34 @@ class PolystarContent implements PathContent, KeyPathElementContent {
|
||||||
var cp1y = radius * roundedness * _polygonMagicNumber * cp1Dy;
|
var cp1y = radius * roundedness * _polygonMagicNumber * cp1Dy;
|
||||||
var cp2x = radius * roundedness * _polygonMagicNumber * cp2Dx;
|
var cp2x = radius * roundedness * _polygonMagicNumber * cp2Dx;
|
||||||
var cp2y = radius * roundedness * _polygonMagicNumber * cp2Dy;
|
var cp2y = radius * roundedness * _polygonMagicNumber * cp2Dy;
|
||||||
|
if (i == numPoints - 1) {
|
||||||
|
// When there is a huge stroke, it will flash if the path ends where it starts.
|
||||||
|
// We want the final bezier curve to end *slightly* before the start.
|
||||||
|
// The close() call at the end will complete the polystar.
|
||||||
|
// https://github.com/airbnb/lottie-android/issues/2329
|
||||||
|
_lastSegmentPath.reset();
|
||||||
|
_lastSegmentPath.moveTo(previousX, previousY);
|
||||||
|
_lastSegmentPath.cubicTo(
|
||||||
|
previousX - cp1x,
|
||||||
|
previousY - cp1y,
|
||||||
|
x + cp2x,
|
||||||
|
y + cp2y,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
);
|
||||||
|
var metric = _lastSegmentPath.computeMetrics().first;
|
||||||
|
var position = metric
|
||||||
|
.getTangentForOffset(metric.length * 0.9999)!
|
||||||
|
.position;
|
||||||
|
_path.cubicTo(
|
||||||
|
previousX - cp1x,
|
||||||
|
previousY - cp1y,
|
||||||
|
x + cp2x,
|
||||||
|
y + cp2y,
|
||||||
|
position.dx,
|
||||||
|
position.dy,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
_path.cubicTo(
|
_path.cubicTo(
|
||||||
previousX - cp1x,
|
previousX - cp1x,
|
||||||
previousY - cp1y,
|
previousY - cp1y,
|
||||||
|
|
@ -283,7 +312,14 @@ class PolystarContent implements PathContent, KeyPathElementContent {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (i == numPoints - 1) {
|
||||||
|
// When there is a huge stroke, it will flash if the path ends where it starts.
|
||||||
|
// The close() call should make the path effectively equivalent.
|
||||||
|
// https://github.com/airbnb/lottie-android/issues/2329
|
||||||
|
continue;
|
||||||
|
}
|
||||||
_path.lineTo(x, y);
|
_path.lineTo(x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,11 @@ class RoundedCornersContent implements ShapeModifierContent {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addUpdateListener(void Function() listener) {
|
||||||
|
roundedCorners.addUpdateListener(listener);
|
||||||
|
}
|
||||||
|
|
||||||
/// Rounded corner algorithm:
|
/// Rounded corner algorithm:
|
||||||
/// Iterate through each vertex.
|
/// Iterate through each vertex.
|
||||||
/// If a vertex is a sharp corner, it rounds it.
|
/// If a vertex is a sharp corner, it rounds it.
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,22 @@
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
import '../../lottie_drawable.dart';
|
import '../../lottie_drawable.dart';
|
||||||
|
import '../../lottie_property.dart';
|
||||||
import '../../model/content/shape_path.dart';
|
import '../../model/content/shape_path.dart';
|
||||||
import '../../model/content/shape_trim_path.dart';
|
import '../../model/content/shape_trim_path.dart';
|
||||||
|
import '../../model/key_path.dart';
|
||||||
import '../../model/layer/base_layer.dart';
|
import '../../model/layer/base_layer.dart';
|
||||||
import '../../utils.dart';
|
import '../../utils.dart';
|
||||||
|
import '../../utils/misc.dart';
|
||||||
|
import '../../value/lottie_value_callback.dart';
|
||||||
import '../keyframe/shape_keyframe_animation.dart';
|
import '../keyframe/shape_keyframe_animation.dart';
|
||||||
import 'compound_trim_path_content.dart';
|
import 'compound_trim_path_content.dart';
|
||||||
import 'content.dart';
|
import 'content.dart';
|
||||||
|
import 'key_path_element_content.dart';
|
||||||
import 'path_content.dart';
|
import 'path_content.dart';
|
||||||
import 'shape_modifier_content.dart';
|
import 'shape_modifier_content.dart';
|
||||||
import 'trim_path_content.dart';
|
import 'trim_path_content.dart';
|
||||||
|
|
||||||
class ShapeContent implements PathContent {
|
class ShapeContent implements PathContent, KeyPathElementContent {
|
||||||
final _path = Path();
|
final _path = Path();
|
||||||
|
|
||||||
final ShapePath _shape;
|
final ShapePath _shape;
|
||||||
|
|
@ -46,6 +51,7 @@ class ShapeContent implements PathContent {
|
||||||
trimPath.addListener(_invalidate);
|
trimPath.addListener(_invalidate);
|
||||||
} else if (content is ShapeModifierContent) {
|
} else if (content is ShapeModifierContent) {
|
||||||
shapeModifierContents ??= [];
|
shapeModifierContents ??= [];
|
||||||
|
content.addUpdateListener(_invalidate);
|
||||||
shapeModifierContents.add(content);
|
shapeModifierContents.add(content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +63,7 @@ class ShapeContent implements PathContent {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Path getPath() {
|
Path getPath() {
|
||||||
if (_isPathValid) {
|
if (_isPathValid && !_shapeAnimation.hasValueCallback) {
|
||||||
return _path;
|
return _path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,4 +82,27 @@ class ShapeContent implements PathContent {
|
||||||
_isPathValid = true;
|
_isPathValid = true;
|
||||||
return _path;
|
return _path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void resolveKeyPath(
|
||||||
|
KeyPath keyPath,
|
||||||
|
int depth,
|
||||||
|
List<KeyPath> accumulator,
|
||||||
|
KeyPath currentPartialKeyPath,
|
||||||
|
) {
|
||||||
|
MiscUtils.resolveKeyPath(
|
||||||
|
keyPath,
|
||||||
|
depth,
|
||||||
|
accumulator,
|
||||||
|
currentPartialKeyPath,
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addValueCallback<T>(T property, LottieValueCallback<T>? callback) {
|
||||||
|
if (property == LottieProperty.path) {
|
||||||
|
_shapeAnimation.setValueCallback(callback as LottieValueCallback<Path>?);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,6 @@ import 'content.dart';
|
||||||
|
|
||||||
abstract class ShapeModifierContent extends Content {
|
abstract class ShapeModifierContent extends Content {
|
||||||
ShapeData modifyShape(ShapeData shapeData);
|
ShapeData modifyShape(ShapeData shapeData);
|
||||||
|
|
||||||
|
void addUpdateListener(void Function() listener);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,8 @@ abstract class BaseKeyframeAnimation<K extends Object, A extends Object?> {
|
||||||
|
|
||||||
var linearProgress = getLinearCurrentKeyframeProgress();
|
var linearProgress = getLinearCurrentKeyframeProgress();
|
||||||
if (valueCallback == null &&
|
if (valueCallback == null &&
|
||||||
_keyframesWrapper.isCachedValueEnabled(linearProgress)) {
|
_keyframesWrapper.isCachedValueEnabled(linearProgress) &&
|
||||||
|
!skipCache) {
|
||||||
return _cachedGetValue!;
|
return _cachedGetValue!;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,6 +151,11 @@ abstract class BaseKeyframeAnimation<K extends Object, A extends Object?> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get hasValueCallback => valueCallback != null;
|
||||||
|
|
||||||
|
@protected
|
||||||
|
bool get skipCache => false;
|
||||||
|
|
||||||
/// keyframeProgress will be [0, 1] unless the interpolator has overshoot in which case, this
|
/// keyframeProgress will be [0, 1] unless the interpolator has overshoot in which case, this
|
||||||
/// should be able to handle values outside of that range.
|
/// should be able to handle values outside of that range.
|
||||||
A getValue(Keyframe<K> keyframe, double keyframeProgress);
|
A getValue(Keyframe<K> keyframe, double keyframeProgress);
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,19 @@ class ShapeKeyframeAnimation extends BaseKeyframeAnimation<ShapeData, Path> {
|
||||||
final ShapeData _tempShapeData = ShapeData.empty();
|
final ShapeData _tempShapeData = ShapeData.empty();
|
||||||
final Path _tempPath = Path();
|
final Path _tempPath = Path();
|
||||||
List<ShapeModifierContent>? _shapeModifiers;
|
List<ShapeModifierContent>? _shapeModifiers;
|
||||||
|
Path? _valueCallbackStartPath;
|
||||||
|
Path? _valueCallbackEndPath;
|
||||||
|
|
||||||
ShapeKeyframeAnimation(super.keyframes);
|
ShapeKeyframeAnimation(super.keyframes);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Path getValue(Keyframe<ShapeData> keyframe, double keyframeProgress) {
|
Path getValue(Keyframe<ShapeData> keyframe, double keyframeProgress) {
|
||||||
var startShapeData = keyframe.startValue!;
|
var startShapeData = keyframe.startValue!;
|
||||||
var endShapeData = keyframe.endValue ?? startShapeData;
|
var endShapeData = keyframe.endValue;
|
||||||
|
|
||||||
_tempShapeData.interpolateBetween(
|
_tempShapeData.interpolateBetween(
|
||||||
startShapeData,
|
startShapeData,
|
||||||
endShapeData,
|
endShapeData ?? startShapeData,
|
||||||
keyframeProgress,
|
keyframeProgress,
|
||||||
);
|
);
|
||||||
var modifiedShapeData = _tempShapeData;
|
var modifiedShapeData = _tempShapeData;
|
||||||
|
|
@ -30,10 +32,39 @@ class ShapeKeyframeAnimation extends BaseKeyframeAnimation<ShapeData, Path> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MiscUtils.getPathFromData(modifiedShapeData, _tempPath);
|
MiscUtils.getPathFromData(modifiedShapeData, _tempPath);
|
||||||
|
|
||||||
|
var valueCallback = this.valueCallback;
|
||||||
|
if (valueCallback != null) {
|
||||||
|
var startPath = _valueCallbackStartPath ??= Path();
|
||||||
|
var endPath = _valueCallbackEndPath ??= Path();
|
||||||
|
MiscUtils.getPathFromData(startShapeData, startPath);
|
||||||
|
if (endShapeData != null) {
|
||||||
|
MiscUtils.getPathFromData(endShapeData, endPath);
|
||||||
|
}
|
||||||
|
return valueCallback.getValueInternal(
|
||||||
|
keyframe.startFrame,
|
||||||
|
keyframe.endFrame,
|
||||||
|
startPath,
|
||||||
|
endShapeData == null ? startPath : endPath,
|
||||||
|
keyframeProgress,
|
||||||
|
getLinearCurrentKeyframeProgress(),
|
||||||
|
progress,
|
||||||
|
) ??
|
||||||
|
_tempPath;
|
||||||
|
}
|
||||||
|
|
||||||
return _tempPath;
|
return _tempPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setShapeModifiers(List<ShapeModifierContent>? shapeModifiers) {
|
void setShapeModifiers(List<ShapeModifierContent>? shapeModifiers) {
|
||||||
_shapeModifiers = shapeModifiers;
|
_shapeModifiers = shapeModifiers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get skipCache {
|
||||||
|
// If there are shape modifiers but no animation on the shape itself, the shape animation
|
||||||
|
// will think nothing changed and will keep returning its cached value.
|
||||||
|
var shapeModifiers = _shapeModifiers;
|
||||||
|
return shapeModifiers != null && shapeModifiers.isNotEmpty;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ class TransformKeyframeAnimation {
|
||||||
_position = animatableTransform.position?.createAnimation(),
|
_position = animatableTransform.position?.createAnimation(),
|
||||||
_scale = animatableTransform.scale?.createAnimation(),
|
_scale = animatableTransform.scale?.createAnimation(),
|
||||||
_rotation = animatableTransform.rotation?.createAnimation(),
|
_rotation = animatableTransform.rotation?.createAnimation(),
|
||||||
|
_rotationX = animatableTransform.rotationX?.createAnimation(),
|
||||||
|
_rotationY = animatableTransform.rotationY?.createAnimation(),
|
||||||
_autoOrient = animatableTransform.isAutoOrient,
|
_autoOrient = animatableTransform.isAutoOrient,
|
||||||
_skew = animatableTransform.skew?.createAnimation(),
|
_skew = animatableTransform.skew?.createAnimation(),
|
||||||
_skewAngle = animatableTransform.skewAngle?.createAnimation(),
|
_skewAngle = animatableTransform.skewAngle?.createAnimation(),
|
||||||
|
|
@ -42,6 +44,8 @@ class TransformKeyframeAnimation {
|
||||||
BaseKeyframeAnimation<Offset, Offset>? _position;
|
BaseKeyframeAnimation<Offset, Offset>? _position;
|
||||||
BaseKeyframeAnimation<Offset, Offset>? _scale;
|
BaseKeyframeAnimation<Offset, Offset>? _scale;
|
||||||
BaseKeyframeAnimation<double, double>? _rotation;
|
BaseKeyframeAnimation<double, double>? _rotation;
|
||||||
|
BaseKeyframeAnimation<double, double>? _rotationX;
|
||||||
|
BaseKeyframeAnimation<double, double>? _rotationY;
|
||||||
DoubleKeyframeAnimation? _skew;
|
DoubleKeyframeAnimation? _skew;
|
||||||
DoubleKeyframeAnimation? _skewAngle;
|
DoubleKeyframeAnimation? _skewAngle;
|
||||||
|
|
||||||
|
|
@ -65,6 +69,8 @@ class TransformKeyframeAnimation {
|
||||||
layer.addAnimation(_position);
|
layer.addAnimation(_position);
|
||||||
layer.addAnimation(_scale);
|
layer.addAnimation(_scale);
|
||||||
layer.addAnimation(_rotation);
|
layer.addAnimation(_rotation);
|
||||||
|
layer.addAnimation(_rotationX);
|
||||||
|
layer.addAnimation(_rotationY);
|
||||||
layer.addAnimation(_skew);
|
layer.addAnimation(_skew);
|
||||||
layer.addAnimation(_skewAngle);
|
layer.addAnimation(_skewAngle);
|
||||||
}
|
}
|
||||||
|
|
@ -77,6 +83,8 @@ class TransformKeyframeAnimation {
|
||||||
_position?.addUpdateListener(listener);
|
_position?.addUpdateListener(listener);
|
||||||
_scale?.addUpdateListener(listener);
|
_scale?.addUpdateListener(listener);
|
||||||
_rotation?.addUpdateListener(listener);
|
_rotation?.addUpdateListener(listener);
|
||||||
|
_rotationX?.addUpdateListener(listener);
|
||||||
|
_rotationY?.addUpdateListener(listener);
|
||||||
_skew?.addUpdateListener(listener);
|
_skew?.addUpdateListener(listener);
|
||||||
_skewAngle?.addUpdateListener(listener);
|
_skewAngle?.addUpdateListener(listener);
|
||||||
}
|
}
|
||||||
|
|
@ -89,6 +97,8 @@ class TransformKeyframeAnimation {
|
||||||
_position?.setProgress(progress);
|
_position?.setProgress(progress);
|
||||||
_scale?.setProgress(progress);
|
_scale?.setProgress(progress);
|
||||||
_rotation?.setProgress(progress);
|
_rotation?.setProgress(progress);
|
||||||
|
_rotationX?.setProgress(progress);
|
||||||
|
_rotationY?.setProgress(progress);
|
||||||
_skew?.setProgress(progress);
|
_skew?.setProgress(progress);
|
||||||
_skewAngle?.setProgress(progress);
|
_skewAngle?.setProgress(progress);
|
||||||
}
|
}
|
||||||
|
|
@ -132,6 +142,20 @@ class TransformKeyframeAnimation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Approximate X/Y rotation as a 2D foreshortening: scale the perpendicular axis by cos(angle).
|
||||||
|
if (_rotationY != null) {
|
||||||
|
final rotationY = _rotationY!.value;
|
||||||
|
if (rotationY != 0) {
|
||||||
|
_matrix.scaleByDouble(cos(radians(rotationY)), 1, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_rotationX != null) {
|
||||||
|
final rotationX = _rotationX!.value;
|
||||||
|
if (rotationX != 0) {
|
||||||
|
_matrix.scaleByDouble(1, cos(radians(rotationX)), 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_skew != null) {
|
if (_skew != null) {
|
||||||
final mCos = _skewAngle == null
|
final mCos = _skewAngle == null
|
||||||
? 0.0
|
? 0.0
|
||||||
|
|
@ -258,6 +282,19 @@ class TransformKeyframeAnimation {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_rotationY != null) {
|
||||||
|
final rotationY = _rotationY!.value * amount;
|
||||||
|
if (rotationY != 0) {
|
||||||
|
_matrix.scaleByDouble(cos(radians(rotationY)), 1, 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (_rotationX != null) {
|
||||||
|
final rotationX = _rotationX!.value * amount;
|
||||||
|
if (rotationX != 0) {
|
||||||
|
_matrix.scaleByDouble(1, cos(radians(rotationX)), 1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return _matrix;
|
return _matrix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,6 +366,24 @@ class TransformKeyframeAnimation {
|
||||||
} else {
|
} else {
|
||||||
_endOpacity!.setValueCallback(callback as LottieValueCallback<double>?);
|
_endOpacity!.setValueCallback(callback as LottieValueCallback<double>?);
|
||||||
}
|
}
|
||||||
|
} else if (property == LottieProperty.transformRotationX) {
|
||||||
|
if (_rotationX == null) {
|
||||||
|
_rotationX = ValueCallbackKeyframeAnimation(
|
||||||
|
callback as LottieValueCallback<double>?,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_rotationX!.setValueCallback(callback as LottieValueCallback<double>?);
|
||||||
|
}
|
||||||
|
} else if (property == LottieProperty.transformRotationY) {
|
||||||
|
if (_rotationY == null) {
|
||||||
|
_rotationY = ValueCallbackKeyframeAnimation(
|
||||||
|
callback as LottieValueCallback<double>?,
|
||||||
|
0.0,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
_rotationY!.setValueCallback(callback as LottieValueCallback<double>?);
|
||||||
|
}
|
||||||
} else if (property == LottieProperty.transformSkew) {
|
} else if (property == LottieProperty.transformSkew) {
|
||||||
_skew ??= DoubleKeyframeAnimation([Keyframe.nonAnimated(0.0)]);
|
_skew ??= DoubleKeyframeAnimation([Keyframe.nonAnimated(0.0)]);
|
||||||
_skew!.setValueCallback(callback as LottieValueCallback<double>?);
|
_skew!.setValueCallback(callback as LottieValueCallback<double>?);
|
||||||
|
|
|
||||||
|
|
@ -77,14 +77,21 @@ class LottieComposition {
|
||||||
if (filePicker != null) {
|
if (filePicker != null) {
|
||||||
jsonFile = filePicker(archive.files);
|
jsonFile = filePicker(archive.files);
|
||||||
}
|
}
|
||||||
jsonFile ??= archive.files.firstWhere((e) => e.name.endsWith('.json'));
|
jsonFile ??= archive.files.firstWhereOrNull(
|
||||||
|
(e) => e.name.startsWith('animations/') && e.name.endsWith('.json'),
|
||||||
|
);
|
||||||
|
jsonFile ??= archive.files.firstWhere(
|
||||||
|
(e) =>
|
||||||
|
e.name.endsWith('.json') && p.basename(e.name) != 'manifest.json',
|
||||||
|
);
|
||||||
|
|
||||||
var composition = parseJsonBytes(jsonFile.content);
|
var composition = parseJsonBytes(jsonFile.content);
|
||||||
|
|
||||||
for (var image in composition.images.values) {
|
for (var image in composition.images.values) {
|
||||||
var imagePath = p.posix.join(image.dirName, image.fileName);
|
var imagePath = p.posix.join(image.dirName, image.fileName);
|
||||||
var found = archive.files.firstWhereOrNull(
|
var found = archive.files.firstWhereOrNull(
|
||||||
(f) => f.name.toLowerCase() == imagePath.toLowerCase(),
|
(f) =>
|
||||||
|
_normalizeArchivePath(f.name) == _normalizeArchivePath(imagePath),
|
||||||
);
|
);
|
||||||
|
|
||||||
ImageProvider? provider;
|
ImageProvider? provider;
|
||||||
|
|
@ -119,6 +126,14 @@ class LottieComposition {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String _normalizeArchivePath(String path) {
|
||||||
|
var normalized = p.posix.normalize(path).toLowerCase();
|
||||||
|
if (normalized.startsWith('/')) {
|
||||||
|
normalized = normalized.substring(1);
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
static Future<LottieComposition?> decodeGZip(List<int> bytes) async {
|
static Future<LottieComposition?> decodeGZip(List<int> bytes) async {
|
||||||
if (bytes[0] == 31 && bytes[1] == 139) {
|
if (bytes[0] == 31 && bytes[1] == 139) {
|
||||||
var decodedBytes = GZipDecoder().decodeBytes(bytes);
|
var decodedBytes = GZipDecoder().decodeBytes(bytes);
|
||||||
|
|
|
||||||
|
|
@ -389,6 +389,12 @@ class Lottie extends StatefulWidget {
|
||||||
class _LottieState extends State<Lottie> with TickerProviderStateMixin {
|
class _LottieState extends State<Lottie> with TickerProviderStateMixin {
|
||||||
late AnimationController _autoAnimation;
|
late AnimationController _autoAnimation;
|
||||||
|
|
||||||
|
/// The last frame we rebuilt for, expressed as the frame-rate-rounded
|
||||||
|
/// progress. The vsync ticker notifies us every frame, but we only rebuild
|
||||||
|
/// when this value actually changes, so build/paint run at the composition's
|
||||||
|
/// frame rate instead of the display refresh rate.
|
||||||
|
double? _renderedProgress;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
@ -397,6 +403,7 @@ class _LottieState extends State<Lottie> with TickerProviderStateMixin {
|
||||||
vsync: this,
|
vsync: this,
|
||||||
duration: widget.composition?.duration ?? const Duration(seconds: 1),
|
duration: widget.composition?.duration ?? const Duration(seconds: 1),
|
||||||
);
|
);
|
||||||
|
_progressAnimation.addListener(_onProgressChanged);
|
||||||
_updateAutoAnimation();
|
_updateAutoAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -404,6 +411,13 @@ class _LottieState extends State<Lottie> with TickerProviderStateMixin {
|
||||||
void didUpdateWidget(Lottie oldWidget) {
|
void didUpdateWidget(Lottie oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
||||||
|
if (oldWidget.controller != widget.controller) {
|
||||||
|
(oldWidget.controller ?? _autoAnimation).removeListener(
|
||||||
|
_onProgressChanged,
|
||||||
|
);
|
||||||
|
_progressAnimation.addListener(_onProgressChanged);
|
||||||
|
}
|
||||||
|
|
||||||
_autoAnimation.duration =
|
_autoAnimation.duration =
|
||||||
widget.composition?.duration ?? const Duration(seconds: 1);
|
widget.composition?.duration ?? const Duration(seconds: 1);
|
||||||
_updateAutoAnimation();
|
_updateAutoAnimation();
|
||||||
|
|
@ -421,8 +435,25 @@ class _LottieState extends State<Lottie> with TickerProviderStateMixin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Called on every vsync tick. Quantizes the raw progress to the target frame
|
||||||
|
/// rate and only triggers a rebuild when the resulting frame changes.
|
||||||
|
void _onProgressChanged() {
|
||||||
|
var rounded =
|
||||||
|
widget.composition?.roundProgress(
|
||||||
|
_progressAnimation.value,
|
||||||
|
frameRate: _frameRate,
|
||||||
|
) ??
|
||||||
|
_progressAnimation.value;
|
||||||
|
if (rounded != _renderedProgress) {
|
||||||
|
setState(() => _renderedProgress = rounded);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FrameRate get _frameRate => widget.frameRate ?? FrameRate.composition;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_progressAnimation.removeListener(_onProgressChanged);
|
||||||
_autoAnimation.dispose();
|
_autoAnimation.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
@ -432,10 +463,7 @@ class _LottieState extends State<Lottie> with TickerProviderStateMixin {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget child = AnimatedBuilder(
|
Widget child = RawLottie(
|
||||||
animation: _progressAnimation,
|
|
||||||
builder: (context, _) {
|
|
||||||
return RawLottie(
|
|
||||||
composition: widget.composition,
|
composition: widget.composition,
|
||||||
delegates: widget.delegates,
|
delegates: widget.delegates,
|
||||||
options: widget.options,
|
options: widget.options,
|
||||||
|
|
@ -448,8 +476,6 @@ class _LottieState extends State<Lottie> with TickerProviderStateMixin {
|
||||||
filterQuality: widget.filterQuality,
|
filterQuality: widget.filterQuality,
|
||||||
renderCache: widget.renderCache,
|
renderCache: widget.renderCache,
|
||||||
);
|
);
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
if (widget.addRepaintBoundary) {
|
if (widget.addRepaintBoundary) {
|
||||||
child = RepaintBoundary(child: child);
|
child = RepaintBoundary(child: child);
|
||||||
|
|
|
||||||
|
|
@ -222,8 +222,6 @@ class LottieDrawable {
|
||||||
var destinationRect = destinationPosition & destinationSize;
|
var destinationRect = destinationPosition & destinationSize;
|
||||||
var sourceRect = alignment.inscribe(sourceSize, Offset.zero & inputSize);
|
var sourceRect = alignment.inscribe(sourceSize, Offset.zero & inputSize);
|
||||||
|
|
||||||
_matrix.setIdentity();
|
|
||||||
|
|
||||||
var cacheUsed = false;
|
var cacheUsed = false;
|
||||||
if (renderCache != null) {
|
if (renderCache != null) {
|
||||||
var progressForCache = _progressAliases[progress] ?? progress;
|
var progressForCache = _progressAliases[progress] ?? progress;
|
||||||
|
|
@ -243,12 +241,7 @@ class LottieDrawable {
|
||||||
if (!cacheUsed) {
|
if (!cacheUsed) {
|
||||||
canvas.save();
|
canvas.save();
|
||||||
canvas.translate(destinationRect.left, destinationRect.top);
|
canvas.translate(destinationRect.left, destinationRect.top);
|
||||||
_matrix.scaleByDouble(
|
_matrix.setSourceToDestinationScale(sourceRect, destinationSize);
|
||||||
destinationSize.width / sourceRect.width,
|
|
||||||
destinationSize.height / sourceRect.height,
|
|
||||||
destinationSize.width / sourceRect.width,
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
_compositionLayer.draw(canvas, _matrix, parentAlpha: 255);
|
_compositionLayer.draw(canvas, _matrix, parentAlpha: 255);
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,6 +83,12 @@ abstract class LottieProperty {
|
||||||
/// In degrees */
|
/// In degrees */
|
||||||
static const double transformRotation = 12.0;
|
static const double transformRotation = 12.0;
|
||||||
|
|
||||||
|
/// In degrees (3D X-axis rotation) */
|
||||||
|
static const double transformRotationX = 30.0;
|
||||||
|
|
||||||
|
/// In degrees (3D Y-axis rotation) */
|
||||||
|
static const double transformRotationY = 31.0;
|
||||||
|
|
||||||
/// 0-85 */
|
/// 0-85 */
|
||||||
static const double transformSkew = 13.0;
|
static const double transformSkew = 13.0;
|
||||||
|
|
||||||
|
|
@ -141,6 +147,9 @@ abstract class LottieProperty {
|
||||||
|
|
||||||
static final List<Color> gradientColor = [];
|
static final List<Color> gradientColor = [];
|
||||||
|
|
||||||
|
/// Replace the outline of a shape (`ShapeContent`) at runtime.
|
||||||
|
static final Path path = Path();
|
||||||
|
|
||||||
/// Replace the text for a text layer.
|
/// Replace the text for a text layer.
|
||||||
static const text = 'dynamic_text';
|
static const text = 'dynamic_text';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,9 @@
|
||||||
import 'animatable_color_value.dart';
|
import 'animatable_text_range_selector.dart';
|
||||||
import 'animatable_double_value.dart';
|
import 'animatable_text_style.dart';
|
||||||
|
|
||||||
class AnimatableTextProperties {
|
class AnimatableTextProperties {
|
||||||
final AnimatableColorValue? color;
|
final AnimatableTextStyle? textStyle;
|
||||||
final AnimatableColorValue? stroke;
|
final AnimatableTextRangeSelector? rangeSelector;
|
||||||
final AnimatableDoubleValue? strokeWidth;
|
|
||||||
final AnimatableDoubleValue? tracking;
|
|
||||||
|
|
||||||
AnimatableTextProperties({
|
AnimatableTextProperties({this.textStyle, this.rangeSelector});
|
||||||
this.color,
|
|
||||||
this.stroke,
|
|
||||||
this.strokeWidth,
|
|
||||||
this.tracking,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
import '../content/text_range_units.dart';
|
||||||
|
import 'animatable_integer_value.dart';
|
||||||
|
|
||||||
|
/// Defines an animated range of text that should have an [AnimatableTextStyle] applied to it.
|
||||||
|
class AnimatableTextRangeSelector {
|
||||||
|
final AnimatableIntegerValue? start;
|
||||||
|
final AnimatableIntegerValue? end;
|
||||||
|
final AnimatableIntegerValue? offset;
|
||||||
|
final TextRangeUnits units;
|
||||||
|
|
||||||
|
AnimatableTextRangeSelector({
|
||||||
|
this.start,
|
||||||
|
this.end,
|
||||||
|
this.offset,
|
||||||
|
required this.units,
|
||||||
|
});
|
||||||
|
}
|
||||||
19
lottie/lib/src/model/animatable/animatable_text_style.dart
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import 'animatable_color_value.dart';
|
||||||
|
import 'animatable_double_value.dart';
|
||||||
|
import 'animatable_integer_value.dart';
|
||||||
|
|
||||||
|
class AnimatableTextStyle {
|
||||||
|
final AnimatableColorValue? color;
|
||||||
|
final AnimatableColorValue? stroke;
|
||||||
|
final AnimatableDoubleValue? strokeWidth;
|
||||||
|
final AnimatableDoubleValue? tracking;
|
||||||
|
final AnimatableIntegerValue? opacity;
|
||||||
|
|
||||||
|
AnimatableTextStyle({
|
||||||
|
this.color,
|
||||||
|
this.stroke,
|
||||||
|
this.strokeWidth,
|
||||||
|
this.tracking,
|
||||||
|
this.opacity,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -20,6 +20,11 @@ class AnimatableTransform implements ModifierContent, ContentModel {
|
||||||
|
|
||||||
final AnimatableDoubleValue? rotation;
|
final AnimatableDoubleValue? rotation;
|
||||||
|
|
||||||
|
// 3D rotation. [rotation] is the Z rotation.
|
||||||
|
final AnimatableDoubleValue? rotationX;
|
||||||
|
|
||||||
|
final AnimatableDoubleValue? rotationY;
|
||||||
|
|
||||||
final AnimatableIntegerValue? opacity;
|
final AnimatableIntegerValue? opacity;
|
||||||
|
|
||||||
final AnimatableDoubleValue? skew;
|
final AnimatableDoubleValue? skew;
|
||||||
|
|
@ -39,6 +44,8 @@ class AnimatableTransform implements ModifierContent, ContentModel {
|
||||||
this.position,
|
this.position,
|
||||||
this.scale,
|
this.scale,
|
||||||
this.rotation,
|
this.rotation,
|
||||||
|
this.rotationX,
|
||||||
|
this.rotationY,
|
||||||
this.opacity,
|
this.opacity,
|
||||||
this.skew,
|
this.skew,
|
||||||
this.skewAngle,
|
this.skewAngle,
|
||||||
|
|
|
||||||
2
lottie/lib/src/model/content/text_range_units.dart
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/// `index` is named [charIndex] because every Dart enum already defines an `index` getter.
|
||||||
|
enum TextRangeUnits { percent, charIndex }
|
||||||
|
|
@ -246,7 +246,7 @@ abstract class BaseLayer implements DrawingContent, KeyPathElement {
|
||||||
canvas.saveLayer(bounds, _mattePaint);
|
canvas.saveLayer(bounds, _mattePaint);
|
||||||
L.endSection('Layer#saveLayer');
|
L.endSection('Layer#saveLayer');
|
||||||
_clearCanvas(canvas, bounds);
|
_clearCanvas(canvas, bounds);
|
||||||
_matteLayer!.draw(canvas, parentMatrix, parentAlpha: alpha);
|
_matteLayer!.draw(canvas, parentMatrix, parentAlpha: parentAlpha);
|
||||||
L.beginSection('Layer#restoreLayer');
|
L.beginSection('Layer#restoreLayer');
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
L.endSection('Layer#restoreLayer');
|
L.endSection('Layer#restoreLayer');
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'dart:math' as math;
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import '../../animation/content/content_group.dart';
|
import '../../animation/content/content_group.dart';
|
||||||
import '../../animation/keyframe/base_keyframe_animation.dart';
|
import '../../animation/keyframe/base_keyframe_animation.dart';
|
||||||
|
|
@ -9,6 +10,7 @@ import '../../lottie_property.dart';
|
||||||
import '../../utils.dart';
|
import '../../utils.dart';
|
||||||
import '../../utils/characters.dart';
|
import '../../utils/characters.dart';
|
||||||
import '../../value/lottie_value_callback.dart';
|
import '../../value/lottie_value_callback.dart';
|
||||||
|
import '../content/text_range_units.dart';
|
||||||
import '../document_data.dart';
|
import '../document_data.dart';
|
||||||
import '../font.dart';
|
import '../font.dart';
|
||||||
import '../font_character.dart';
|
import '../font_character.dart';
|
||||||
|
|
@ -48,6 +50,16 @@ class TextLayer extends BaseLayer {
|
||||||
|
|
||||||
BaseKeyframeAnimation<double, double>? _textSizeCallbackAnimation;
|
BaseKeyframeAnimation<double, double>? _textSizeCallbackAnimation;
|
||||||
|
|
||||||
|
BaseKeyframeAnimation<int, int>? _opacityAnimation;
|
||||||
|
|
||||||
|
BaseKeyframeAnimation<int, int>? _textRangeStartAnimation;
|
||||||
|
|
||||||
|
BaseKeyframeAnimation<int, int>? _textRangeEndAnimation;
|
||||||
|
|
||||||
|
BaseKeyframeAnimation<int, int>? _textRangeOffsetAnimation;
|
||||||
|
|
||||||
|
TextRangeUnits _textRangeUnits = TextRangeUnits.charIndex;
|
||||||
|
|
||||||
TextLayer(LottieDrawable lottieDrawable, Layer layerModel)
|
TextLayer(LottieDrawable lottieDrawable, Layer layerModel)
|
||||||
: _composition = layerModel.composition,
|
: _composition = layerModel.composition,
|
||||||
_textAnimation = layerModel.text!.createAnimation(),
|
_textAnimation = layerModel.text!.createAnimation(),
|
||||||
|
|
@ -56,29 +68,59 @@ class TextLayer extends BaseLayer {
|
||||||
addAnimation(_textAnimation);
|
addAnimation(_textAnimation);
|
||||||
|
|
||||||
var textProperties = layerModel.textProperties;
|
var textProperties = layerModel.textProperties;
|
||||||
if (textProperties != null && textProperties.color != null) {
|
var textStyle = textProperties?.textStyle;
|
||||||
_colorAnimation = textProperties.color!.createAnimation()
|
if (textStyle != null && textStyle.color != null) {
|
||||||
|
_colorAnimation = textStyle.color!.createAnimation()
|
||||||
..addUpdateListener(invalidateSelf);
|
..addUpdateListener(invalidateSelf);
|
||||||
addAnimation(_colorAnimation);
|
addAnimation(_colorAnimation);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textProperties != null && textProperties.stroke != null) {
|
if (textStyle != null && textStyle.stroke != null) {
|
||||||
_strokeColorAnimation = textProperties.stroke!.createAnimation()
|
_strokeColorAnimation = textStyle.stroke!.createAnimation()
|
||||||
..addUpdateListener(invalidateSelf);
|
..addUpdateListener(invalidateSelf);
|
||||||
addAnimation(_strokeColorAnimation);
|
addAnimation(_strokeColorAnimation);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textProperties != null && textProperties.strokeWidth != null) {
|
if (textStyle != null && textStyle.strokeWidth != null) {
|
||||||
_strokeWidthAnimation = textProperties.strokeWidth!.createAnimation()
|
_strokeWidthAnimation = textStyle.strokeWidth!.createAnimation()
|
||||||
..addUpdateListener(invalidateSelf);
|
..addUpdateListener(invalidateSelf);
|
||||||
addAnimation(_strokeWidthAnimation);
|
addAnimation(_strokeWidthAnimation);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (textProperties != null && textProperties.tracking != null) {
|
if (textStyle != null && textStyle.tracking != null) {
|
||||||
_trackingAnimation = textProperties.tracking!.createAnimation()
|
_trackingAnimation = textStyle.tracking!.createAnimation()
|
||||||
..addUpdateListener(invalidateSelf);
|
..addUpdateListener(invalidateSelf);
|
||||||
addAnimation(_trackingAnimation);
|
addAnimation(_trackingAnimation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (textStyle != null && textStyle.opacity != null) {
|
||||||
|
_opacityAnimation = textStyle.opacity!.createAnimation()
|
||||||
|
..addUpdateListener(invalidateSelf);
|
||||||
|
addAnimation(_opacityAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
var rangeSelector = textProperties?.rangeSelector;
|
||||||
|
if (rangeSelector != null && rangeSelector.start != null) {
|
||||||
|
_textRangeStartAnimation = rangeSelector.start!.createAnimation()
|
||||||
|
..addUpdateListener(invalidateSelf);
|
||||||
|
addAnimation(_textRangeStartAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangeSelector != null && rangeSelector.end != null) {
|
||||||
|
_textRangeEndAnimation = rangeSelector.end!.createAnimation()
|
||||||
|
..addUpdateListener(invalidateSelf);
|
||||||
|
addAnimation(_textRangeEndAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangeSelector != null && rangeSelector.offset != null) {
|
||||||
|
_textRangeOffsetAnimation = rangeSelector.offset!.createAnimation()
|
||||||
|
..addUpdateListener(invalidateSelf);
|
||||||
|
addAnimation(_textRangeOffsetAnimation);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangeSelector != null) {
|
||||||
|
_textRangeUnits = rangeSelector.units;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -107,24 +149,41 @@ class TextLayer extends BaseLayer {
|
||||||
canvas.save();
|
canvas.save();
|
||||||
canvas.transform(parentMatrix.storage);
|
canvas.transform(parentMatrix.storage);
|
||||||
|
|
||||||
_configurePaint(documentData, parentAlpha);
|
_configurePaint(documentData, parentAlpha, 0);
|
||||||
|
|
||||||
if (lottieDrawable.useTextGlyphs) {
|
if (lottieDrawable.useTextGlyphs) {
|
||||||
_drawTextWithGlyphs(documentData, parentMatrix, font, canvas);
|
_drawTextWithGlyphs(
|
||||||
|
documentData,
|
||||||
|
parentMatrix,
|
||||||
|
font,
|
||||||
|
canvas,
|
||||||
|
parentAlpha,
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
_drawTextWithFont(documentData, font, canvas);
|
_drawTextWithFont(documentData, font, canvas, parentAlpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _configurePaint(DocumentData documentData, int parentAlpha) {
|
/// Configures the [_fillPaint] and [_strokePaint] used for drawing based on the
|
||||||
|
/// currently active text ranges.
|
||||||
|
///
|
||||||
|
/// [parentAlpha] is a value from 0 to 255 indicating the alpha of the parented layer.
|
||||||
|
void _configurePaint(
|
||||||
|
DocumentData documentData,
|
||||||
|
int parentAlpha,
|
||||||
|
int indexInDocument,
|
||||||
|
) {
|
||||||
Color fillPaintColor;
|
Color fillPaintColor;
|
||||||
if (_colorCallbackAnimation != null) {
|
if (_colorCallbackAnimation != null) {
|
||||||
|
// dynamic property takes priority
|
||||||
fillPaintColor = _colorCallbackAnimation!.value;
|
fillPaintColor = _colorCallbackAnimation!.value;
|
||||||
} else if (_colorAnimation != null) {
|
} else if (_colorAnimation != null &&
|
||||||
|
_isIndexInRangeSelection(indexInDocument)) {
|
||||||
fillPaintColor = _colorAnimation!.value;
|
fillPaintColor = _colorAnimation!.value;
|
||||||
} else {
|
} else {
|
||||||
|
// fall back to the document color
|
||||||
fillPaintColor = documentData.color;
|
fillPaintColor = documentData.color;
|
||||||
}
|
}
|
||||||
_fillPaint.color = fillPaintColor.withValues(alpha: _fillPaint.color.a);
|
_fillPaint.color = fillPaintColor.withValues(alpha: _fillPaint.color.a);
|
||||||
|
|
@ -132,7 +191,8 @@ class TextLayer extends BaseLayer {
|
||||||
Color strokePaintColor;
|
Color strokePaintColor;
|
||||||
if (_strokeColorCallbackAnimation != null) {
|
if (_strokeColorCallbackAnimation != null) {
|
||||||
strokePaintColor = _strokeColorCallbackAnimation!.value;
|
strokePaintColor = _strokeColorCallbackAnimation!.value;
|
||||||
} else if (_strokeColorAnimation != null) {
|
} else if (_strokeColorAnimation != null &&
|
||||||
|
_isIndexInRangeSelection(indexInDocument)) {
|
||||||
strokePaintColor = _strokeColorAnimation!.value;
|
strokePaintColor = _strokeColorAnimation!.value;
|
||||||
} else {
|
} else {
|
||||||
strokePaintColor = documentData.strokeColor;
|
strokePaintColor = documentData.strokeColor;
|
||||||
|
|
@ -141,25 +201,78 @@ class TextLayer extends BaseLayer {
|
||||||
alpha: _strokePaint.color.a,
|
alpha: _strokePaint.color.a,
|
||||||
);
|
);
|
||||||
|
|
||||||
var opacity = transform.opacity?.value ?? 100;
|
// These opacity values are in the range 0 to 100.
|
||||||
var alpha = opacity * 255 / 100 * parentAlpha ~/ 255;
|
var transformOpacity = transform.opacity?.value ?? 100;
|
||||||
|
var textRangeOpacity =
|
||||||
|
_opacityAnimation != null && _isIndexInRangeSelection(indexInDocument)
|
||||||
|
? _opacityAnimation!.value
|
||||||
|
: 100;
|
||||||
|
|
||||||
|
// This alpha value needs to be in the range 0 to 255 to be applied to the Paint
|
||||||
|
// instances. We map the layer transform's opacity into that range and multiply it by
|
||||||
|
// the fractional opacity of the text range and the parent.
|
||||||
|
var alpha =
|
||||||
|
(transformOpacity *
|
||||||
|
255 /
|
||||||
|
100 *
|
||||||
|
(textRangeOpacity / 100) *
|
||||||
|
parentAlpha /
|
||||||
|
255)
|
||||||
|
.round();
|
||||||
_fillPaint.setAlpha(alpha);
|
_fillPaint.setAlpha(alpha);
|
||||||
_strokePaint.setAlpha(alpha);
|
_strokePaint.setAlpha(alpha);
|
||||||
|
|
||||||
if (_strokeWidthCallbackAnimation != null) {
|
if (_strokeWidthCallbackAnimation != null) {
|
||||||
_strokePaint.strokeWidth = _strokeWidthCallbackAnimation!.value;
|
_strokePaint.strokeWidth = _strokeWidthCallbackAnimation!.value;
|
||||||
} else if (_strokeWidthAnimation != null) {
|
} else if (_strokeWidthAnimation != null &&
|
||||||
|
_isIndexInRangeSelection(indexInDocument)) {
|
||||||
_strokePaint.strokeWidth = _strokeWidthAnimation!.value;
|
_strokePaint.strokeWidth = _strokeWidthAnimation!.value;
|
||||||
} else {
|
} else {
|
||||||
_strokePaint.strokeWidth = documentData.strokeWidth;
|
_strokePaint.strokeWidth = documentData.strokeWidth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isIndexInRangeSelection(int indexInDocument) {
|
||||||
|
var textLength = _textAnimation.value.text.length;
|
||||||
|
var textRangeStartAnimation = _textRangeStartAnimation;
|
||||||
|
var textRangeEndAnimation = _textRangeEndAnimation;
|
||||||
|
if (textRangeStartAnimation != null && textRangeEndAnimation != null) {
|
||||||
|
// After Effects supports reversed text ranges where the start index is greater than
|
||||||
|
// the end index. For the purposes of determining if the given index is inside of the
|
||||||
|
// range, we take the start as the smaller value.
|
||||||
|
var rangeStart = math.min(
|
||||||
|
textRangeStartAnimation.value,
|
||||||
|
textRangeEndAnimation.value,
|
||||||
|
);
|
||||||
|
var rangeEnd = math.max(
|
||||||
|
textRangeStartAnimation.value,
|
||||||
|
textRangeEndAnimation.value,
|
||||||
|
);
|
||||||
|
|
||||||
|
var textRangeOffsetAnimation = _textRangeOffsetAnimation;
|
||||||
|
if (textRangeOffsetAnimation != null) {
|
||||||
|
var offset = textRangeOffsetAnimation.value;
|
||||||
|
rangeStart += offset;
|
||||||
|
rangeEnd += offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_textRangeUnits == TextRangeUnits.charIndex) {
|
||||||
|
return indexInDocument >= rangeStart && indexInDocument < rangeEnd;
|
||||||
|
} else {
|
||||||
|
var currentIndexAsPercent = indexInDocument / textLength * 100;
|
||||||
|
return currentIndexAsPercent >= rangeStart &&
|
||||||
|
currentIndexAsPercent < rangeEnd;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void _drawTextWithGlyphs(
|
void _drawTextWithGlyphs(
|
||||||
DocumentData documentData,
|
DocumentData documentData,
|
||||||
Matrix4 parentMatrix,
|
Matrix4 parentMatrix,
|
||||||
Font font,
|
Font font,
|
||||||
Canvas canvas,
|
Canvas canvas,
|
||||||
|
int parentAlpha,
|
||||||
) {
|
) {
|
||||||
double textSize;
|
double textSize;
|
||||||
if (_textSizeCallbackAnimation != null) {
|
if (_textSizeCallbackAnimation != null) {
|
||||||
|
|
@ -211,6 +324,7 @@ class TextLayer extends BaseLayer {
|
||||||
parentScale,
|
parentScale,
|
||||||
fontScale,
|
fontScale,
|
||||||
tracking,
|
tracking,
|
||||||
|
parentAlpha,
|
||||||
);
|
);
|
||||||
|
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
|
|
@ -226,21 +340,37 @@ class TextLayer extends BaseLayer {
|
||||||
double parentScale,
|
double parentScale,
|
||||||
double fontScale,
|
double fontScale,
|
||||||
double tracking,
|
double tracking,
|
||||||
|
int parentAlpha,
|
||||||
) {
|
) {
|
||||||
|
var index = 0;
|
||||||
for (var c in text) {
|
for (var c in text) {
|
||||||
var characterHash = FontCharacter.hashFor(c, font.family, font.style);
|
var characterHash = FontCharacter.hashFor(c, font.family, font.style);
|
||||||
var character = _composition.characters[characterHash];
|
var character = _composition.characters[characterHash];
|
||||||
if (character == null) {
|
if (character == null) {
|
||||||
// Something is wrong. Potentially, they didn't export the text as a glyph.
|
// Something is wrong. Potentially, they didn't export the text as a glyph.
|
||||||
|
index++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
_drawCharacterAsGlyph(character, fontScale, documentData, canvas);
|
_drawCharacterAsGlyph(
|
||||||
|
character,
|
||||||
|
fontScale,
|
||||||
|
documentData,
|
||||||
|
canvas,
|
||||||
|
index,
|
||||||
|
parentAlpha,
|
||||||
|
);
|
||||||
var tx = character.width * fontScale + tracking;
|
var tx = character.width * fontScale + tracking;
|
||||||
canvas.translate(tx, 0);
|
canvas.translate(tx, 0);
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _drawTextWithFont(DocumentData documentData, Font font, Canvas canvas) {
|
void _drawTextWithFont(
|
||||||
|
DocumentData documentData,
|
||||||
|
Font font,
|
||||||
|
Canvas canvas,
|
||||||
|
int parentAlpha,
|
||||||
|
) {
|
||||||
var textStyle = lottieDrawable.getTextStyle(font.family, font.style);
|
var textStyle = lottieDrawable.getTextStyle(font.family, font.style);
|
||||||
var text = documentData.text;
|
var text = documentData.text;
|
||||||
var textDelegate = lottieDrawable.delegates?.text;
|
var textDelegate = lottieDrawable.delegates?.text;
|
||||||
|
|
@ -270,6 +400,7 @@ class TextLayer extends BaseLayer {
|
||||||
var textLines = _getTextLines(text);
|
var textLines = _getTextLines(text);
|
||||||
var textLineCount = textLines.length;
|
var textLineCount = textLines.length;
|
||||||
var lineIndex = -1;
|
var lineIndex = -1;
|
||||||
|
var characterIndexAtStartOfLine = 0;
|
||||||
for (var i = 0; i < textLineCount; i++) {
|
for (var i = 0; i < textLineCount; i++) {
|
||||||
var textLine = textLines[i];
|
var textLine = textLines[i];
|
||||||
var boxWidth = documentData.boxSize?.dx ?? 0.0;
|
var boxWidth = documentData.boxSize?.dx ?? 0.0;
|
||||||
|
|
@ -288,7 +419,17 @@ class TextLayer extends BaseLayer {
|
||||||
canvas.save();
|
canvas.save();
|
||||||
|
|
||||||
_offsetCanvas(canvas, documentData, lineIndex, line.width);
|
_offsetCanvas(canvas, documentData, lineIndex, line.width);
|
||||||
_drawFontTextLine(line.text, textStyle, documentData, canvas, tracking);
|
_drawFontTextLine(
|
||||||
|
line.text,
|
||||||
|
textStyle,
|
||||||
|
documentData,
|
||||||
|
canvas,
|
||||||
|
tracking,
|
||||||
|
characterIndexAtStartOfLine,
|
||||||
|
parentAlpha,
|
||||||
|
);
|
||||||
|
|
||||||
|
characterIndexAtStartOfLine += line.text.length;
|
||||||
|
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
}
|
}
|
||||||
|
|
@ -332,16 +473,28 @@ class TextLayer extends BaseLayer {
|
||||||
return textLinesArray.map((l) => l.characters).toList();
|
return textLinesArray.map((l) => l.characters).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [characterIndexAtStartOfLine] is the index within the overall document of the character
|
||||||
|
/// at the start of the line.
|
||||||
void _drawFontTextLine(
|
void _drawFontTextLine(
|
||||||
Characters text,
|
Characters text,
|
||||||
TextStyle textStyle,
|
TextStyle textStyle,
|
||||||
DocumentData documentData,
|
DocumentData documentData,
|
||||||
Canvas canvas,
|
Canvas canvas,
|
||||||
double tracking,
|
double tracking,
|
||||||
|
int characterIndexAtStartOfLine,
|
||||||
|
int parentAlpha,
|
||||||
) {
|
) {
|
||||||
|
var index = 0;
|
||||||
for (var char in text) {
|
for (var char in text) {
|
||||||
var charString = char;
|
var charString = char;
|
||||||
_drawCharacterFromFont(charString, textStyle, documentData, canvas);
|
_drawCharacterFromFont(
|
||||||
|
charString,
|
||||||
|
textStyle,
|
||||||
|
documentData,
|
||||||
|
canvas,
|
||||||
|
characterIndexAtStartOfLine + index,
|
||||||
|
parentAlpha,
|
||||||
|
);
|
||||||
var textPainter = TextPainter(
|
var textPainter = TextPainter(
|
||||||
text: TextSpan(text: charString, style: textStyle),
|
text: TextSpan(text: charString, style: textStyle),
|
||||||
textDirection: _textDirection,
|
textDirection: _textDirection,
|
||||||
|
|
@ -350,6 +503,7 @@ class TextLayer extends BaseLayer {
|
||||||
var charWidth = textPainter.width;
|
var charWidth = textPainter.width;
|
||||||
var tx = charWidth + tracking;
|
var tx = charWidth + tracking;
|
||||||
canvas.translate(tx, 0);
|
canvas.translate(tx, 0);
|
||||||
|
index++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -463,7 +617,10 @@ class TextLayer extends BaseLayer {
|
||||||
double fontScale,
|
double fontScale,
|
||||||
DocumentData documentData,
|
DocumentData documentData,
|
||||||
Canvas canvas,
|
Canvas canvas,
|
||||||
|
int indexInDocument,
|
||||||
|
int parentAlpha,
|
||||||
) {
|
) {
|
||||||
|
_configurePaint(documentData, parentAlpha, indexInDocument);
|
||||||
var contentGroups = _getContentsForCharacter(character);
|
var contentGroups = _getContentsForCharacter(character);
|
||||||
for (var j = 0; j < contentGroups.length; j++) {
|
for (var j = 0; j < contentGroups.length; j++) {
|
||||||
var path = contentGroups[j].getPath();
|
var path = contentGroups[j].getPath();
|
||||||
|
|
@ -496,7 +653,10 @@ class TextLayer extends BaseLayer {
|
||||||
TextStyle textStyle,
|
TextStyle textStyle,
|
||||||
DocumentData documentData,
|
DocumentData documentData,
|
||||||
Canvas canvas,
|
Canvas canvas,
|
||||||
|
int indexInDocument,
|
||||||
|
int parentAlpha,
|
||||||
) {
|
) {
|
||||||
|
_configurePaint(documentData, parentAlpha, indexInDocument);
|
||||||
if (documentData.strokeOverFill) {
|
if (documentData.strokeOverFill) {
|
||||||
_drawCharacter(character, textStyle, _fillPaint, canvas);
|
_drawCharacter(character, textStyle, _fillPaint, canvas);
|
||||||
_drawCharacter(character, textStyle, _strokePaint, canvas);
|
_drawCharacter(character, textStyle, _strokePaint, canvas);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,35 @@
|
||||||
import '../composition.dart';
|
import '../composition.dart';
|
||||||
import '../model/animatable/animatable_color_value.dart';
|
import '../model/animatable/animatable_color_value.dart';
|
||||||
import '../model/animatable/animatable_double_value.dart';
|
import '../model/animatable/animatable_double_value.dart';
|
||||||
|
import '../model/animatable/animatable_integer_value.dart';
|
||||||
import '../model/animatable/animatable_text_properties.dart';
|
import '../model/animatable/animatable_text_properties.dart';
|
||||||
|
import '../model/animatable/animatable_text_range_selector.dart';
|
||||||
|
import '../model/animatable/animatable_text_style.dart';
|
||||||
|
import '../model/content/text_range_units.dart';
|
||||||
|
import '../value/keyframe.dart';
|
||||||
import 'animatable_value_parser.dart';
|
import 'animatable_value_parser.dart';
|
||||||
import 'moshi/json_reader.dart';
|
import 'moshi/json_reader.dart';
|
||||||
|
|
||||||
class AnimatableTextPropertiesParser {
|
class AnimatableTextPropertiesParser {
|
||||||
static final JsonReaderOptions _propertiesNames = JsonReaderOptions.of(['a']);
|
static final JsonReaderOptions _propertiesNames = JsonReaderOptions.of([
|
||||||
|
's', // Range selector
|
||||||
|
'a', // Text style for this range
|
||||||
|
]);
|
||||||
|
static final JsonReaderOptions _animatableRangePropertiesNames =
|
||||||
|
JsonReaderOptions.of([
|
||||||
|
's', // start
|
||||||
|
'e', // end
|
||||||
|
'o', // offset
|
||||||
|
'r', // text range units (percent or index)
|
||||||
|
]);
|
||||||
static final JsonReaderOptions _animatablePropertiesNames =
|
static final JsonReaderOptions _animatablePropertiesNames =
|
||||||
JsonReaderOptions.of(['fc', 'sc', 'sw', 't']);
|
JsonReaderOptions.of([
|
||||||
|
'fc',
|
||||||
|
'sc',
|
||||||
|
'sw',
|
||||||
|
't',
|
||||||
|
'o', // opacity
|
||||||
|
]);
|
||||||
|
|
||||||
AnimatableTextPropertiesParser();
|
AnimatableTextPropertiesParser();
|
||||||
|
|
||||||
|
|
@ -16,27 +37,84 @@ class AnimatableTextPropertiesParser {
|
||||||
JsonReader reader,
|
JsonReader reader,
|
||||||
LottieComposition composition,
|
LottieComposition composition,
|
||||||
) {
|
) {
|
||||||
AnimatableTextProperties? anim;
|
AnimatableTextStyle? textStyle;
|
||||||
|
AnimatableTextRangeSelector? rangeSelector;
|
||||||
|
|
||||||
reader.beginObject();
|
reader.beginObject();
|
||||||
while (reader.hasNext()) {
|
while (reader.hasNext()) {
|
||||||
switch (reader.selectName(_propertiesNames)) {
|
switch (reader.selectName(_propertiesNames)) {
|
||||||
case 0:
|
case 0: // Range selector
|
||||||
anim = _parseAnimatableTextProperties(reader, composition);
|
rangeSelector = _parseAnimatableTextRangeSelector(
|
||||||
|
reader,
|
||||||
|
composition,
|
||||||
|
);
|
||||||
|
case 1: // Text style for this range
|
||||||
|
textStyle = _parseAnimatableTextStyle(reader, composition);
|
||||||
default:
|
default:
|
||||||
reader.skipName();
|
reader.skipName();
|
||||||
reader.skipValue();
|
reader.skipValue();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reader.endObject();
|
reader.endObject();
|
||||||
if (anim == null) {
|
|
||||||
// Not sure if this is possible.
|
return AnimatableTextProperties(
|
||||||
return AnimatableTextProperties();
|
textStyle: textStyle,
|
||||||
}
|
rangeSelector: rangeSelector,
|
||||||
return anim;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static AnimatableTextProperties _parseAnimatableTextProperties(
|
static AnimatableTextRangeSelector _parseAnimatableTextRangeSelector(
|
||||||
|
JsonReader reader,
|
||||||
|
LottieComposition composition,
|
||||||
|
) {
|
||||||
|
AnimatableIntegerValue? start;
|
||||||
|
AnimatableIntegerValue? end;
|
||||||
|
AnimatableIntegerValue? offset;
|
||||||
|
var units = TextRangeUnits.charIndex;
|
||||||
|
|
||||||
|
reader.beginObject();
|
||||||
|
while (reader.hasNext()) {
|
||||||
|
switch (reader.selectName(_animatableRangePropertiesNames)) {
|
||||||
|
case 0: // start
|
||||||
|
start = AnimatableValueParser.parseInteger(reader, composition);
|
||||||
|
case 1: // end
|
||||||
|
end = AnimatableValueParser.parseInteger(reader, composition);
|
||||||
|
case 2: // offset
|
||||||
|
offset = AnimatableValueParser.parseInteger(reader, composition);
|
||||||
|
case 3: // text range units (percent or index)
|
||||||
|
var textRangeUnits = reader.nextInt();
|
||||||
|
if (textRangeUnits != 1 && textRangeUnits != 2) {
|
||||||
|
composition.addWarning(
|
||||||
|
'Unsupported text range units: $textRangeUnits',
|
||||||
|
);
|
||||||
|
units = TextRangeUnits.charIndex;
|
||||||
|
} else {
|
||||||
|
units = textRangeUnits == 1
|
||||||
|
? TextRangeUnits.percent
|
||||||
|
: TextRangeUnits.charIndex;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
reader.skipName();
|
||||||
|
reader.skipValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
reader.endObject();
|
||||||
|
|
||||||
|
// If no start value is provided, default to a non-animated value of 0 to match
|
||||||
|
// After Effects/Bodymovin.
|
||||||
|
if (start == null && end != null) {
|
||||||
|
start = AnimatableIntegerValue.fromKeyframes([Keyframe.nonAnimated(0)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return AnimatableTextRangeSelector(
|
||||||
|
start: start,
|
||||||
|
end: end,
|
||||||
|
offset: offset,
|
||||||
|
units: units,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static AnimatableTextStyle _parseAnimatableTextStyle(
|
||||||
JsonReader reader,
|
JsonReader reader,
|
||||||
LottieComposition composition,
|
LottieComposition composition,
|
||||||
) {
|
) {
|
||||||
|
|
@ -44,6 +122,7 @@ class AnimatableTextPropertiesParser {
|
||||||
AnimatableColorValue? stroke;
|
AnimatableColorValue? stroke;
|
||||||
AnimatableDoubleValue? strokeWidth;
|
AnimatableDoubleValue? strokeWidth;
|
||||||
AnimatableDoubleValue? tracking;
|
AnimatableDoubleValue? tracking;
|
||||||
|
AnimatableIntegerValue? opacity;
|
||||||
|
|
||||||
reader.beginObject();
|
reader.beginObject();
|
||||||
while (reader.hasNext()) {
|
while (reader.hasNext()) {
|
||||||
|
|
@ -56,6 +135,8 @@ class AnimatableTextPropertiesParser {
|
||||||
strokeWidth = AnimatableValueParser.parseFloat(reader, composition);
|
strokeWidth = AnimatableValueParser.parseFloat(reader, composition);
|
||||||
case 3:
|
case 3:
|
||||||
tracking = AnimatableValueParser.parseFloat(reader, composition);
|
tracking = AnimatableValueParser.parseFloat(reader, composition);
|
||||||
|
case 4: // opacity
|
||||||
|
opacity = AnimatableValueParser.parseInteger(reader, composition);
|
||||||
default:
|
default:
|
||||||
reader.skipName();
|
reader.skipName();
|
||||||
reader.skipValue();
|
reader.skipValue();
|
||||||
|
|
@ -63,11 +144,12 @@ class AnimatableTextPropertiesParser {
|
||||||
}
|
}
|
||||||
reader.endObject();
|
reader.endObject();
|
||||||
|
|
||||||
return AnimatableTextProperties(
|
return AnimatableTextStyle(
|
||||||
color: color,
|
color: color,
|
||||||
stroke: stroke,
|
stroke: stroke,
|
||||||
strokeWidth: strokeWidth,
|
strokeWidth: strokeWidth,
|
||||||
tracking: tracking,
|
tracking: tracking,
|
||||||
|
opacity: opacity,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@ class AnimatableTransformParser {
|
||||||
'eo',
|
'eo',
|
||||||
'sk',
|
'sk',
|
||||||
'sa',
|
'sa',
|
||||||
|
'rx',
|
||||||
|
'ry',
|
||||||
]);
|
]);
|
||||||
static final JsonReaderOptions _animatableNames = JsonReaderOptions.of(['k']);
|
static final JsonReaderOptions _animatableNames = JsonReaderOptions.of(['k']);
|
||||||
|
|
||||||
|
|
@ -37,6 +39,8 @@ class AnimatableTransformParser {
|
||||||
AnimatableValue<Offset, Offset>? position;
|
AnimatableValue<Offset, Offset>? position;
|
||||||
AnimatableScaleValue? scale;
|
AnimatableScaleValue? scale;
|
||||||
AnimatableDoubleValue? rotation;
|
AnimatableDoubleValue? rotation;
|
||||||
|
AnimatableDoubleValue? rotationX;
|
||||||
|
AnimatableDoubleValue? rotationY;
|
||||||
AnimatableIntegerValue? opacity;
|
AnimatableIntegerValue? opacity;
|
||||||
AnimatableDoubleValue? startOpacity;
|
AnimatableDoubleValue? startOpacity;
|
||||||
AnimatableDoubleValue? endOpacity;
|
AnimatableDoubleValue? endOpacity;
|
||||||
|
|
@ -74,10 +78,7 @@ class AnimatableTransformParser {
|
||||||
scale = AnimatableValueParser.parseScale(reader, composition);
|
scale = AnimatableValueParser.parseScale(reader, composition);
|
||||||
case 3:
|
case 3:
|
||||||
case 4:
|
case 4:
|
||||||
if (name == 3) {
|
// Both "rz" (3D Z rotation) and "r" (2D rotation) feed the Z rotation.
|
||||||
composition.addWarning("Lottie doesn't support 3D layers.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sometimes split path rotation gets exported like:
|
// Sometimes split path rotation gets exported like:
|
||||||
// "rz": {
|
// "rz": {
|
||||||
// "a": 1,
|
// "a": 1,
|
||||||
|
|
@ -116,6 +117,10 @@ class AnimatableTransformParser {
|
||||||
skew = AnimatableValueParser.parseFloat(reader, composition);
|
skew = AnimatableValueParser.parseFloat(reader, composition);
|
||||||
case 9:
|
case 9:
|
||||||
skewAngle = AnimatableValueParser.parseFloat(reader, composition);
|
skewAngle = AnimatableValueParser.parseFloat(reader, composition);
|
||||||
|
case 10:
|
||||||
|
rotationX = AnimatableValueParser.parseFloat(reader, composition);
|
||||||
|
case 11:
|
||||||
|
rotationY = AnimatableValueParser.parseFloat(reader, composition);
|
||||||
default:
|
default:
|
||||||
reader.skipName();
|
reader.skipName();
|
||||||
reader.skipValue();
|
reader.skipValue();
|
||||||
|
|
@ -134,6 +139,12 @@ class AnimatableTransformParser {
|
||||||
if (isRotationIdentity(rotation)) {
|
if (isRotationIdentity(rotation)) {
|
||||||
rotation = null;
|
rotation = null;
|
||||||
}
|
}
|
||||||
|
if (isRotationIdentity(rotationX)) {
|
||||||
|
rotationX = null;
|
||||||
|
}
|
||||||
|
if (isRotationIdentity(rotationY)) {
|
||||||
|
rotationY = null;
|
||||||
|
}
|
||||||
if (isScaleIdentity(scale)) {
|
if (isScaleIdentity(scale)) {
|
||||||
scale = null;
|
scale = null;
|
||||||
}
|
}
|
||||||
|
|
@ -148,6 +159,8 @@ class AnimatableTransformParser {
|
||||||
position: position,
|
position: position,
|
||||||
scale: scale,
|
scale: scale,
|
||||||
rotation: rotation,
|
rotation: rotation,
|
||||||
|
rotationX: rotationX,
|
||||||
|
rotationY: rotationY,
|
||||||
opacity: opacity,
|
opacity: opacity,
|
||||||
startOpacity: startOpacity,
|
startOpacity: startOpacity,
|
||||||
endOpacity: endOpacity,
|
endOpacity: endOpacity,
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ class FileLottie extends LottieProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => file.hashCode;
|
int get hashCode => file.path.hashCode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => '$runtimeType(file: ${file.path})';
|
String toString() => '$runtimeType(file: ${file.path})';
|
||||||
|
|
|
||||||
|
|
@ -35,3 +35,34 @@ class CacheKey {
|
||||||
String toString() =>
|
String toString() =>
|
||||||
'CacheKey(${composition.hashCode}, $size, $config, $delegates)';
|
'CacheKey(${composition.hashCode}, $size, $config, $delegates)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A [CacheKey] for the raster cache, where frames are rasterized and cropped
|
||||||
|
/// to [sourceRect] ahead of time. Unlike the drawing-commands cache (whose
|
||||||
|
/// cached [Picture] is recorded in full composition space and cropped fresh
|
||||||
|
/// on every draw), the same [composition]/[size] with a different
|
||||||
|
/// [sourceRect] (i.e. a different `fit`/`alignment`) must not share a cached
|
||||||
|
/// image.
|
||||||
|
@immutable
|
||||||
|
class RasterCacheKey extends CacheKey {
|
||||||
|
final Rect sourceRect;
|
||||||
|
|
||||||
|
RasterCacheKey({
|
||||||
|
required super.composition,
|
||||||
|
required super.size,
|
||||||
|
required this.sourceRect,
|
||||||
|
required super.config,
|
||||||
|
required super.delegates,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(super.hashCode, sourceRect);
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(other) =>
|
||||||
|
other is RasterCacheKey &&
|
||||||
|
super == other &&
|
||||||
|
other.sourceRect == sourceRect;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => '${super.toString()}, sourceRect: $sourceRect';
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:ui';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import '../lottie_drawable.dart';
|
import '../lottie_drawable.dart';
|
||||||
import '../render_cache.dart';
|
import '../render_cache.dart';
|
||||||
|
import '../utils.dart';
|
||||||
import 'key.dart';
|
import 'key.dart';
|
||||||
import 'store.dart';
|
import 'store.dart';
|
||||||
|
|
||||||
|
|
@ -107,14 +108,8 @@ base class DrawingEntry extends CacheEntry<CacheKey> {
|
||||||
drawable.compositionLayer.draw(cacheCanvas, _matrix, parentAlpha: 255);
|
drawable.compositionLayer.draw(cacheCanvas, _matrix, parentAlpha: 255);
|
||||||
});
|
});
|
||||||
if (cachedImage != null) {
|
if (cachedImage != null) {
|
||||||
var destinationSize = destinationRect.size;
|
|
||||||
|
|
||||||
canvas.save();
|
canvas.save();
|
||||||
canvas.translate(destinationRect.left, destinationRect.top);
|
canvas.applySourceToDestinationTransform(sourceRect, destinationRect);
|
||||||
canvas.scale(
|
|
||||||
destinationSize.width / sourceRect.width,
|
|
||||||
destinationSize.height / sourceRect.height,
|
|
||||||
);
|
|
||||||
canvas.drawPicture(cachedImage);
|
canvas.drawPicture(cachedImage);
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:ui';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import '../lottie_drawable.dart';
|
import '../lottie_drawable.dart';
|
||||||
import '../render_cache.dart';
|
import '../render_cache.dart';
|
||||||
|
import '../utils.dart';
|
||||||
import 'key.dart';
|
import 'key.dart';
|
||||||
import 'store.dart';
|
import 'store.dart';
|
||||||
|
|
||||||
|
|
@ -27,7 +28,7 @@ class RenderCacheRaster implements RenderCache {
|
||||||
}
|
}
|
||||||
|
|
||||||
class RasterAnimationCache extends AnimationCache {
|
class RasterAnimationCache extends AnimationCache {
|
||||||
final Handle<RasterEntry, CacheKey> handle;
|
final Handle<RasterEntry, RasterCacheKey> handle;
|
||||||
|
|
||||||
RasterAnimationCache(this.handle);
|
RasterAnimationCache(this.handle);
|
||||||
|
|
||||||
|
|
@ -52,9 +53,10 @@ class RasterAnimationCache extends AnimationCache {
|
||||||
(rect.size.height * devicePixelRatio).roundToDouble(),
|
(rect.size.height * devicePixelRatio).roundToDouble(),
|
||||||
);
|
);
|
||||||
|
|
||||||
var key = CacheKey(
|
var key = RasterCacheKey(
|
||||||
composition: drawable.composition,
|
composition: drawable.composition,
|
||||||
size: cacheImageSize,
|
size: cacheImageSize,
|
||||||
|
sourceRect: sourceRect,
|
||||||
config: drawable.configHash(),
|
config: drawable.configHash(),
|
||||||
delegates: drawable.delegatesHash(),
|
delegates: drawable.delegatesHash(),
|
||||||
);
|
);
|
||||||
|
|
@ -72,7 +74,7 @@ class RasterAnimationCache extends AnimationCache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RasterStore extends Store<RasterEntry, CacheKey> {
|
class RasterStore extends Store<RasterEntry, RasterCacheKey> {
|
||||||
final int maxMemory;
|
final int maxMemory;
|
||||||
|
|
||||||
RasterStore(this.maxMemory);
|
RasterStore(this.maxMemory);
|
||||||
|
|
@ -88,7 +90,7 @@ class RasterStore extends Store<RasterEntry, CacheKey> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
RasterEntry createEntry(CacheKey key) {
|
RasterEntry createEntry(RasterCacheKey key) {
|
||||||
return RasterEntry(this, key);
|
return RasterEntry(this, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -97,7 +99,7 @@ class RasterStore extends Store<RasterEntry, CacheKey> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
base class RasterEntry extends CacheEntry<CacheKey> {
|
base class RasterEntry extends CacheEntry<RasterCacheKey> {
|
||||||
final RasterStore store;
|
final RasterStore store;
|
||||||
final images = <double, Image>{};
|
final images = <double, Image>{};
|
||||||
int currentMemory = 0;
|
int currentMemory = 0;
|
||||||
|
|
@ -146,13 +148,7 @@ base class RasterEntry extends CacheEntry<CacheKey> {
|
||||||
var cacheImageSize = key.size;
|
var cacheImageSize = key.size;
|
||||||
|
|
||||||
var cachedImage = imageForProgress(progress, (cacheCanvas) {
|
var cachedImage = imageForProgress(progress, (cacheCanvas) {
|
||||||
_matrix.setIdentity();
|
_matrix.setSourceToDestinationScale(sourceRect, cacheImageSize);
|
||||||
_matrix.scaleByDouble(
|
|
||||||
cacheImageSize.width / sourceSize.width,
|
|
||||||
cacheImageSize.height / sourceSize.height,
|
|
||||||
cacheImageSize.width / sourceSize.width,
|
|
||||||
1,
|
|
||||||
);
|
|
||||||
drawable.compositionLayer.draw(cacheCanvas, _matrix, parentAlpha: 255);
|
drawable.compositionLayer.draw(cacheCanvas, _matrix, parentAlpha: 255);
|
||||||
});
|
});
|
||||||
if (cachedImage != null) {
|
if (cachedImage != null) {
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,13 @@ class RenderLottie extends RenderBox {
|
||||||
bool? enableApplyingOpacityToLayers,
|
bool? enableApplyingOpacityToLayers,
|
||||||
double progress = 0.0,
|
double progress = 0.0,
|
||||||
FrameRate? frameRate,
|
FrameRate? frameRate,
|
||||||
double? width,
|
this._width,
|
||||||
double? height,
|
this._height,
|
||||||
BoxFit? fit,
|
this._fit,
|
||||||
AlignmentGeometry alignment = Alignment.center,
|
this._alignment = Alignment.center,
|
||||||
FilterQuality? filterQuality,
|
FilterQuality? filterQuality,
|
||||||
RenderCache? renderCache,
|
RenderCache? renderCache,
|
||||||
required double devicePixelRatio,
|
required this._devicePixelRatio,
|
||||||
}) : assert(progress >= 0.0 && progress <= 1.0),
|
}) : assert(progress >= 0.0 && progress <= 1.0),
|
||||||
assert(
|
assert(
|
||||||
renderCache == null || frameRate != FrameRate.max,
|
renderCache == null || frameRate != FrameRate.max,
|
||||||
|
|
@ -39,12 +39,7 @@ class RenderLottie extends RenderBox {
|
||||||
enableApplyingOpacityToLayers ?? false
|
enableApplyingOpacityToLayers ?? false
|
||||||
..filterQuality = filterQuality)
|
..filterQuality = filterQuality)
|
||||||
: null,
|
: null,
|
||||||
_width = width,
|
_renderCache = renderCache;
|
||||||
_height = height,
|
|
||||||
_fit = fit,
|
|
||||||
_alignment = alignment,
|
|
||||||
_renderCache = renderCache,
|
|
||||||
_devicePixelRatio = devicePixelRatio;
|
|
||||||
|
|
||||||
/// The lottie composition to display.
|
/// The lottie composition to display.
|
||||||
LottieComposition? get composition => _drawable?.composition;
|
LottieComposition? get composition => _drawable?.composition;
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,37 @@ extension Matrix4Extension on Matrix4 {
|
||||||
|
|
||||||
return p0.x == p1.x || p0.y == p1.y;
|
return p0.x == p1.x || p0.y == p1.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets this matrix to map `p -> (p - sourceRect.topLeft) * scale`,
|
||||||
|
/// i.e. crop to [sourceRect] and scale it to fit [destinationSize].
|
||||||
|
void setSourceToDestinationScale(Rect sourceRect, Size destinationSize) {
|
||||||
|
var scaleX = destinationSize.width / sourceRect.width;
|
||||||
|
var scaleY = destinationSize.height / sourceRect.height;
|
||||||
|
setIdentity();
|
||||||
|
translateByDouble(
|
||||||
|
-sourceRect.left * scaleX,
|
||||||
|
-sourceRect.top * scaleY,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
scaleByDouble(scaleX, scaleY, scaleX, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension CanvasExtension on Canvas {
|
||||||
|
/// Applies `destinationRect.topLeft + (p - sourceRect.topLeft) * scale`
|
||||||
|
/// to subsequent drawing, i.e. positions and crops/scales a source rect
|
||||||
|
/// into a destination rect.
|
||||||
|
void applySourceToDestinationTransform(
|
||||||
|
Rect sourceRect,
|
||||||
|
Rect destinationRect,
|
||||||
|
) {
|
||||||
|
var scaleX = destinationRect.width / sourceRect.width;
|
||||||
|
var scaleY = destinationRect.height / sourceRect.height;
|
||||||
|
translate(destinationRect.left, destinationRect.top);
|
||||||
|
scale(scaleX, scaleY);
|
||||||
|
translate(-sourceRect.left, -sourceRect.top);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension OffsetExtension on Offset {
|
extension OffsetExtension on Offset {
|
||||||
|
|
|
||||||
|
|
@ -394,6 +394,14 @@ class ValueDelegate<T> {
|
||||||
DropShadow Function(LottieFrameInfo<DropShadow>)? callback,
|
DropShadow Function(LottieFrameInfo<DropShadow>)? callback,
|
||||||
}) => ValueDelegate._(keyPath, LottieProperty.dropShadow, value, callback);
|
}) => ValueDelegate._(keyPath, LottieProperty.dropShadow, value, callback);
|
||||||
|
|
||||||
|
/// Replace the outline of a shape at runtime. The callback receives the start/end paths of
|
||||||
|
/// the underlying shape keyframe and returns the [Path] to draw.
|
||||||
|
static ValueDelegate<Path> path(
|
||||||
|
List<String> keyPath, {
|
||||||
|
Path? value,
|
||||||
|
Path Function(LottieFrameInfo<Path>)? callback,
|
||||||
|
}) => ValueDelegate._(keyPath, LottieProperty.path, value, callback);
|
||||||
|
|
||||||
ResolvedValueDelegate<T>? _resolved;
|
ResolvedValueDelegate<T>? _resolved;
|
||||||
ResolvedValueDelegate _resolve(List<KeyPath> resolvedPaths) {
|
ResolvedValueDelegate _resolve(List<KeyPath> resolvedPaths) {
|
||||||
_resolved = ResolvedValueDelegate<T>(this, resolvedPaths);
|
_resolved = ResolvedValueDelegate<T>(this, resolvedPaths);
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,18 @@
|
||||||
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: 3.3.2
|
version: 3.5.1
|
||||||
repository: https://github.com/xvrh/lottie-flutter
|
repository: https://github.com/xvrh/lottie-flutter
|
||||||
|
|
||||||
|
workspace:
|
||||||
|
- example
|
||||||
|
|
||||||
funding:
|
funding:
|
||||||
- https://www.buymeacoffee.com/xvrh
|
- https://www.buymeacoffee.com/xvrh
|
||||||
- https://github.com/sponsors/xvrh
|
- https://github.com/sponsors/xvrh
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '^3.9.0'
|
sdk: '^3.12.0'
|
||||||
flutter: '>=3.35.0'
|
flutter: '>=3.44.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
archive: ^4.0.0
|
archive: ^4.0.0
|
||||||
|
|
|
||||||
|
|
@ -1,37 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:lottie/src/utils/characters.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test('TrimTrailing characters', () {
|
|
||||||
expect(
|
|
||||||
'ab c d '.characters.trimTrailing(' '.characters),
|
|
||||||
'ab c d'.characters,
|
|
||||||
);
|
|
||||||
expect(' '.characters.trimTrailing(' '.characters), ''.characters);
|
|
||||||
expect(' a '.characters.trimTrailing(' '.characters), ' a'.characters);
|
|
||||||
expect(' aa'.characters.trimTrailing('a'.characters), ' '.characters);
|
|
||||||
expect('aabcbc'.characters.trimTrailing('bc'.characters), 'aa'.characters);
|
|
||||||
expect('bcbc'.characters.trimTrailing('bc'.characters), ''.characters);
|
|
||||||
expect(''.characters.trimTrailing(' '.characters), ''.characters);
|
|
||||||
expect(''.characters.trimTrailing('bc'.characters), ''.characters);
|
|
||||||
expect(' '.characters.trimTrailing('bc'.characters), ' '.characters);
|
|
||||||
expect(' bc'.characters.trimTrailing('bc'.characters), ' '.characters);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('TrimLeading characters', () {
|
|
||||||
expect(' ab '.characters.trimLeading(' '.characters), 'ab '.characters);
|
|
||||||
expect(' '.characters.trimLeading(' '.characters), ''.characters);
|
|
||||||
expect(' '.characters.trimLeading(' '.characters), ''.characters);
|
|
||||||
expect(' a'.characters.trimLeading(' '.characters), 'a'.characters);
|
|
||||||
expect('abc'.characters.trimLeading('ab'.characters), 'c'.characters);
|
|
||||||
expect('ababc'.characters.trimLeading('ab'.characters), 'c'.characters);
|
|
||||||
expect('abab'.characters.trimLeading('ab'.characters), ''.characters);
|
|
||||||
expect('ababcd'.characters.trimLeading('ab'.characters), 'cd'.characters);
|
|
||||||
expect(''.characters.trimLeading(''.characters), ''.characters);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Trim characters', () {
|
|
||||||
expect(' ab '.characters.trim(' '.characters), 'ab'.characters);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:ui';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:lottie/src/composition.dart';
|
|
||||||
import 'package:lottie/src/lottie_drawable.dart';
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
var assetsPath = 'example/assets';
|
|
||||||
for (var file
|
|
||||||
in Directory(assetsPath)
|
|
||||||
.listSync(recursive: true)
|
|
||||||
.whereType<File>()
|
|
||||||
.where((f) => f.path.endsWith('.json'))) {
|
|
||||||
test('Parse and draw ${p.relative(file.path, from: assetsPath)}', () async {
|
|
||||||
var composition = await LottieComposition.fromBytes(
|
|
||||||
file.readAsBytesSync(),
|
|
||||||
);
|
|
||||||
expect(composition, isNotNull);
|
|
||||||
|
|
||||||
var drawable = LottieDrawable(composition);
|
|
||||||
|
|
||||||
var recorder = PictureRecorder();
|
|
||||||
var canvas = Canvas(recorder);
|
|
||||||
for (var progress = 0; progress <= 100; progress += 20) {
|
|
||||||
drawable
|
|
||||||
..setProgress(progress / 100)
|
|
||||||
..draw(canvas, const Rect.fromLTWH(0, 0, 200, 200));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,689 +0,0 @@
|
||||||
{
|
|
||||||
"v": "5.6.6",
|
|
||||||
"ip": 0,
|
|
||||||
"op": 1,
|
|
||||||
"fr": 60,
|
|
||||||
"w": 953,
|
|
||||||
"h": 272,
|
|
||||||
"layers": [
|
|
||||||
{
|
|
||||||
"ind": 2345,
|
|
||||||
"nm": "surface13687",
|
|
||||||
"ao": 0,
|
|
||||||
"ip": 0,
|
|
||||||
"op": 60,
|
|
||||||
"st": 0,
|
|
||||||
"ty": 4,
|
|
||||||
"ks": {
|
|
||||||
"ty": "tr",
|
|
||||||
"o": { "k": 100 },
|
|
||||||
"r": { "k": 0 },
|
|
||||||
"p": { "k": [0, 0] },
|
|
||||||
"a": { "k": [0, 0] },
|
|
||||||
"s": { "k": [133.33, 133.33] },
|
|
||||||
"sk": { "k": 0 },
|
|
||||||
"sa": { "k": 0 }
|
|
||||||
},
|
|
||||||
"shapes": [
|
|
||||||
{
|
|
||||||
"ty": "gr",
|
|
||||||
"hd": false,
|
|
||||||
"nm": "surface13687",
|
|
||||||
"it": [
|
|
||||||
{
|
|
||||||
"ty": "gr",
|
|
||||||
"hd": false,
|
|
||||||
"it": [
|
|
||||||
{
|
|
||||||
"ty": "sh",
|
|
||||||
"ks": {
|
|
||||||
"k": {
|
|
||||||
"i": [
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[20, -0.05],
|
|
||||||
[0.73, 0.64],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"o": [
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[-20.01, 0.05],
|
|
||||||
[-1.14, 0.25],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"v": [
|
|
||||||
[79.56, 177.9],
|
|
||||||
[110.31, 147.43],
|
|
||||||
[163.43, 200.61],
|
|
||||||
[103.41, 200.64],
|
|
||||||
[100.84, 199.21],
|
|
||||||
[79.55, 177.9]
|
|
||||||
],
|
|
||||||
"c": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "fl",
|
|
||||||
"o": { "k": 100 },
|
|
||||||
"c": { "k": [0.03, 0.35, 0.61, 1] }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "tr",
|
|
||||||
"o": { "k": 100 },
|
|
||||||
"r": { "k": 0 },
|
|
||||||
"p": { "k": [0, 0] },
|
|
||||||
"a": { "k": [0, 0] },
|
|
||||||
"s": { "k": [100, 100] },
|
|
||||||
"sk": { "k": 0 },
|
|
||||||
"sa": { "k": 0 },
|
|
||||||
"hd": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "gr",
|
|
||||||
"hd": false,
|
|
||||||
"it": [
|
|
||||||
{
|
|
||||||
"ty": "sh",
|
|
||||||
"ks": {
|
|
||||||
"k": {
|
|
||||||
"i": [
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"o": [
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"v": [
|
|
||||||
[49.02, 147.35],
|
|
||||||
[79.55, 116.86],
|
|
||||||
[110.18, 147.3],
|
|
||||||
[110.31, 147.43],
|
|
||||||
[79.56, 177.9]
|
|
||||||
],
|
|
||||||
"c": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "fl",
|
|
||||||
"o": { "k": 100 },
|
|
||||||
"c": { "k": [0.12, 0.74, 0.99, 1] }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "tr",
|
|
||||||
"o": { "k": 100 },
|
|
||||||
"r": { "k": 0 },
|
|
||||||
"p": { "k": [0, 0] },
|
|
||||||
"a": { "k": [0, 0] },
|
|
||||||
"s": { "k": [100, 100] },
|
|
||||||
"sk": { "k": 0 },
|
|
||||||
"sa": { "k": 0 },
|
|
||||||
"hd": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "gr",
|
|
||||||
"hd": false,
|
|
||||||
"it": [
|
|
||||||
{
|
|
||||||
"ty": "sh",
|
|
||||||
"ks": {
|
|
||||||
"k": {
|
|
||||||
"i": [
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[17.48, -0.07],
|
|
||||||
[0.15, -1.03],
|
|
||||||
[0, 0],
|
|
||||||
[-1.04, -0.16],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[13.93, -0.01],
|
|
||||||
[2.05, -0.36],
|
|
||||||
[0, 0],
|
|
||||||
[4.49, 0.1],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"o": [
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[-17.47, 0],
|
|
||||||
[-1.34, 0.44],
|
|
||||||
[0, 0],
|
|
||||||
[0.44, 1.33],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[-13.93, -0.01],
|
|
||||||
[-2.07, -0.01],
|
|
||||||
[0, 0],
|
|
||||||
[-4.49, -0.18],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"v": [
|
|
||||||
[249.55, 45.91],
|
|
||||||
[316.33, 45.93],
|
|
||||||
[316.32, 59.08],
|
|
||||||
[263.9, 59.1],
|
|
||||||
[263.05, 62.24],
|
|
||||||
[263.1, 97.61],
|
|
||||||
[266.25, 98.46],
|
|
||||||
[311.07, 98.44],
|
|
||||||
[311.07, 111.57],
|
|
||||||
[269.28, 111.57],
|
|
||||||
[263.07, 111.79],
|
|
||||||
[262.91, 161.71],
|
|
||||||
[249.44, 161.6],
|
|
||||||
[249.43, 48.03],
|
|
||||||
[249.55, 45.91]
|
|
||||||
],
|
|
||||||
"c": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "sh",
|
|
||||||
"ks": {
|
|
||||||
"k": {
|
|
||||||
"i": [
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[4.45, -0.12],
|
|
||||||
[0, 1.84],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"o": [
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[-4.46, -0.08],
|
|
||||||
[-0.34, -1.83],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"v": [
|
|
||||||
[332.52, 45.93],
|
|
||||||
[346.23, 45.93],
|
|
||||||
[346.24, 124.5],
|
|
||||||
[346.09, 161.5],
|
|
||||||
[332.71, 161.51],
|
|
||||||
[332.51, 155.98],
|
|
||||||
[332.52, 45.93]
|
|
||||||
],
|
|
||||||
"c": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "sh",
|
|
||||||
"ks": {
|
|
||||||
"k": {
|
|
||||||
"i": [
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[-6.71, -0.54],
|
|
||||||
[0.07, -4.17],
|
|
||||||
[6.68, 0.1],
|
|
||||||
[0, 0],
|
|
||||||
[-4.17, -2.21],
|
|
||||||
[-4.51, 2.61],
|
|
||||||
[0, 0],
|
|
||||||
[5.33, 0.73],
|
|
||||||
[2.27, 7.45],
|
|
||||||
[0.03, 4.26],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[4.75, 0.17],
|
|
||||||
[-0.27, 4.24],
|
|
||||||
[-4.82, 0.37]
|
|
||||||
],
|
|
||||||
"o": [
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[6.73, 0.16],
|
|
||||||
[-0.19, 4.17],
|
|
||||||
[-6.69, 0.02],
|
|
||||||
[0, 0],
|
|
||||||
[0.14, 4.29],
|
|
||||||
[4.76, 2.71],
|
|
||||||
[0, 0],
|
|
||||||
[-4.96, 2.25],
|
|
||||||
[-7.52, -1.15],
|
|
||||||
[-1.51, -4.08],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[-4.75, -0.25],
|
|
||||||
[0.03, -4.25],
|
|
||||||
[4.83, -0.08],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"v": [
|
|
||||||
[466.71, 55.67],
|
|
||||||
[480.46, 55.68],
|
|
||||||
[480.49, 78.93],
|
|
||||||
[500.69, 79.16],
|
|
||||||
[500.59, 91.68],
|
|
||||||
[480.53, 91.64],
|
|
||||||
[480.48, 137.95],
|
|
||||||
[485.71, 149.63],
|
|
||||||
[500.83, 148.46],
|
|
||||||
[500.83, 161.93],
|
|
||||||
[484.87, 163.72],
|
|
||||||
[467.88, 150],
|
|
||||||
[466.69, 137.26],
|
|
||||||
[466.66, 92.16],
|
|
||||||
[466.32, 91.78],
|
|
||||||
[452.05, 91.65],
|
|
||||||
[452.16, 78.92],
|
|
||||||
[466.66, 78.77]
|
|
||||||
],
|
|
||||||
"c": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "sh",
|
|
||||||
"ks": {
|
|
||||||
"k": {
|
|
||||||
"i": [
|
|
||||||
[0, 0],
|
|
||||||
[-4.57, -0.16],
|
|
||||||
[0, 0],
|
|
||||||
[-0.41, -7.62],
|
|
||||||
[-6.51, 0.04],
|
|
||||||
[0, 0],
|
|
||||||
[6.55, 0.04],
|
|
||||||
[0, 0],
|
|
||||||
[-3.97, -2.02],
|
|
||||||
[-4.52, 2.71],
|
|
||||||
[0, 0],
|
|
||||||
[5.2, 0.6],
|
|
||||||
[3.18, 6.19],
|
|
||||||
[0.14, 3.58],
|
|
||||||
[0, 0],
|
|
||||||
[4.74, 0.02],
|
|
||||||
[0, 0],
|
|
||||||
[-4.67, -0.16],
|
|
||||||
[-0.29, 7.79]
|
|
||||||
],
|
|
||||||
"o": [
|
|
||||||
[4.57, 0.16],
|
|
||||||
[0, 0],
|
|
||||||
[0.17, 7.64],
|
|
||||||
[6.52, -0.12],
|
|
||||||
[0, 0],
|
|
||||||
[-6.55, 0.25],
|
|
||||||
[0, 0],
|
|
||||||
[-0.01, 4.1],
|
|
||||||
[4.87, 2.66],
|
|
||||||
[0, 0],
|
|
||||||
[-4.82, 2.18],
|
|
||||||
[-6.76, -0.9],
|
|
||||||
[-1.53, -3.24],
|
|
||||||
[0, 0],
|
|
||||||
[-4.75, -0.01],
|
|
||||||
[0, 0],
|
|
||||||
[4.67, 0.13],
|
|
||||||
[0.5, -7.77],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"v": [
|
|
||||||
[524.82, 55.62],
|
|
||||||
[538.54, 55.73],
|
|
||||||
[538.86, 56.07],
|
|
||||||
[539.04, 78.99],
|
|
||||||
[558.58, 78.93],
|
|
||||||
[558.63, 91.57],
|
|
||||||
[538.94, 91.63],
|
|
||||||
[538.87, 138.74],
|
|
||||||
[544.01, 149.71],
|
|
||||||
[559.3, 148.29],
|
|
||||||
[559.29, 161.84],
|
|
||||||
[543.82, 163.83],
|
|
||||||
[527.28, 152.93],
|
|
||||||
[524.75, 142.52],
|
|
||||||
[524.75, 91.69],
|
|
||||||
[510.52, 91.69],
|
|
||||||
[510.55, 78.89],
|
|
||||||
[524.57, 79.01],
|
|
||||||
[524.82, 55.63]
|
|
||||||
],
|
|
||||||
"c": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "sh",
|
|
||||||
"ks": {
|
|
||||||
"k": {
|
|
||||||
"i": [
|
|
||||||
[0, 0],
|
|
||||||
[-9.7, -3.3],
|
|
||||||
[-3.15, -11.24],
|
|
||||||
[0.29, -6.48],
|
|
||||||
[0, 0],
|
|
||||||
[-4.83, -5.22],
|
|
||||||
[-9.97, 3.14],
|
|
||||||
[-3.59, 6.19],
|
|
||||||
[-3.96, -1.82],
|
|
||||||
[11.57, -2.22],
|
|
||||||
[8.71, 4.7],
|
|
||||||
[2.22, 12.3],
|
|
||||||
[-3.55, 8.97],
|
|
||||||
[-9.7, 4.25]
|
|
||||||
],
|
|
||||||
"o": [
|
|
||||||
[9.5, -3.94],
|
|
||||||
[11.11, 3.83],
|
|
||||||
[2.06, 6.23],
|
|
||||||
[0, 0],
|
|
||||||
[0.54, 6.95],
|
|
||||||
[6.86, 8],
|
|
||||||
[7, -2.06],
|
|
||||||
[3.98, 1.78],
|
|
||||||
[-5.41, 10.32],
|
|
||||||
[-9.5, 1.78],
|
|
||||||
[-11.31, -5.62],
|
|
||||||
[-1.61, -9.45],
|
|
||||||
[3.73, -9.85],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"v": [
|
|
||||||
[591.59, 79.36],
|
|
||||||
[621.73, 78.65],
|
|
||||||
[644.59, 103.42],
|
|
||||||
[646.27, 122.75],
|
|
||||||
[580.98, 122.82],
|
|
||||||
[588.43, 142.08],
|
|
||||||
[617.08, 149.91],
|
|
||||||
[633.13, 136.27],
|
|
||||||
[644.93, 141.9],
|
|
||||||
[618.09, 162.64],
|
|
||||||
[589.48, 159.77],
|
|
||||||
[568.16, 130.41],
|
|
||||||
[570.61, 101.99],
|
|
||||||
[591.58, 79.36]
|
|
||||||
],
|
|
||||||
"c": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "sh",
|
|
||||||
"ks": {
|
|
||||||
"k": {
|
|
||||||
"i": [
|
|
||||||
[0, 0],
|
|
||||||
[1.61, -8.46],
|
|
||||||
[0, 0],
|
|
||||||
[4.05, 4],
|
|
||||||
[9.3, -4.04]
|
|
||||||
],
|
|
||||||
"o": [
|
|
||||||
[-7.99, 3.69],
|
|
||||||
[0, 0],
|
|
||||||
[-0.8, -5.53],
|
|
||||||
[-7.09, -7.61],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"v": [
|
|
||||||
[596.75, 91.22],
|
|
||||||
[582.01, 111.57],
|
|
||||||
[632.08, 111.41],
|
|
||||||
[625.27, 96.27],
|
|
||||||
[596.75, 91.23]
|
|
||||||
],
|
|
||||||
"c": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "sh",
|
|
||||||
"ks": {
|
|
||||||
"k": {
|
|
||||||
"i": [
|
|
||||||
[0, 0],
|
|
||||||
[-12.7, -5.32],
|
|
||||||
[0, 0],
|
|
||||||
[5.63, -1.66],
|
|
||||||
[2.18, -7.55],
|
|
||||||
[-0.06, -5.23],
|
|
||||||
[0.44, -6.76],
|
|
||||||
[-0.5, -5.85],
|
|
||||||
[4.66, -0.16],
|
|
||||||
[0.07, 17.64],
|
|
||||||
[0, 0],
|
|
||||||
[-4.38, -0.11],
|
|
||||||
[-0.73, -4.37]
|
|
||||||
],
|
|
||||||
"o": [
|
|
||||||
[5.23, -12.7],
|
|
||||||
[0, 0],
|
|
||||||
[-5.33, -2.1],
|
|
||||||
[-7.7, 2.29],
|
|
||||||
[-1.5, 5.1],
|
|
||||||
[-0.59, 6.77],
|
|
||||||
[0.36, 5.84],
|
|
||||||
[-4.66, 0.23],
|
|
||||||
[-0.27, -17.63],
|
|
||||||
[0, 0],
|
|
||||||
[4.37, 0.12],
|
|
||||||
[0.4, 4.38],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"v": [
|
|
||||||
[675.99, 92.31],
|
|
||||||
[709.98, 78.26],
|
|
||||||
[709.98, 93.08],
|
|
||||||
[693.04, 91.34],
|
|
||||||
[677.46, 108.05],
|
|
||||||
[676.59, 123.7],
|
|
||||||
[676.44, 144],
|
|
||||||
[676.62, 161.54],
|
|
||||||
[662.63, 161.68],
|
|
||||||
[662.52, 108.76],
|
|
||||||
[662.62, 79.07],
|
|
||||||
[675.75, 79.16],
|
|
||||||
[675.99, 92.31]
|
|
||||||
],
|
|
||||||
"c": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "sh",
|
|
||||||
"ks": {
|
|
||||||
"k": {
|
|
||||||
"i": [
|
|
||||||
[0, 0],
|
|
||||||
[-4.63, -0.52],
|
|
||||||
[0, 0],
|
|
||||||
[-4.16, -4.66],
|
|
||||||
[-7.34, 2.35],
|
|
||||||
[0.39, 10.38],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[4.43, 0.05],
|
|
||||||
[0, 0],
|
|
||||||
[4.12, -2.27],
|
|
||||||
[9.44, 2.72],
|
|
||||||
[3.05, 5.58],
|
|
||||||
[0.21, 6.99],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"o": [
|
|
||||||
[4.65, 0.2],
|
|
||||||
[0, 0],
|
|
||||||
[-0.05, 6.01],
|
|
||||||
[5.36, 5.94],
|
|
||||||
[9.8, -3.51],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[-4.44, -0.07],
|
|
||||||
[0, 0],
|
|
||||||
[-2.87, 3.61],
|
|
||||||
[-8.43, 5.32],
|
|
||||||
[-6.09, -1.84],
|
|
||||||
[-3.62, -6.14],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"v": [
|
|
||||||
[365.89, 78.91],
|
|
||||||
[379.86, 79.15],
|
|
||||||
[379.79, 128.31],
|
|
||||||
[384.75, 145.54],
|
|
||||||
[406.54, 150.22],
|
|
||||||
[422.78, 125.95],
|
|
||||||
[422.93, 78.91],
|
|
||||||
[436.93, 78.94],
|
|
||||||
[436.85, 161.48],
|
|
||||||
[423.55, 161.43],
|
|
||||||
[423.5, 149.68],
|
|
||||||
[413.49, 159.21],
|
|
||||||
[384.93, 162.51],
|
|
||||||
[370.48, 150.83],
|
|
||||||
[365.73, 130.45],
|
|
||||||
[365.9, 78.91]
|
|
||||||
],
|
|
||||||
"c": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ "ty": "fl", "o": { "k": 54 }, "c": { "k": [0, 0, 0, 1] } },
|
|
||||||
{
|
|
||||||
"ty": "tr",
|
|
||||||
"o": { "k": 100 },
|
|
||||||
"r": { "k": 0 },
|
|
||||||
"p": { "k": [0, 0] },
|
|
||||||
"a": { "k": [0, 0] },
|
|
||||||
"s": { "k": [100, 100] },
|
|
||||||
"sk": { "k": 0 },
|
|
||||||
"sa": { "k": 0 },
|
|
||||||
"hd": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "gr",
|
|
||||||
"hd": false,
|
|
||||||
"it": [
|
|
||||||
{
|
|
||||||
"ty": "sh",
|
|
||||||
"ks": {
|
|
||||||
"k": {
|
|
||||||
"i": [
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"o": [
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"v": [
|
|
||||||
[102.21, 3.07],
|
|
||||||
[163.46, 3],
|
|
||||||
[60.7, 105.71],
|
|
||||||
[33.92, 132.34],
|
|
||||||
[3.45, 101.83]
|
|
||||||
],
|
|
||||||
"c": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "sh",
|
|
||||||
"ks": {
|
|
||||||
"k": {
|
|
||||||
"i": [
|
|
||||||
[0, 0],
|
|
||||||
[-1.11, 0.14],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"o": [
|
|
||||||
[0.8, -1.05],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0],
|
|
||||||
[0, 0]
|
|
||||||
],
|
|
||||||
"v": [
|
|
||||||
[101.64, 94.78],
|
|
||||||
[105.01, 94.11],
|
|
||||||
[163.41, 94.12],
|
|
||||||
[110.18, 147.3],
|
|
||||||
[79.55, 116.86],
|
|
||||||
[101.64, 94.78]
|
|
||||||
],
|
|
||||||
"c": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "fl",
|
|
||||||
"o": { "k": 100 },
|
|
||||||
"c": { "k": [0.27, 0.82, 0.99, 1] }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "tr",
|
|
||||||
"o": { "k": 100 },
|
|
||||||
"r": { "k": 0 },
|
|
||||||
"p": { "k": [0, 0] },
|
|
||||||
"a": { "k": [0, 0] },
|
|
||||||
"s": { "k": [100, 100] },
|
|
||||||
"sk": { "k": 0 },
|
|
||||||
"sa": { "k": 0 },
|
|
||||||
"hd": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"ty": "tr",
|
|
||||||
"o": { "k": 100 },
|
|
||||||
"r": { "k": 0 },
|
|
||||||
"p": { "k": [0, 0] },
|
|
||||||
"a": { "k": [0, 0] },
|
|
||||||
"s": { "k": [100, 100] },
|
|
||||||
"sk": { "k": 0 },
|
|
||||||
"sa": { "k": 0 },
|
|
||||||
"hd": false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"meta": { "g": "LF SVG to Lottie" }
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:ui';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:lottie/lottie.dart';
|
|
||||||
import 'package:lottie/src/utils.dart';
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('Dotlottie', (tester) async {
|
|
||||||
var size = const Size(500, 400);
|
|
||||||
tester.view.physicalSize = size;
|
|
||||||
tester.view.devicePixelRatio = 1.0;
|
|
||||||
|
|
||||||
var provider = FileLottie(
|
|
||||||
File('example/assets/cat.lottie'),
|
|
||||||
decoder: customDecoder,
|
|
||||||
);
|
|
||||||
await tester.runAsync(() => provider.load());
|
|
||||||
|
|
||||||
await tester.pumpWidget(LottieBuilder(lottie: provider));
|
|
||||||
|
|
||||||
await expectLater(
|
|
||||||
find.byType(Lottie),
|
|
||||||
matchesGoldenFile(p.join('goldens/dotlottie.png')),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<LottieComposition?> customDecoder(List<int> bytes) {
|
|
||||||
return LottieComposition.decodeZip(
|
|
||||||
bytes,
|
|
||||||
filePicker: (files) {
|
|
||||||
return files.firstWhereOrNull(
|
|
||||||
(f) => f.name.startsWith('animations/') && f.name.endsWith('.json'),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,96 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:ui' as ui;
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:lottie/lottie.dart';
|
|
||||||
import 'utils.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('Can specify ImageProvider with zip file ', (tester) async {
|
|
||||||
var size = const Size(500, 400);
|
|
||||||
tester.view.physicalSize = size;
|
|
||||||
tester.view.devicePixelRatio = 1.0;
|
|
||||||
|
|
||||||
var callCount = 0;
|
|
||||||
ImageProvider imageProviderFactory(LottieImageAsset image) {
|
|
||||||
++callCount;
|
|
||||||
return FileImage(File('example/assets/Images/WeAccept/img_0.png'));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<LottieComposition?> decoder(List<int> bytes) {
|
|
||||||
return LottieComposition.decodeZip(
|
|
||||||
bytes,
|
|
||||||
imageProviderFactory: imageProviderFactory,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var composition = (await tester.runAsync(
|
|
||||||
() => FileLottie(
|
|
||||||
File('example/assets/spinning_carrousel.zip'),
|
|
||||||
imageProviderFactory: imageProviderFactory,
|
|
||||||
decoder: decoder,
|
|
||||||
).load(),
|
|
||||||
))!;
|
|
||||||
|
|
||||||
await tester.pumpWidget(FilmStrip(composition, size: size));
|
|
||||||
|
|
||||||
expect(callCount, 2);
|
|
||||||
await expectLater(
|
|
||||||
find.byType(FilmStrip),
|
|
||||||
matchesGoldenFile('goldens/dynamic_image/zip_with_provider.png'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
testWidgets('Can specify image delegate', (tester) async {
|
|
||||||
var size = const Size(500, 400);
|
|
||||||
tester.view.physicalSize = size;
|
|
||||||
tester.view.devicePixelRatio = 1.0;
|
|
||||||
|
|
||||||
var image = await tester.runAsync(
|
|
||||||
() async => loadImage(
|
|
||||||
FileImage(File('example/assets/Images/WeAccept/img_0.png')),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
var composition = (await tester.runAsync(
|
|
||||||
() async =>
|
|
||||||
FileLottie(File('example/assets/spinning_carrousel.zip')).load(),
|
|
||||||
))!;
|
|
||||||
|
|
||||||
var delegates = LottieDelegates(
|
|
||||||
image: (composition, asset) {
|
|
||||||
return image;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
await tester.pumpWidget(
|
|
||||||
FilmStrip(composition, size: size, delegates: delegates),
|
|
||||||
);
|
|
||||||
|
|
||||||
await expectLater(
|
|
||||||
find.byType(FilmStrip),
|
|
||||||
matchesGoldenFile('goldens/dynamic_image/delegate.png'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<ui.Image?> loadImage(ImageProvider provider) {
|
|
||||||
var completer = Completer<ui.Image?>();
|
|
||||||
var imageStream = provider.resolve(ImageConfiguration.empty);
|
|
||||||
late ImageStreamListener listener;
|
|
||||||
listener = ImageStreamListener(
|
|
||||||
(image, synchronousLoaded) {
|
|
||||||
imageStream.removeListener(listener);
|
|
||||||
|
|
||||||
completer.complete(image.image);
|
|
||||||
},
|
|
||||||
onError: (dynamic e, _) {
|
|
||||||
imageStream.removeListener(listener);
|
|
||||||
|
|
||||||
completer.complete();
|
|
||||||
},
|
|
||||||
);
|
|
||||||
imageStream.addListener(listener);
|
|
||||||
|
|
||||||
return completer.future;
|
|
||||||
}
|
|
||||||
|
|
@ -1,478 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:ui';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:lottie/lottie.dart';
|
|
||||||
import 'utils.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
void testGolden(
|
|
||||||
String description,
|
|
||||||
ValueDelegate delegate, {
|
|
||||||
double? progress,
|
|
||||||
String? filePath,
|
|
||||||
}) {
|
|
||||||
filePath ??= 'Tests/Shapes.json';
|
|
||||||
|
|
||||||
var screenshotName = description
|
|
||||||
.toLowerCase()
|
|
||||||
.replaceAll(RegExp('[^a-z0-9 ]'), '')
|
|
||||||
.replaceAll(' ', '_');
|
|
||||||
|
|
||||||
testWidgets(description, (tester) async {
|
|
||||||
var composition = await LottieComposition.fromBytes(
|
|
||||||
File('example/assets/$filePath').readAsBytesSync(),
|
|
||||||
);
|
|
||||||
|
|
||||||
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]),
|
|
||||||
addRepaintBoundary: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
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: const LottieDelegates(values: []),
|
|
||||||
addRepaintBoundary: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await tester.pump();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
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: const Offset(20, 20)),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Transform position',
|
|
||||||
ValueDelegate.transformPosition([
|
|
||||||
'Shape Layer 1',
|
|
||||||
'Rectangle',
|
|
||||||
], value: const Offset(20, 20)),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Transform position (relative)',
|
|
||||||
ValueDelegate.transformPosition([
|
|
||||||
'Shape Layer 1',
|
|
||||||
'Rectangle',
|
|
||||||
], relative: const 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: const 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: const Offset(20, 20)),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Rectangle size',
|
|
||||||
ValueDelegate.rectangleSize([
|
|
||||||
'Shape Layer 1',
|
|
||||||
'Rectangle',
|
|
||||||
'Rectangle Path 1',
|
|
||||||
], relative: const Offset(30, 40)),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Ellipse position',
|
|
||||||
ValueDelegate.position([
|
|
||||||
'Shape Layer 1',
|
|
||||||
'Ellipse',
|
|
||||||
'Ellipse Path 1',
|
|
||||||
], relative: const Offset(20, 20)),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Ellipse size',
|
|
||||||
ValueDelegate.ellipseSize([
|
|
||||||
'Shape Layer 1',
|
|
||||||
'Ellipse',
|
|
||||||
'Ellipse Path 1',
|
|
||||||
], relative: const 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: const 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: const 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: const 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: const Offset(2, 2)),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Time remapping',
|
|
||||||
ValueDelegate.timeRemap(['Circle 1'], value: 1),
|
|
||||||
progress: 0.1,
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Color Filter',
|
|
||||||
ValueDelegate.colorFilter([
|
|
||||||
'**',
|
|
||||||
], value: const ColorFilter.mode(Colors.green, BlendMode.srcATop)),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden('Null Color Filter', ValueDelegate.colorFilter(['**']));
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Matte property',
|
|
||||||
ValueDelegate.rectangleSize([
|
|
||||||
'Shape Layer 1',
|
|
||||||
'Rectangle 1',
|
|
||||||
'Rectangle Path 1',
|
|
||||||
], value: const Offset(50, 50)),
|
|
||||||
filePath: 'Tests/TrackMattes.json',
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden('Blur', ValueDelegate.blurRadius(['**'], value: 10));
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Drop shadow',
|
|
||||||
ValueDelegate.dropShadow(
|
|
||||||
['Shape Layer 1', '**'],
|
|
||||||
value: const DropShadow(
|
|
||||||
color: Colors.green,
|
|
||||||
direction: 150,
|
|
||||||
distance: 20,
|
|
||||||
radius: 10,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Solid Color',
|
|
||||||
ValueDelegate.color(['Cyan Solid 1', '**'], value: Colors.yellow),
|
|
||||||
filePath: 'Tests/SolidLayerTransform.json',
|
|
||||||
);
|
|
||||||
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
testWidgets('warningShimmer', (tester) async {
|
|
||||||
var size = const Size(500, 400);
|
|
||||||
tester.view.physicalSize = size;
|
|
||||||
tester.view.devicePixelRatio = 1.0;
|
|
||||||
|
|
||||||
var composition = await LottieComposition.fromBytes(
|
|
||||||
File('test/data/warningShimmer.json').readAsBytesSync(),
|
|
||||||
);
|
|
||||||
|
|
||||||
var delegates = <String, List<ValueDelegate>>{
|
|
||||||
'1': [
|
|
||||||
for (var i in ['1', '2', '5'])
|
|
||||||
ValueDelegate.color(['Layer $i Outlines', '**'], value: Colors.red),
|
|
||||||
for (var i in ['3', '4'])
|
|
||||||
ValueDelegate.color([
|
|
||||||
'Layer $i Outlines',
|
|
||||||
'**',
|
|
||||||
], value: Colors.greenAccent),
|
|
||||||
],
|
|
||||||
'2': [
|
|
||||||
for (var i in ['1', '2', '5'])
|
|
||||||
ValueDelegate.color([
|
|
||||||
'Layer $i Outlines',
|
|
||||||
'Group 1',
|
|
||||||
'*',
|
|
||||||
], value: Colors.red),
|
|
||||||
for (var i in ['3', '4'])
|
|
||||||
ValueDelegate.color([
|
|
||||||
'Layer $i Outlines',
|
|
||||||
'Group 1',
|
|
||||||
'*',
|
|
||||||
], value: Colors.greenAccent),
|
|
||||||
],
|
|
||||||
'3': [
|
|
||||||
for (var i in ['1', '2', '5'])
|
|
||||||
ValueDelegate.color([
|
|
||||||
'Layer $i Outlines',
|
|
||||||
'Group 1',
|
|
||||||
'Fill 1',
|
|
||||||
], value: Colors.red),
|
|
||||||
for (var i in ['3', '4'])
|
|
||||||
ValueDelegate.color([
|
|
||||||
'Layer $i Outlines',
|
|
||||||
'Group 1',
|
|
||||||
'Fill 1',
|
|
||||||
], value: Colors.greenAccent),
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (var variant in delegates.entries) {
|
|
||||||
await tester.pumpWidget(
|
|
||||||
FilmStrip(
|
|
||||||
composition,
|
|
||||||
size: size,
|
|
||||||
delegates: LottieDelegates(values: variant.value),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await expectLater(
|
|
||||||
find.byType(FilmStrip),
|
|
||||||
matchesGoldenFile('goldens/warningShimmer_${variant.key}.png'),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:ui';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:lottie/lottie.dart';
|
|
||||||
import 'utils.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
var root = 'example/assets';
|
|
||||||
|
|
||||||
testWidgets('Mirror animation', (tester) async {
|
|
||||||
var size = const Size(500, 400);
|
|
||||||
tester.view.physicalSize = size;
|
|
||||||
tester.view.devicePixelRatio = 1.0;
|
|
||||||
|
|
||||||
var composition = (await tester.runAsync(
|
|
||||||
() => FileLottie(File('$root/Tests/MatteTimeStretchScan.json')).load(),
|
|
||||||
))!;
|
|
||||||
await tester.pumpWidget(
|
|
||||||
FilmStrip(
|
|
||||||
composition,
|
|
||||||
size: size,
|
|
||||||
delegates: LottieDelegates(
|
|
||||||
values: [
|
|
||||||
ValueDelegate.transformAnchorPoint(
|
|
||||||
[],
|
|
||||||
value: Offset(composition.bounds.width.toDouble(), 0),
|
|
||||||
),
|
|
||||||
ValueDelegate.transformScale([], value: const Offset(-1, 1)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
await expectLater(
|
|
||||||
find.byType(FilmStrip),
|
|
||||||
matchesGoldenFile('goldens/mirror.png'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:lottie/lottie.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
late LottieComposition composition;
|
|
||||||
|
|
||||||
setUpAll(() async {
|
|
||||||
composition = await LottieComposition.fromBytes(
|
|
||||||
File('example/assets/Tests/Text.json').readAsBytesSync(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
void testGolden(String description, ValueDelegate delegate) async {
|
|
||||||
var screenshotName = description
|
|
||||||
.toLowerCase()
|
|
||||||
.replaceAll(RegExp('[^a-z0-9 ]'), '')
|
|
||||||
.replaceAll(' ', '_');
|
|
||||||
|
|
||||||
testWidgets(description, (tester) async {
|
|
||||||
await tester.pumpWidget(
|
|
||||||
Lottie(
|
|
||||||
composition: composition,
|
|
||||||
delegates: LottieDelegates(values: [delegate]),
|
|
||||||
addRepaintBoundary: false,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await tester.pump();
|
|
||||||
await expectLater(
|
|
||||||
find.byType(Lottie),
|
|
||||||
matchesGoldenFile('goldens/dynamic_text/$screenshotName.png'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Text Fill (Blue -> Green)',
|
|
||||||
ValueDelegate.color(['Text'], callback: (_) => Colors.green),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Text Stroke (Red -> Yellow)',
|
|
||||||
ValueDelegate.strokeColor(['Text'], callback: (_) => Colors.yellow),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Text Stroke Width',
|
|
||||||
ValueDelegate.strokeWidth(['Text'], callback: (_) => 200),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Text Tracking 1',
|
|
||||||
ValueDelegate.textTracking(['Text'], callback: (_) => 20),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Text Tracking 2',
|
|
||||||
ValueDelegate.textSize(['Text'], callback: (_) => 60),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,103 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:lottie/lottie.dart';
|
|
||||||
import 'utils.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
late LottieComposition composition;
|
|
||||||
|
|
||||||
setUpAll(() async {
|
|
||||||
composition = await LottieComposition.fromBytes(
|
|
||||||
File('example/assets/Tests/DynamicText.json').readAsBytesSync(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
void testGolden(String description, LottieDelegates delegates) async {
|
|
||||||
var screenshotName = description
|
|
||||||
.toLowerCase()
|
|
||||||
.replaceAll(RegExp('[^a-z0-9 ]'), '')
|
|
||||||
.replaceAll(' ', '_');
|
|
||||||
|
|
||||||
var size = const Size(500, 400);
|
|
||||||
testWidgets(description, (tester) async {
|
|
||||||
await tester.pumpWidget(
|
|
||||||
MaterialApp(
|
|
||||||
home: FilmStrip(composition, delegates: delegates, size: size),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
await tester.pump();
|
|
||||||
await expectLater(
|
|
||||||
find.byType(FilmStrip),
|
|
||||||
matchesGoldenFile('goldens/dynamic_text/$screenshotName.png'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Dynamic text delegate',
|
|
||||||
LottieDelegates(
|
|
||||||
text: (input) => '🔥c️🔥👮🏿🔥',
|
|
||||||
textStyle: (font) => const TextStyle(
|
|
||||||
fontFamily: 'Roboto',
|
|
||||||
fontFamilyFallback: ['Noto Emoji'],
|
|
||||||
),
|
|
||||||
values: const [],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Dynamic Text ValueDelegate',
|
|
||||||
LottieDelegates(
|
|
||||||
values: [
|
|
||||||
ValueDelegate.text(['NAME'], value: 'Text with ValueDelegate'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Dynamic Text ValueDelegate overallProgress',
|
|
||||||
LottieDelegates(
|
|
||||||
values: [
|
|
||||||
ValueDelegate.text([
|
|
||||||
'NAME',
|
|
||||||
], callback: (frame) => '${frame.overallProgress}'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Dynamic Text ValueDelegate startValue',
|
|
||||||
LottieDelegates(
|
|
||||||
values: [
|
|
||||||
ValueDelegate.text([
|
|
||||||
'NAME',
|
|
||||||
], callback: (frame) => '${frame.startValue}!!'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Dynamic Text ValueDelegate endValue',
|
|
||||||
LottieDelegates(
|
|
||||||
values: [
|
|
||||||
ValueDelegate.text([
|
|
||||||
'NAME',
|
|
||||||
], callback: (frame) => '${frame.endValue}!!'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
testGolden(
|
|
||||||
'Dynamic Text Emoji',
|
|
||||||
LottieDelegates(
|
|
||||||
textStyle: (font) => const TextStyle(
|
|
||||||
fontFamily: 'Roboto',
|
|
||||||
fontFamilyFallback: ['Noto Emoji'],
|
|
||||||
),
|
|
||||||
values: [
|
|
||||||
ValueDelegate.text(['NAME'], value: '🔥💪💯'),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:ui';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:lottie/lottie.dart';
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
import 'utils.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('Animations with stroke', (tester) async {
|
|
||||||
var size = const Size(500, 400);
|
|
||||||
tester.view.physicalSize = size;
|
|
||||||
tester.view.devicePixelRatio = 1.0;
|
|
||||||
|
|
||||||
var composition = await LottieComposition.fromBytes(
|
|
||||||
File('example/assets/17297-fireworks.json').readAsBytesSync(),
|
|
||||||
);
|
|
||||||
|
|
||||||
await tester.pumpWidget(FilmStrip(composition, size: size));
|
|
||||||
|
|
||||||
await expectLater(
|
|
||||||
find.byType(FilmStrip),
|
|
||||||
matchesGoldenFile(p.join('goldens/fireworks.png')),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:io';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:path/path.dart' as path;
|
|
||||||
|
|
||||||
Future<void> testExecutable(FutureOr<void> Function() testMain) async {
|
|
||||||
await loadFonts();
|
|
||||||
return testMain();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> loadFonts() async {
|
|
||||||
for (var file in Directory(
|
|
||||||
'example/assets/fonts',
|
|
||||||
).listSync().whereType<File>().where((f) => f.path.endsWith('.ttf'))) {
|
|
||||||
var fontLoader = FontLoader(
|
|
||||||
path.basenameWithoutExtension(file.path).replaceAll('-', ' '),
|
|
||||||
);
|
|
||||||
var future = file.readAsBytes().then((value) => value.buffer.asByteData());
|
|
||||||
fontLoader.addFont(future);
|
|
||||||
await fontLoader.load();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
import 'dart:ui';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:lottie/src/utils/gamma_evaluator.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
test('Evaluate for same color values', () {
|
|
||||||
for (var color = 0x000000; color <= 0xffffff; color++) {
|
|
||||||
var colorToTest = Color(0xff000000 | color);
|
|
||||||
var actual = GammaEvaluator.evaluate(0.3, colorToTest, colorToTest);
|
|
||||||
expect(actual, colorToTest);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 153 KiB |
|
|
@ -1,37 +0,0 @@
|
||||||
import 'dart:io';
|
|
||||||
import 'dart:ui';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:lottie/lottie.dart';
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
import 'utils.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
var root = 'example/assets';
|
|
||||||
for (var asset
|
|
||||||
in Directory(root)
|
|
||||||
.listSync(recursive: true)
|
|
||||||
.whereType<File>()
|
|
||||||
.where(
|
|
||||||
(f) => const ['.json', '.zip'].contains(p.extension(f.path)),
|
|
||||||
)) {
|
|
||||||
testWidgets('Goldens ${asset.path}', (tester) async {
|
|
||||||
var size = const Size(500, 400);
|
|
||||||
tester.view.physicalSize = size;
|
|
||||||
tester.view.devicePixelRatio = 1.0;
|
|
||||||
var composition = (await tester.runAsync(
|
|
||||||
() => FileLottie(asset).load(),
|
|
||||||
))!;
|
|
||||||
|
|
||||||
await tester.pumpWidget(FilmStrip(composition, size: size));
|
|
||||||
|
|
||||||
var folder = p.relative(asset.path, from: root);
|
|
||||||
var fileName = '${p.basenameWithoutExtension(asset.path)}.png'
|
|
||||||
.toLowerCase();
|
|
||||||
await expectLater(
|
|
||||||
find.byType(FilmStrip),
|
|
||||||
matchesGoldenFile(p.join('goldens/all', p.dirname(folder), fileName)),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 5.7 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 6.4 KiB |
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 22 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 87 KiB |
|
Before Width: | Height: | Size: 101 KiB |