From 80857cbe43a9edc39b0f3efcdb8c01ad8ec5b051 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 1 Mar 2022 11:42:34 -0800 Subject: [PATCH 1/6] add basic vector graphics asset --- packages/vector_graphics/CHANGELOG.md | 3 + .../vector_graphics/lib/src/listener.dart | 131 ++++++++++++++++++ .../vector_graphics/lib/vector_graphics.dart | 99 ++++++++++++- packages/vector_graphics/pubspec.yaml | 8 +- .../test/vector_graphics_test.dart | 58 +++++++- 5 files changed, 287 insertions(+), 12 deletions(-) create mode 100644 packages/vector_graphics/lib/src/listener.dart diff --git a/packages/vector_graphics/CHANGELOG.md b/packages/vector_graphics/CHANGELOG.md index 958ea8a7b4d3..ec6a6ba50346 100644 --- a/packages/vector_graphics/CHANGELOG.md +++ b/packages/vector_graphics/CHANGELOG.md @@ -1,5 +1,8 @@ # CHANGELOG +## 0.0.1 + * Added `VectorGraphicsWidget` which consumes encoded vector graphics assets. + ## 0.0.0 * Create repository diff --git a/packages/vector_graphics/lib/src/listener.dart b/packages/vector_graphics/lib/src/listener.dart new file mode 100644 index 000000000000..5075ef7eba71 --- /dev/null +++ b/packages/vector_graphics/lib/src/listener.dart @@ -0,0 +1,131 @@ +import 'dart:ui' as ui; +import 'dart:typed_data'; + +import 'package:vector_graphics_codec/vector_graphics_codec.dart'; + +/// A listener implementation for the vector graphics codec that converts the +/// format into a [ui.Picture]. +class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener { + + /// Create a new [FlutterVectorGraphicsListener]. + factory FlutterVectorGraphicsListener() { + final ui.PictureRecorder recorder = ui.PictureRecorder(); + final ui.Canvas canvas = ui.Canvas(recorder); + return FlutterVectorGraphicsListener._(canvas, recorder); + } + + FlutterVectorGraphicsListener._(this._canvas, this._recorder); + + final ui.PictureRecorder _recorder; + final ui.Canvas _canvas; + final List _paints = []; + final List _paths = []; + ui.Path? _currentPath; + bool _done = false; + + static final _emptyPaint = ui.Paint(); + + /// Convert the vector graphics asset this listener decoded into a [ui.Picture]. + /// + /// This method can only be called once for a given listener instance. + ui.Picture toPicture() { + assert(!_done); + _done = true; + return _recorder.endRecording(); + } + + @override + void onDrawPath(int pathId, int? paintId) { + final ui.Path path = _paths[pathId]; + ui.Paint? paint; + if (paintId != null) { + paint = _paints[paintId]; + } + _canvas.drawPath(path, paint ?? _emptyPaint); + } + + @override + void onDrawVertices(Float32List vertices, Uint16List? indices, int? paintId) { + final ui.Vertices vextexData = + ui.Vertices.raw(ui.VertexMode.triangles, vertices, indices: indices); + ui.Paint? paint; + if (paintId != null) { + paint = _paints[paintId]; + } + _canvas.drawVertices( + vextexData, ui.BlendMode.srcOver, paint ?? _emptyPaint); + } + + @override + void onPaintObject({ + required int color, + required int? strokeCap, + required int? strokeJoin, + required int blendMode, + required double? strokeMiterLimit, + required double? strokeWidth, + required int paintStyle, + required int id, + }) { + assert(_paints.length == id, 'Expect ID to be ${_paints.length}'); + final ui.Paint paint = ui.Paint(); + if (paintStyle == 0) { + // fill + paint + ..color = ui.Color(color) + ..blendMode = ui.BlendMode.values[blendMode] + ..style = ui.PaintingStyle.fill; + } else { + paint + ..color = ui.Color(color) + ..blendMode = ui.BlendMode.values[blendMode] + ..style = ui.PaintingStyle.stroke + ..strokeCap = ui.StrokeCap.values[strokeCap ?? 0] + ..strokeJoin = ui.StrokeJoin.values[strokeJoin ?? 0]; + if (strokeMiterLimit != null) { + paint.strokeMiterLimit = strokeMiterLimit; + } + if (strokeWidth != null) { + paint.strokeWidth = strokeWidth; + } + } + _paints.add(paint); + } + + @override + void onPathClose() { + _currentPath!.close(); + } + + @override + void onPathCubicTo( + double x1, double y1, double x2, double y2, double x3, double y3) { + _currentPath!.cubicTo(x1, y1, x2, y2, x3, y3); + } + + @override + void onPathFinished() { + _currentPath = null; + } + + @override + void onPathLineTo(double x, double y) { + _currentPath!.lineTo(x, y); + } + + @override + void onPathMoveTo(double x, double y) { + _currentPath!.moveTo(x, y); + } + + @override + void onPathStart(int id, int fillType) { + assert(_currentPath == null); + assert(_paths.length == id, 'Expected Id to be $id'); + + final ui.Path path = ui.Path(); + path.fillType = ui.PathFillType.values[fillType]; + _paths.add(path); + _currentPath = path; + } +} diff --git a/packages/vector_graphics/lib/vector_graphics.dart b/packages/vector_graphics/lib/vector_graphics.dart index affce5668426..40323bb2a04f 100644 --- a/packages/vector_graphics/lib/vector_graphics.dart +++ b/packages/vector_graphics/lib/vector_graphics.dart @@ -1,7 +1,96 @@ -library vector_graphics; +import 'dart:typed_data'; +import 'dart:ui' as ui; +import 'package:flutter/rendering.dart'; +import 'package:flutter/widgets.dart'; +import 'package:vector_graphics_codec/vector_graphics_codec.dart'; -/// A Calculator. -class Calculator { - /// Returns [value] plus 1. - int addOne(int value) => value + 1; +import 'src/listener.dart'; + +const VectorGraphicsCodec _codec = VectorGraphicsCodec(); + +/// A widget that displays a vector_graphics formatted asset. +class VectorGraphicsWidget extends StatefulWidget { + const VectorGraphicsWidget.asset({ Key? key, required this.assetName }) : super(key: key); + + /// The name of the asset to be displayed. + final String assetName; + + @override + State createState() => _VectorGraphicsWidgetState(); +} + +class _VectorGraphicsWidgetState extends State { + ui.Picture? _picture; + + @override + void initState() { + _loadAssetBytes(); + super.initState(); + } + + @override + void didUpdateWidget(covariant VectorGraphicsWidget oldWidget) { + if (oldWidget.assetName != widget.assetName) { + _loadAssetBytes(); + } + super.didUpdateWidget(oldWidget); + } + + @override + void dispose() { + _picture?.dispose(); + super.dispose(); + } + + void _loadAssetBytes() { + final AssetBundle bundle = DefaultAssetBundle.of(context); + _picture?.dispose(); + bundle.load(widget.assetName).then((ByteData data) { + final FlutterVectorGraphicsListener listener = FlutterVectorGraphicsListener(); + _codec.decode(data, listener); + _picture = listener.toPicture(); + }, onError: (dynamic error, StackTrace stackTrace) { + _picture?.dispose(); + _picture = null; + }); + } + + @override + Widget build(BuildContext context) { + final ui.Picture? picture = _picture; + if (picture == null) { + return const SizedBox(); + } + return _RawVectorGraphicsWidget(picture: picture); + } +} + +class _RawVectorGraphicsWidget extends SingleChildRenderObjectWidget { + const _RawVectorGraphicsWidget({Key? key, required this.picture}) : super(key: key); + + final ui.Picture picture; + + @override + RenderObject createRenderObject(BuildContext context) { + return _RenderVectorGraphics(picture); + } + + @override + void updateRenderObject(BuildContext context, covariant _RenderVectorGraphics renderObject) { + renderObject.picture = picture; + } +} + +class _RenderVectorGraphics extends RenderProxyBox { + _RenderVectorGraphics(this._picture); + + ui.Picture get picture => _picture; + ui.Picture _picture; + set picture(ui.Picture value) { + if (identical(value, _picture)) { + return; + } + _picture = value; + markNeedsPaint(); + } } diff --git a/packages/vector_graphics/pubspec.yaml b/packages/vector_graphics/pubspec.yaml index b10cce442cdf..82150d555245 100644 --- a/packages/vector_graphics/pubspec.yaml +++ b/packages/vector_graphics/pubspec.yaml @@ -1,6 +1,6 @@ name: vector_graphics description: A vector graphics rendering package for Flutter. -version: 0.0.0 +version: 0.0.1 homepage: https://github.com/dnfield/vector_graphics environment: @@ -10,8 +10,14 @@ environment: dependencies: flutter: sdk: flutter + vector_graphics_codec: ^0.0.1 dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^1.0.0 + +# Comment out before publishing +dependency_overrides: + vector_graphics_codec: + path: ../vector_graphics_codec \ No newline at end of file diff --git a/packages/vector_graphics/test/vector_graphics_test.dart b/packages/vector_graphics/test/vector_graphics_test.dart index 879fa64130c0..7a14d00c20db 100644 --- a/packages/vector_graphics/test/vector_graphics_test.dart +++ b/packages/vector_graphics/test/vector_graphics_test.dart @@ -1,12 +1,58 @@ +import 'dart:typed_data'; + import 'package:flutter_test/flutter_test.dart'; +import 'package:vector_graphics/src/listener.dart'; + +import 'package:vector_graphics_codec/vector_graphics_codec.dart'; -import 'package:vector_graphics/vector_graphics.dart'; +const VectorGraphicsCodec codec = VectorGraphicsCodec(); void main() { - test('adds one to input values', () { - final calculator = Calculator(); - expect(calculator.addOne(2), 3); - expect(calculator.addOne(-7), -6); - expect(calculator.addOne(0), 1); + test('Can decode a message without a stroke and vertices', () { + final buffer = VectorGraphicsBuffer(); + final FlutterVectorGraphicsListener listener = + FlutterVectorGraphicsListener(); + final int paintId = codec.writeStroke(buffer, 44, 1, 2, 3, 4.0, 6.0); + codec.writeDrawVertices( + buffer, + Float32List.fromList([ + 0.0, + 2.0, + 3.0, + 4.0, + 2.0, + 4.0, + ]), + null, + paintId); + + codec.decode(buffer.done(), listener); + + expect(listener.toPicture, returnsNormally); + }); + + test('Can decode a message with a fill and path', () { + final buffer = VectorGraphicsBuffer(); + final FlutterVectorGraphicsListener listener = + FlutterVectorGraphicsListener(); + final int paintId = codec.writeFill(buffer, 23, 0); + final int pathId = codec.writeStartPath(buffer, 0); + codec.writeMoveTo(buffer, 1, 2); + codec.writeLineTo(buffer, 2, 3); + codec.writeClose(buffer); + codec.writeFinishPath(buffer); + codec.writeDrawPath(buffer, pathId, paintId); + + codec.decode(buffer.done(), listener); + + expect(listener.toPicture, returnsNormally); + }); + + test('Asserts if toPicture is called more than once', () { + final FlutterVectorGraphicsListener listener = + FlutterVectorGraphicsListener(); + listener.toPicture(); + + expect(listener.toPicture, throwsAssertionError); }); } From 87659e027288ed1ebebaf248eb7fd28159f0768b Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 1 Mar 2022 12:57:09 -0800 Subject: [PATCH 2/6] example of controller --- .../vector_graphics/lib/vector_graphics.dart | 77 ++++++++++++++----- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/packages/vector_graphics/lib/vector_graphics.dart b/packages/vector_graphics/lib/vector_graphics.dart index 40323bb2a04f..b0eca6c8324a 100644 --- a/packages/vector_graphics/lib/vector_graphics.dart +++ b/packages/vector_graphics/lib/vector_graphics.dart @@ -9,17 +9,16 @@ import 'src/listener.dart'; const VectorGraphicsCodec _codec = VectorGraphicsCodec(); /// A widget that displays a vector_graphics formatted asset. -class VectorGraphicsWidget extends StatefulWidget { - const VectorGraphicsWidget.asset({ Key? key, required this.assetName }) : super(key: key); +class VectorGraphics extends StatefulWidget { + const VectorGraphics({Key? key, required this.controller}) : super(key: key); - /// The name of the asset to be displayed. - final String assetName; + final VectorGraphicsController controller; @override - State createState() => _VectorGraphicsWidgetState(); + State createState() => _VectorGraphicsWidgetState(); } -class _VectorGraphicsWidgetState extends State { +class _VectorGraphicsWidgetState extends State { ui.Picture? _picture; @override @@ -29,8 +28,8 @@ class _VectorGraphicsWidgetState extends State { } @override - void didUpdateWidget(covariant VectorGraphicsWidget oldWidget) { - if (oldWidget.assetName != widget.assetName) { + void didUpdateWidget(covariant VectorGraphics oldWidget) { + if (!oldWidget.controller.equivalent(widget.controller)) { _loadAssetBytes(); } super.didUpdateWidget(oldWidget); @@ -39,20 +38,18 @@ class _VectorGraphicsWidgetState extends State { @override void dispose() { _picture?.dispose(); + _picture = null; super.dispose(); } void _loadAssetBytes() { - final AssetBundle bundle = DefaultAssetBundle.of(context); - _picture?.dispose(); - bundle.load(widget.assetName).then((ByteData data) { - final FlutterVectorGraphicsListener listener = FlutterVectorGraphicsListener(); - _codec.decode(data, listener); - _picture = listener.toPicture(); - }, onError: (dynamic error, StackTrace stackTrace) { - _picture?.dispose(); - _picture = null; - }); + widget.controller.load() + .then((ui.Picture picture) { + setState(() { + _picture?.dispose(); + _picture = picture; + }); + }); } @override @@ -65,8 +62,47 @@ class _VectorGraphicsWidgetState extends State { } } +abstract class VectorGraphicsController { + const VectorGraphicsController(); + + Future load(); + + bool equivalent(VectorGraphicsController other) => false; +} + +class VectorGraphicsAssetController extends VectorGraphicsController { + const VectorGraphicsAssetController({ + required this.assetName, + this.packageName, + required this.assetBundle, + }); + + final String assetName; + final String? packageName; + final AssetBundle assetBundle; + + @override + bool equivalent(VectorGraphicsController other) { + return other is VectorGraphicsAssetController && + other.assetName == assetName && + other.packageName == packageName && + other.assetBundle == assetBundle; + } + + @override + Future load() { + return assetBundle.load(assetName).then((ByteData data) { + final FlutterVectorGraphicsListener listener = + FlutterVectorGraphicsListener(); + _codec.decode(data, listener); + return listener.toPicture(); + }); + } +} + class _RawVectorGraphicsWidget extends SingleChildRenderObjectWidget { - const _RawVectorGraphicsWidget({Key? key, required this.picture}) : super(key: key); + const _RawVectorGraphicsWidget({Key? key, required this.picture}) + : super(key: key); final ui.Picture picture; @@ -76,7 +112,8 @@ class _RawVectorGraphicsWidget extends SingleChildRenderObjectWidget { } @override - void updateRenderObject(BuildContext context, covariant _RenderVectorGraphics renderObject) { + void updateRenderObject( + BuildContext context, covariant _RenderVectorGraphics renderObject) { renderObject.picture = picture; } } From f179ecb0be37e79fef7e89dbca68be454b664b30 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 1 Mar 2022 13:23:14 -0800 Subject: [PATCH 3/6] add more examples --- .../vector_graphics/lib/vector_graphics.dart | 91 +++++++++++++++---- 1 file changed, 74 insertions(+), 17 deletions(-) diff --git a/packages/vector_graphics/lib/vector_graphics.dart b/packages/vector_graphics/lib/vector_graphics.dart index b0eca6c8324a..0936f8918b6c 100644 --- a/packages/vector_graphics/lib/vector_graphics.dart +++ b/packages/vector_graphics/lib/vector_graphics.dart @@ -1,13 +1,27 @@ import 'dart:typed_data'; import 'dart:ui' as ui; +import 'dart:io'; + +import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; + import 'package:vector_graphics_codec/vector_graphics_codec.dart'; import 'src/listener.dart'; const VectorGraphicsCodec _codec = VectorGraphicsCodec(); +/// Decode a vector graphics binary asset into a [ui.Picture]. +/// +/// Throws a [StateError] if the data is invalid. +ui.Picture decodeVectorGraphics(ByteData data) { + final FlutterVectorGraphicsListener listener = + FlutterVectorGraphicsListener(); + _codec.decode(data, listener); + return listener.toPicture(); +} + /// A widget that displays a vector_graphics formatted asset. class VectorGraphics extends StatefulWidget { const VectorGraphics({Key? key, required this.controller}) : super(key: key); @@ -43,13 +57,13 @@ class _VectorGraphicsWidgetState extends State { } void _loadAssetBytes() { - widget.controller.load() - .then((ui.Picture picture) { - setState(() { - _picture?.dispose(); - _picture = picture; - }); + widget.controller.loadBytes().then((ByteData data) { + final ui.Picture picture = decodeVectorGraphics(data); + setState(() { + _picture?.dispose(); + _picture = picture; }); + }); } @override @@ -65,12 +79,16 @@ class _VectorGraphicsWidgetState extends State { abstract class VectorGraphicsController { const VectorGraphicsController(); - Future load(); + Future loadBytes(); bool equivalent(VectorGraphicsController other) => false; } +/// A controller for loading vector graphics data from an asset bundle. class VectorGraphicsAssetController extends VectorGraphicsController { + /// Create a new [VectorGraphicsAssetController]. + /// + /// The default asset bundle can be acquired using [DefaultAssetBundle.of]. const VectorGraphicsAssetController({ required this.assetName, this.packageName, @@ -84,19 +102,52 @@ class VectorGraphicsAssetController extends VectorGraphicsController { @override bool equivalent(VectorGraphicsController other) { return other is VectorGraphicsAssetController && - other.assetName == assetName && - other.packageName == packageName && - other.assetBundle == assetBundle; + other.assetName == assetName && + other.packageName == packageName && + other.assetBundle == assetBundle; } @override - Future load() { - return assetBundle.load(assetName).then((ByteData data) { - final FlutterVectorGraphicsListener listener = - FlutterVectorGraphicsListener(); - _codec.decode(data, listener); - return listener.toPicture(); - }); + Future loadBytes() { + return assetBundle.load(assetName); + } +} + +/// A controller for loading vector graphics data from over the network. +class VectorGraphicsNetworkController extends VectorGraphicsController { + const VectorGraphicsNetworkController({ + required this.url, + this.headers, + this.client, + }); + + final Map? headers; + final Uri url; + final HttpClient? client; + + @override + bool equivalent(VectorGraphicsController other) { + // This intentionally does not use [client]. + return other is VectorGraphicsNetworkController && + other.url == url && + mapEquals(other.headers, headers); + } + + @override + Future loadBytes() async { + final HttpClient currentClient = client ?? HttpClient(); + final HttpClientRequest request = await currentClient.getUrl(url); + headers?.forEach(request.headers.add); + + final HttpClientResponse response = await request.close(); + if (response.statusCode != HttpStatus.ok) { + await response.drain>([]); + throw Exception('Failed to load VectorGraphic: ${response.statusCode}'); + } + final Uint8List bytes = await consolidateHttpClientResponseBytes( + response, + ); + return bytes.buffer.asByteData(); } } @@ -130,4 +181,10 @@ class _RenderVectorGraphics extends RenderProxyBox { _picture = value; markNeedsPaint(); } + + @override + void paint(PaintingContext context, ui.Offset offset) { + context.canvas.translate(offset.dx, offset.dy); + context.canvas.drawPicture(picture); + } } From f2ea3d0247163488886454f0ced68290e566d626 Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 1 Mar 2022 13:41:59 -0800 Subject: [PATCH 4/6] update paint construction --- .../vector_graphics/lib/src/listener.dart | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/vector_graphics/lib/src/listener.dart b/packages/vector_graphics/lib/src/listener.dart index 5075ef7eba71..427f77d43b54 100644 --- a/packages/vector_graphics/lib/src/listener.dart +++ b/packages/vector_graphics/lib/src/listener.dart @@ -6,7 +6,6 @@ import 'package:vector_graphics_codec/vector_graphics_codec.dart'; /// A listener implementation for the vector graphics codec that converts the /// format into a [ui.Picture]. class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener { - /// Create a new [FlutterVectorGraphicsListener]. factory FlutterVectorGraphicsListener() { final ui.PictureRecorder recorder = ui.PictureRecorder(); @@ -68,24 +67,24 @@ class FlutterVectorGraphicsListener extends VectorGraphicsCodecListener { required int id, }) { assert(_paints.length == id, 'Expect ID to be ${_paints.length}'); - final ui.Paint paint = ui.Paint(); - if (paintStyle == 0) { - // fill - paint - ..color = ui.Color(color) - ..blendMode = ui.BlendMode.values[blendMode] - ..style = ui.PaintingStyle.fill; - } else { - paint - ..color = ui.Color(color) - ..blendMode = ui.BlendMode.values[blendMode] - ..style = ui.PaintingStyle.stroke - ..strokeCap = ui.StrokeCap.values[strokeCap ?? 0] - ..strokeJoin = ui.StrokeJoin.values[strokeJoin ?? 0]; - if (strokeMiterLimit != null) { + final ui.Paint paint = ui.Paint()..color = ui.Color(color); + + if (blendMode != 0) { + paint.blendMode = ui.BlendMode.values[blendMode]; + } + + if (paintStyle == 1) { + paint.style = ui.PaintingStyle.stroke; + if (strokeCap != null && strokeCap != 0) { + paint.strokeCap = ui.StrokeCap.values[strokeCap]; + } + if (strokeJoin != null && strokeJoin != 0) { + paint.strokeJoin = ui.StrokeJoin.values[strokeJoin]; + } + if (strokeMiterLimit != null && strokeMiterLimit != 4.0) { paint.strokeMiterLimit = strokeMiterLimit; } - if (strokeWidth != null) { + if (strokeWidth != null && strokeWidth != 1.0) { paint.strokeWidth = strokeWidth; } } From 6b0ae22f5f5c9c2da7a9bec4d357baf3362142ae Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 1 Mar 2022 14:24:41 -0800 Subject: [PATCH 5/6] Remove equality and add more doc comments --- .../vector_graphics/lib/vector_graphics.dart | 87 +++++++++++-------- 1 file changed, 53 insertions(+), 34 deletions(-) diff --git a/packages/vector_graphics/lib/vector_graphics.dart b/packages/vector_graphics/lib/vector_graphics.dart index 0936f8918b6c..38cd06e6d442 100644 --- a/packages/vector_graphics/lib/vector_graphics.dart +++ b/packages/vector_graphics/lib/vector_graphics.dart @@ -23,16 +23,42 @@ ui.Picture decodeVectorGraphics(ByteData data) { } /// A widget that displays a vector_graphics formatted asset. -class VectorGraphics extends StatefulWidget { - const VectorGraphics({Key? key, required this.controller}) : super(key: key); +/// +/// A bytes loader class should not be constructed directly in a build method, +/// if this is done the corresponding [VectorGraphic] widget may repeatedly +/// reload the bytes. +/// +/// ```dart +/// class MyVectorGraphic extends StatefulWidget { +/// +/// State createState() => +/// } +/// +/// class _MyVectorGraphicState extends State { +/// BytesLoader? loader; +/// +/// @override +/// void initState() { +/// super.initState(); +/// loader = AssetBytesLoader(assetName: 'foobar', assetBundle: DefaultAssetBundle.of(context)); +/// } +/// +/// @override +/// Widget build(BuildContext context) { +/// return VectorGraphic(bytesLoader: loader!); +/// } +/// } +/// ``` +class VectorGraphic extends StatefulWidget { + const VectorGraphic({Key? key, required this.bytesLoader}) : super(key: key); - final VectorGraphicsController controller; + final BytesLoader bytesLoader; @override - State createState() => _VectorGraphicsWidgetState(); + State createState() => _VectorGraphicsWidgetState(); } -class _VectorGraphicsWidgetState extends State { +class _VectorGraphicsWidgetState extends State { ui.Picture? _picture; @override @@ -42,8 +68,8 @@ class _VectorGraphicsWidgetState extends State { } @override - void didUpdateWidget(covariant VectorGraphics oldWidget) { - if (!oldWidget.controller.equivalent(widget.controller)) { + void didUpdateWidget(covariant VectorGraphic oldWidget) { + if (oldWidget.bytesLoader != widget.bytesLoader) { _loadAssetBytes(); } super.didUpdateWidget(oldWidget); @@ -57,7 +83,7 @@ class _VectorGraphicsWidgetState extends State { } void _loadAssetBytes() { - widget.controller.loadBytes().then((ByteData data) { + widget.bytesLoader.loadBytes().then((ByteData data) { final ui.Picture picture = decodeVectorGraphics(data); setState(() { _picture?.dispose(); @@ -76,20 +102,30 @@ class _VectorGraphicsWidgetState extends State { } } -abstract class VectorGraphicsController { - const VectorGraphicsController(); - +/// An interface that can be implemented to support decoding vector graphic +/// binary assets from different byte sources. +/// +/// A bytes loader class should not be constructed directly in a build method, +/// if this is done the corresponding [VectorGraphic] widget may repeatedly +/// reload the bytes. +/// +/// See also: +/// * [AssetBytesLoader], for loading from the asset bundle. +/// * [NetworkBytesLoader], for loading network bytes. +abstract class BytesLoader { + /// const constructor to allow subtypes to be const. + const BytesLoader(); + + /// Load the byte data for a vector graphic binary asset. Future loadBytes(); - - bool equivalent(VectorGraphicsController other) => false; } /// A controller for loading vector graphics data from an asset bundle. -class VectorGraphicsAssetController extends VectorGraphicsController { +class AssetBytesLoader extends BytesLoader { /// Create a new [VectorGraphicsAssetController]. /// /// The default asset bundle can be acquired using [DefaultAssetBundle.of]. - const VectorGraphicsAssetController({ + const AssetBytesLoader({ required this.assetName, this.packageName, required this.assetBundle, @@ -99,14 +135,6 @@ class VectorGraphicsAssetController extends VectorGraphicsController { final String? packageName; final AssetBundle assetBundle; - @override - bool equivalent(VectorGraphicsController other) { - return other is VectorGraphicsAssetController && - other.assetName == assetName && - other.packageName == packageName && - other.assetBundle == assetBundle; - } - @override Future loadBytes() { return assetBundle.load(assetName); @@ -114,8 +142,8 @@ class VectorGraphicsAssetController extends VectorGraphicsController { } /// A controller for loading vector graphics data from over the network. -class VectorGraphicsNetworkController extends VectorGraphicsController { - const VectorGraphicsNetworkController({ +class NetworkBytesLoader extends BytesLoader { + const NetworkBytesLoader({ required this.url, this.headers, this.client, @@ -125,14 +153,6 @@ class VectorGraphicsNetworkController extends VectorGraphicsController { final Uri url; final HttpClient? client; - @override - bool equivalent(VectorGraphicsController other) { - // This intentionally does not use [client]. - return other is VectorGraphicsNetworkController && - other.url == url && - mapEquals(other.headers, headers); - } - @override Future loadBytes() async { final HttpClient currentClient = client ?? HttpClient(); @@ -184,7 +204,6 @@ class _RenderVectorGraphics extends RenderProxyBox { @override void paint(PaintingContext context, ui.Offset offset) { - context.canvas.translate(offset.dx, offset.dy); context.canvas.drawPicture(picture); } } From 7b3feeee65a2fe1d36ad3944a69bfbdac4abad8c Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 1 Mar 2022 14:42:40 -0800 Subject: [PATCH 6/6] fix pubspec and CHANGELOG --- packages/vector_graphics/CHANGELOG.md | 5 ++++- packages/vector_graphics/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/vector_graphics/CHANGELOG.md b/packages/vector_graphics/CHANGELOG.md index ec6a6ba50346..eb0a03cb1a80 100644 --- a/packages/vector_graphics/CHANGELOG.md +++ b/packages/vector_graphics/CHANGELOG.md @@ -1,7 +1,10 @@ # CHANGELOG ## 0.0.1 - * Added `VectorGraphicsWidget` which consumes encoded vector graphics assets. + * Added `VectorGraphic` which consumes encoded vector graphics assets using + a `BytesLoader`. + * Added `AssetBytesLoader` and `NetworkBytesLoader` as example loader + implementations. ## 0.0.0 diff --git a/packages/vector_graphics/pubspec.yaml b/packages/vector_graphics/pubspec.yaml index 82150d555245..ce1d47351c11 100644 --- a/packages/vector_graphics/pubspec.yaml +++ b/packages/vector_graphics/pubspec.yaml @@ -20,4 +20,4 @@ dev_dependencies: # Comment out before publishing dependency_overrides: vector_graphics_codec: - path: ../vector_graphics_codec \ No newline at end of file + path: ../vector_graphics_codec