Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions packages/vector_graphics/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,22 @@ class MyApp extends StatelessWidget {
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Scaffold(
home: Scaffold(
body: Center(
child: VectorGraphic(
loader: NetworkSvgLoader(
placeholderBuilder: (BuildContext context) => Container(
color: Colors.green,
width: double.infinity,
height: double.infinity,
child: const Center(
child: SizedBox(
width: 25,
height: 25,
child: CircularProgressIndicator(),
),
)),
transitionConfig: const VectorGraphicsStateTransitionConfig(),
loader: const NetworkSvgLoader(
'https://upload.wikimedia.org/wikipedia/commons/f/fd/Ghostscript_Tiger.svg',
),
),
Expand Down
48 changes: 48 additions & 0 deletions packages/vector_graphics/lib/src/state_transition_config.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'package:flutter/material.dart';

/// The configuration of transition animation between loading/loaded states
///
/// Containing all parameters supported by the [AnimatedSwitcher] widget which is
/// used for the animation.
class VectorGraphicsStateTransitionConfig {

/// Creates a [VectorGraphicsStateTransitionConfig] with the given parameters.
///
/// All parameters are optional and have default values to simplify usage.
const VectorGraphicsStateTransitionConfig({
this.duration = const Duration(milliseconds: 400),
this.reverseDuration,
this.switchInCurve = Curves.linear,
this.switchOutCurve = Curves.linear,
this.transitionBuilder = AnimatedSwitcher.defaultTransitionBuilder,
this.layoutBuilder = AnimatedSwitcher.defaultLayoutBuilder,
});

/// The duration of the transition from one state to the next.
///
/// Defaults to 400 milliseconds.
final Duration duration;

/// The duration of the reverse transition from the next state to the previous one.
final Duration? reverseDuration;

/// The curve to apply when transitioning in.
///
/// Defaults to [Curves.linear].
final Curve switchInCurve;

/// The curve to apply when transitioning out.
///
/// Defaults to [Curves.linear].
final Curve switchOutCurve;

/// The builder for constructing the transition animations.
///
/// Defaults to [AnimatedSwitcher.defaultTransitionBuilder].
final AnimatedSwitcherTransitionBuilder transitionBuilder;

/// The builder for laying out the child widgets during the transition.
///
/// Defaults to [AnimatedSwitcher.defaultLayoutBuilder].
final AnimatedSwitcherLayoutBuilder layoutBuilder;
}
196 changes: 118 additions & 78 deletions packages/vector_graphics/lib/src/vector_graphics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:vector_graphics/src/state_transition_config.dart';

import 'package:vector_graphics_codec/vector_graphics_codec.dart';

Expand All @@ -18,6 +19,7 @@ import 'render_vector_graphic.dart';

export 'listener.dart' show PictureInfo;
export 'loader.dart';
export 'state_transition_config.dart';

/// How the vector graphic will be rendered by the Flutter framework.
///
Expand Down Expand Up @@ -120,6 +122,7 @@ class VectorGraphic extends StatefulWidget {
this.clipBehavior = Clip.hardEdge,
this.placeholderBuilder,
this.errorBuilder,
this.transitionConfig,
this.colorFilter,
this.opacity,
this.clipViewbox = true,
Expand All @@ -138,6 +141,7 @@ class VectorGraphic extends StatefulWidget {
this.excludeFromSemantics = false,
this.clipBehavior = Clip.hardEdge,
this.placeholderBuilder,
this.transitionConfig,
this.errorBuilder,
this.colorFilter,
this.opacity,
Expand Down Expand Up @@ -218,6 +222,11 @@ class VectorGraphic extends StatefulWidget {
/// A callback that fires if some exception happens during data acquisition or decoding.
final VectorGraphicsErrorWidget? errorBuilder;

/// If provided, will be used to animate the transition between the various states of svg loading
///
/// For example used to transition from the placeholder to error/success states.
final VectorGraphicsStateTransitionConfig? transitionConfig;

/// If provided, a color filter to apply to the vector graphic when painting.
///
/// For example, `ColorFilter.mode(Colors.red, BlendMode.srcIn)` to give the vector
Expand Down Expand Up @@ -424,93 +433,40 @@ class _VectorGraphicWidgetState extends State<VectorGraphic> {
Widget build(BuildContext context) {
final PictureInfo? pictureInfo = _pictureInfo?.pictureInfo;

final Object? localError = _error;
final VectorGraphicsErrorWidget? localErrorBuilder = widget.errorBuilder;
final VectorGraphicsStateTransitionConfig? localTransitionConfig = widget.transitionConfig;

Widget child;
if (pictureInfo != null) {
// If the caller did not specify a width or height, fall back to the
// size of the graphic.
// If the caller did specify a width or height, preserve the aspect ratio
// of the graphic and center it within that width and height.
double? width = widget.width;
double? height = widget.height;

if (width == null && height == null) {
width = pictureInfo.size.width;
height = pictureInfo.size.height;
} else if (height != null && !pictureInfo.size.isEmpty) {
width = height / pictureInfo.size.height * pictureInfo.size.width;
} else if (width != null && !pictureInfo.size.isEmpty) {
height = width / pictureInfo.size.width * pictureInfo.size.height;
}

assert(width != null && height != null);

double scale = 1.0;
scale = math.min(
pictureInfo.size.width / width!,
pictureInfo.size.height / height!,
child = _buildSvgChild(pictureInfo, context);
} else if (localError != null && localErrorBuilder != null) {
child = _buildErrorChild(context, localError, localErrorBuilder);
} else {
child = Container(
key: const ValueKey<String>('svg-placeholder-state'),
child: widget.placeholderBuilder?.call(context) ??
SizedBox(
width: widget.width,
height: widget.height,
),
);
}

if (_webRenderObject) {
child = _RawWebVectorGraphicWidget(
pictureInfo: pictureInfo,
assetKey: _pictureInfo!.key,
colorFilter: widget.colorFilter,
opacity: widget.opacity,
);
} else if (widget.strategy == RenderingStrategy.raster) {
child = _RawVectorGraphicWidget(
pictureInfo: pictureInfo,
assetKey: _pictureInfo!.key,
colorFilter: widget.colorFilter,
opacity: widget.opacity,
scale: scale,
);
} else {
child = _RawPictureVectorGraphicWidget(
pictureInfo: pictureInfo,
assetKey: _pictureInfo!.key,
colorFilter: widget.colorFilter,
opacity: widget.opacity,
);
}

if (widget.matchTextDirection) {
final TextDirection direction = Directionality.of(context);
if (direction == TextDirection.rtl) {
child = Transform(
transform: Matrix4.identity()
..translate(pictureInfo.size.width)
..scale(-1.0, 1.0),
child: child,
);
}
}

child = SizedBox(
width: width,
height: height,
child: FittedBox(
fit: widget.fit,
alignment: widget.alignment,
clipBehavior: widget.clipBehavior,
child: SizedBox.fromSize(
size: pictureInfo.size,
child: child,
),
),
);
} else if (_error != null && widget.errorBuilder != null) {
child = widget.errorBuilder!(
context,
_error!,
_stackTrace ?? StackTrace.empty,
if (localTransitionConfig != null) {
child = AnimatedSwitcher(
duration: localTransitionConfig.duration,
reverseDuration: localTransitionConfig.reverseDuration,
switchInCurve: localTransitionConfig.switchInCurve,
switchOutCurve: localTransitionConfig.switchOutCurve,
transitionBuilder: localTransitionConfig.transitionBuilder,
layoutBuilder: localTransitionConfig.layoutBuilder,
child: child,
);
} else {
child = widget.placeholderBuilder?.call(context) ??
SizedBox(
width: widget.width,
height: widget.height,
);
}

if (!widget.excludeFromSemantics) {
Expand All @@ -523,6 +479,90 @@ class _VectorGraphicWidgetState extends State<VectorGraphic> {
}
return child;
}

Widget _buildErrorChild(BuildContext context, Object error,
VectorGraphicsErrorWidget errorBuilder) =>
Container(
key: const ValueKey<String>('svg-error-state'),
child: errorBuilder(
context,
error,
_stackTrace ?? StackTrace.empty,
),
);

Widget _buildSvgChild(PictureInfo pictureInfo, BuildContext context) {
double? width = widget.width;
double? height = widget.height;
if (width == null && height == null) {
width = pictureInfo.size.width;
height = pictureInfo.size.height;
} else if (height != null && !pictureInfo.size.isEmpty) {
width = height / pictureInfo.size.height * pictureInfo.size.width;
} else if (width != null && !pictureInfo.size.isEmpty) {
height = width / pictureInfo.size.width * pictureInfo.size.height;
}

assert(width != null && height != null);

double scale = 1.0;
scale = math.min(
pictureInfo.size.width / width!,
pictureInfo.size.height / height!,
);

Widget child;
if (_webRenderObject) {
child = _RawWebVectorGraphicWidget(
pictureInfo: pictureInfo,
assetKey: _pictureInfo!.key,
colorFilter: widget.colorFilter,
opacity: widget.opacity,
);
} else if (widget.strategy == RenderingStrategy.raster) {
child = _RawVectorGraphicWidget(
pictureInfo: pictureInfo,
assetKey: _pictureInfo!.key,
colorFilter: widget.colorFilter,
opacity: widget.opacity,
scale: scale,
);
} else {
child = _RawPictureVectorGraphicWidget(
pictureInfo: pictureInfo,
assetKey: _pictureInfo!.key,
colorFilter: widget.colorFilter,
opacity: widget.opacity,
);
}

if (widget.matchTextDirection) {
final TextDirection direction = Directionality.of(context);
if (direction == TextDirection.rtl) {
child = Transform(
transform: Matrix4.identity()
..translate(pictureInfo.size.width)
..scale(-1.0, 1.0),
child: child,
);
}
}

return SizedBox(
key: const ValueKey<String>('svg-loaded-state'),
width: width,
height: height,
child: FittedBox(
fit: widget.fit,
alignment: widget.alignment,
clipBehavior: widget.clipBehavior,
child: SizedBox.fromSize(
size: pictureInfo.size,
child: child,
),
),
);
}
}

class _RawVectorGraphicWidget extends SingleChildRenderObjectWidget {
Expand Down
1 change: 1 addition & 0 deletions packages/vector_graphics/lib/vector_graphics.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export 'src/vector_graphics.dart'
BytesLoader,
VectorGraphic,
VectorGraphicUtilities,
VectorGraphicsStateTransitionConfig,
vg;
1 change: 1 addition & 0 deletions packages/vector_graphics/lib/vector_graphics_compat.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export 'src/vector_graphics.dart'
BytesLoader,
VectorGraphic,
VectorGraphicUtilities,
VectorGraphicsStateTransitionConfig,
vg,
RenderingStrategy,
createCompatVectorGraphic;