Skip to content

Instantly share code, notes, and snippets.

@abarth
Created December 1, 2016 09:37
Show Gist options
  • Save abarth/bfa452b048aa4bba700fb3945f4dd7de to your computer and use it in GitHub Desktop.
Save abarth/bfa452b048aa4bba700fb3945f4dd7de to your computer and use it in GitHub Desktop.
flutter_sprites.patch
From b931cff0e20d8de43449abe745236a9f6ca16f22 Mon Sep 17 00:00:00 2001
From: Adam Barth <[email protected]>
Date: Thu, 22 Sep 2016 10:15:56 -0700
Subject: [PATCH] Make flutter_sprites standalone
Adds a LICENSE file and some examples.
---
.gitignore | 8 +
AUTHORS | 8 +
LICENSE | 27 ++
README.md | 64 +++--
examples/flutter.yaml | 14 +
examples/lib/fitness_demo.dart | 596 +++++++++++++++++++++++++++++++++++++++++
examples/lib/weather_demo.dart | 566 ++++++++++++++++++++++++++++++++++++++
pubspec.yaml | 17 +-
8 files changed, 1273 insertions(+), 27 deletions(-)
create mode 100644 .gitignore
create mode 100644 AUTHORS
create mode 100644 LICENSE
create mode 100644 examples/flutter.yaml
create mode 100644 examples/lib/fitness_demo.dart
create mode 100644 examples/lib/weather_demo.dart
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..14dde91
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,8 @@
+.atom
+.buildlog
+.DS_Store
+.idea
+.packages
+.pub
+packages
+pubspec.lock
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..5d0cac1
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,8 @@
+# Below is a list of people and organizations that have contributed
+# to the Flutter project. Names should be added to the list like so:
+#
+# Name/Organization <email address>
+
+Google Inc.
+Jim Simon <[email protected]>
+Lex Berezhny <[email protected]>
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..972bb2e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,27 @@
+// Copyright 2014 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
index a348284..ae40f0e 100644
--- a/README.md
+++ b/README.md
@@ -1,28 +1,32 @@
# Flutter Sprites
+
Flutter Sprites is a toolkit for building complex, high performance animations and 2D games with Flutter. Your sprite render tree lives inside a SpriteWidget that mixes seamlessly with other Flutter and Material widgets. You can use Flutter Sprites to create anything from an animated icon to a full fledged game.
+This library is currently not maintained or tested. If you are interested in using and maintaining this library, please contact [email protected] for more information.
+
This guide assumes a basic knowledge of Flutter and Dart. You can find an example of Flutter Sprites in the Flutter Gallery in the Weather demo, or in the flutter/game repository on Github.
## Setting up a SpriteWidget
+
The first thing you need to do to use Flutter Sprites is to setup a SpriteWidget with a root node that is used to draw it's contents. Any sprite nodes that you add to the root node will be rendered by the SpriteWidget. Typically, your root node is part of your app's state. This is an example of how you can setup a custom stateful widget with Flutter Sprites:
import 'package:flutter/material.dart';
import 'package:flutter_sprites/flutter_sprites.dart';
-
+
class MyWidget extends StatefulWidget {
@override
MyWidgetState createState() => new MyWidgetState();
}
-
+
class MyWidgetState extends State<MyWidget> {
NodeWithSize rootNode;
-
+
@override
void initState() {
super.initState();
rootNode = new NodeWithSize(const Size(1024.0, 1024.0));
}
-
+
@override
Widget build(BuildContext context) {
return new SpriteWidget(rootNode);
@@ -34,6 +38,7 @@ The root node that you provide the SpriteWidget is a NodeWithSize, the size of t
When you have added the SpriteWidget to your app's build method it will automatically start running animations and handling user input. There is no need for any other extra setup.
## Adding objects to your node graph
+
Your SpriteWidget manages a node graph, the root node is the NodeWithSize that is passed in to the SpriteWidget when it's created. To render sprites, particles systems, or any other objects simply add them to the node graph.
Each node in the node graph has a transform. The transform is inherited by its children, this makes it possible to build more complex structures by grouping objects together as children to a node and then manipulating the parent node. For example the following code creates a car sprite with two wheels attached to it. The car is added to the root node.
@@ -41,24 +46,26 @@ Each node in the node graph has a transform. The transform is inherited by its c
Sprite car = new Sprite.fromImage(carImage);
Sprite frontWheel = new Sprite.fromImage(wheelImage);
Sprite rearWheel = new Sprite.fromImage(wheelImage);
-
+
frontWheel.position = const Point(100, 50);
rearWheel.position = const Point(-100, 50);
-
+
car.addChild(frontWheel);
car.addChild(rearWheel);
-
+
rootNode.addChild(car);
-
+
You can manipulate the transform by setting the position, rotation, scale, and skew properties.
## Sprites, textures, and sprite sheets
+
The most common node type is the Sprite node. A sprite simply draws an image to the screen. Sprites can be drawn from Image objects or Texture objects. A texture is a part of an Image. Using a SpriteSheet you can pack several texture elements within a single image. This saves space in the device's gpu memory and also make drawing faster. Currently Flutter Sprites supports sprite sheets in json format and produced with a tool such as TexturePacker. It's uncommon to manually edit the sprite sheet files. You can create a SpriteSheet with a definition in json and an image:
SpriteSheet sprites = new SpriteSheet(myImage, jsonCode);
Texture texture = sprites['texture.png'];
## The frame cycle
+
Each time a new frame is rendered to screen Flutter Sprites will perform a number of actions. Sometimes when creating more advanced interactive animations or games, the order in which these actions are performed may matter.
This is the order things will happen:
@@ -66,25 +73,26 @@ This is the order things will happen:
1. Handle input events
2. Run animation actions
3. Call update functions on nodes
-4. Apply constraints
+4. Apply constraints
5. Render the frame to screen
Read more about each of the different phases below.
## Handling user input
+
You can subclass any node type to handle touches. To receive touches, you need to set the userInteractionEnabled property to true and override the handleEvent method. If the node you are subclassing doesn't have a size, you will also need to override the isPointInside method.
class EventHandlingNode extends NodeWithSize {
EventHandlingNode(Size size) : super(size) {
userInteractionEnabled = true;
}
-
+
@override handleEvent(SpriteBoxEvent event) {
if (event.type == PointerDownEvent)
...
else if (event.type == PointerMoveEvent)
...
-
+
return true;
}
}
@@ -92,11 +100,13 @@ You can subclass any node type to handle touches. To receive touches, you need t
If you want your node to receive multiple touches, set the handleMultiplePointers property to true. Each touch down or dragged touch will generate a separate call to the handleEvent method, you can distinguish each touch by its pointer property.
## Animating using actions
+
Flutter Sprites provides easy to use functions for animating nodes through actions. You can combine simple action blocks to create more complex animations.
To execute an action animation you first build the action itself, then pass it to the run method of a nodes action manager (see the Tweens section below for an example).
### Tweens
+
Tweens are the simplest building block for creating an animation. It will interpolate a value or property over a specified time period. You provide the ActionTween class with a setter function, its start and end value, and the duration for the tween.
After creating a tween, execute it by running it through a node's action manager.
@@ -109,12 +119,13 @@ After creating a tween, execute it by running it through a node's action manager
const Point(100.0, 0.0),
1.0
);
-
+
myNode.actions.run(myTween);
You can animate values of different types, such as floats, points, rectangles, and even colors. You can also optionally provide the ActionTween class with an easing function.
### Sequences
+
When you need to play two or more actions in a sequence, use the ActionSequence class:
ActionSequence sequence = new ActionSequence([
@@ -122,23 +133,26 @@ When you need to play two or more actions in a sequence, use the ActionSequence
middleAction,
lastAction
]);
-
+
### Groups
+
Use ActionGroup to play actions in parallel:
ActionGroup group = new ActionGroup([
action0,
action1
]);
-
+
### Repeat
+
You can loop any action, either a fixed number of times, or until the end of times:
ActionRepeat repeat = new ActionRepeat(loopedAction, 5);
-
+
ActionRepeatForever longLoop = new ActionRepeatForever(loopedAction);
-
+
### Composition
+
It's possible to create more complex actions by composing them in any way:
ActionSequence complexAction = new ActionSequence([
@@ -148,8 +162,9 @@ It's possible to create more complex actions by composing them in any way:
action1
])
]);
-
+
## Handle update events
+
Each frame, update events are sent to each node in the current node tree. Override the update method to manually do animations or to perform game logic.
MyNode extends Node {
@@ -161,6 +176,7 @@ Each frame, update events are sent to each node in the current node tree. Overri
}
## Defining constraints
+
Constraints are used to constrain properties of nodes. They can be used to position nodes relative other nodes, or adjust the rotation or scale. You can apply more than one constraint to a single node.
For example, you can use a constraint to make a node follow another node at a specific distance with a specified dampening. The dampening will smoothen out the following node's movement.
@@ -176,13 +192,14 @@ For example, you can use a constraint to make a node follow another node at a sp
Constraints are applied at the end of the frame cycle. If you need them to be applied at any other time, you can directly call the applyConstraints method of a Node object.
## Perform custom drawing
+
Flutter Sprites provides a default set of drawing primitives, but there are cases where you may want to perform custom drawing. To do this you will need to subclass either the Node or NodeWithSize class and override the paint method:
class RedCircle extends Node {
RedCircle(this.radius);
-
+
double radius;
-
+
@override
void paint(Canvas canvas) {
canvas.drawCircle(
@@ -192,13 +209,13 @@ Flutter Sprites provides a default set of drawing primitives, but there are case
);
}
}
-
+
If you are overriding a NodeWithSize you may want to call applyTransformForPivot before starting drawing to account for the node's pivot point. After the call the coordinate system is setup so you can perform drawing starting at origo to the size of the node.
@override
void paint(Canvas canvas) {
applyTransformForPivot(canvas);
-
+
canvas.drawRect(
new Rect.fromLTWH(0.0, 0.0, size.width, size.height),
myPaint
@@ -206,6 +223,7 @@ If you are overriding a NodeWithSize you may want to call applyTransformForPivot
}
## Add effects using particle systems
+
Particle systems are great for creating effects such as rain, smoke, or fire. It's easy to setup a particle system, but there are very many properties that can be tweaked. The best way of to get a feel for how they work is to simply play around with the them.
This is an example of how a particle system can be created, configured, and added to the scene:
@@ -220,5 +238,5 @@ This is an example of how a particle system can be created, configured, and adde
life: 1.5 * distance,
lifeVar: 1.0 * distance
);
-
- rootNode.addChild(particles);
\ No newline at end of file
+
+ rootNode.addChild(particles);
diff --git a/examples/flutter.yaml b/examples/flutter.yaml
new file mode 100644
index 0000000..b522943
--- /dev/null
+++ b/examples/flutter.yaml
@@ -0,0 +1,14 @@
+uses-material-design: true
+assets:
+ - packages/flutter_gallery_assets/weather_demo/sun.png
+ - packages/flutter_gallery_assets/weather_demo/clouds-0.png
+ - packages/flutter_gallery_assets/weather_demo/clouds-1.png
+ - packages/flutter_gallery_assets/weather_demo/ray.png
+ - packages/flutter_gallery_assets/weather_demo/sun.png
+ - packages/flutter_gallery_assets/weather_demo/weathersprites.json
+ - packages/flutter_gallery_assets/weather_demo/weathersprites.png
+ - packages/flutter_gallery_assets/weather_demo/icon-sun.png
+ - packages/flutter_gallery_assets/weather_demo/icon-rain.png
+ - packages/flutter_gallery_assets/weather_demo/icon-snow.png
+ - packages/flutter_gallery_assets/fitness_demo/jumpingjack.json
+ - packages/flutter_gallery_assets/fitness_demo/jumpingjack.png
diff --git a/examples/lib/fitness_demo.dart b/examples/lib/fitness_demo.dart
new file mode 100644
index 0000000..06f1efa
--- /dev/null
+++ b/examples/lib/fitness_demo.dart
@@ -0,0 +1,596 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:math' as math;
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_sprites/flutter_sprites.dart';
+
+ImageMap _images;
+SpriteSheet _sprites;
+
+class FitnessDemo extends StatelessWidget {
+ FitnessDemo({ Key key }) : super(key: key);
+
+ static const String routeName = '/fitness';
+
+ @override
+ Widget build(BuildContext context) {
+ return new Scaffold(
+ appBar: new AppBar(
+ title: new Text('Fitness')
+ ),
+ body: new _FitnessDemoContents()
+ );
+ }
+}
+
+class _FitnessDemoContents extends StatefulWidget {
+ _FitnessDemoContents({ Key key }) : super(key: key);
+
+ @override
+ _FitnessDemoContentsState createState() => new _FitnessDemoContentsState();
+}
+
+class _FitnessDemoContentsState extends State<_FitnessDemoContents> {
+
+ Future<Null> _loadAssets(AssetBundle bundle) async {
+ _images = new ImageMap(bundle);
+ await _images.load(<String>[
+ 'packages/flutter_gallery_assets/fitness_demo/jumpingjack.png',
+ ]);
+
+ String json = await DefaultAssetBundle.of(context).loadString('packages/flutter_gallery_assets/fitness_demo/jumpingjack.json');
+ _sprites = new SpriteSheet(_images['packages/flutter_gallery_assets/fitness_demo/jumpingjack.png'], json);
+ }
+
+ @override
+ void initState() {
+ super.initState();
+
+ AssetBundle bundle = DefaultAssetBundle.of(context);
+ _loadAssets(bundle).then((_) {
+ setState(() {
+ _assetsLoaded = true;
+ workoutAnimation = new _WorkoutAnimationNode(
+ onPerformedJumpingJack: () {
+ setState(() {
+ _count += 1;
+ });
+ },
+ onSecondPassed: (int seconds) {
+ setState(() {
+ _time = seconds;
+ });
+ }
+ );
+ });
+ });
+ }
+
+ bool _assetsLoaded = false;
+ int _count = 0;
+ int _time = 0;
+ int get kcal => (_count * 0.2).toInt();
+
+ _WorkoutAnimationNode workoutAnimation;
+
+ @override
+ Widget build(BuildContext context) {
+ if (!_assetsLoaded)
+ return new Container();
+
+ Color buttonColor;
+ String buttonText;
+ VoidCallback onButtonPressed;
+
+ if (workoutAnimation.workingOut) {
+ buttonColor = Colors.red[500];
+ buttonText = "STOP WORKOUT";
+ onButtonPressed = endWorkout;
+ } else {
+ buttonColor = Theme.of(context).primaryColor;
+ buttonText = "START WORKOUT";
+ onButtonPressed = startWorkout;
+ }
+
+ return new Material(
+ child: new Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ new Flexible(
+ child: new Container(
+ decoration: new BoxDecoration(backgroundColor: Colors.grey[800]),
+ child: new SpriteWidget(workoutAnimation, SpriteBoxTransformMode.scaleToFit)
+ )
+ ),
+ new Padding(
+ padding: new EdgeInsets.only(top: 20.0),
+ child: new Text('JUMPING JACKS', style: Theme.of(context).textTheme.title)
+ ),
+ new Padding(
+ padding: new EdgeInsets.only(top: 20.0, bottom: 20.0),
+ child: new Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ _createInfoPanelCell(Icons.accessibility, '$_count', 'COUNT'),
+ _createInfoPanelCell(Icons.timer, _formatSeconds(_time), 'TIME'),
+ _createInfoPanelCell(Icons.flash_on, '$kcal', 'KCAL')
+ ]
+ )
+ ),
+ new Padding(
+ padding: new EdgeInsets.only(bottom: 16.0),
+ child: new SizedBox(
+ width: 300.0,
+ height: 72.0,
+ child: new RaisedButton (
+ onPressed: onButtonPressed,
+ color: buttonColor,
+ child: new Text(
+ buttonText,
+ style: new TextStyle(color: Colors.white, fontSize: 20.0)
+ )
+ )
+ )
+ )
+ ]
+ )
+ );
+ }
+
+ Widget _createInfoPanelCell(IconData icon, String value, String description) {
+ Color color;
+ if (workoutAnimation.workingOut)
+ color = Colors.black87;
+ else
+ color = Theme.of(context).disabledColor;
+
+ return new Container(
+ width: 100.0,
+ child: new Center(
+ child: new Column(
+ children: <Widget>[
+ new Icon(icon, size: 48.0, color: color),
+ new Text(value, style: new TextStyle(fontSize: 24.0, color: color)),
+ new Text(description, style: new TextStyle(color: color))
+ ]
+ )
+ )
+ );
+ }
+
+ String _formatSeconds(int seconds) {
+ int minutes = seconds ~/ 60;
+ String secondsStr = "${seconds % 60}".padLeft(2, "0");
+ return "$minutes:$secondsStr";
+ }
+
+ void startWorkout() {
+ setState(() {
+ _count = 0;
+ _time = 0;
+ workoutAnimation.start();
+ });
+ }
+
+ void endWorkout() {
+ setState(() {
+ workoutAnimation.stop();
+
+ if (_count >= 3) {
+ showDialog(
+ context: context,
+ child: new Stack(children: <Widget>[
+ new _Fireworks(),
+ new Dialog(
+ title: new Text('Awesome workout'),
+ content: new Text('You have completed $_count jumping jacks. Good going!'),
+ actions: <Widget>[
+ new FlatButton(
+ child: new Text('SWEET'),
+ onPressed: () { Navigator.pop(context); }
+ )
+ ]
+ )
+ ])
+ );
+ }
+ });
+ }
+}
+
+typedef void _SecondPassedCallback(int seconds);
+
+class _WorkoutAnimationNode extends NodeWithSize {
+ _WorkoutAnimationNode({
+ this.onPerformedJumpingJack,
+ this.onSecondPassed
+ }) : super(const Size(1024.0, 1024.0)) {
+ reset();
+
+ _progress = new _ProgressCircle(const Size(800.0, 800.0));
+ _progress.pivot = const Point(0.5, 0.5);
+ _progress.position = const Point(512.0, 512.0);
+ addChild(_progress);
+
+ _jumpingJack = new _JumpingJack((){
+ onPerformedJumpingJack();
+ });
+ _jumpingJack.scale = 0.5;
+ _jumpingJack.position = const Point(512.0, 550.0);
+ addChild(_jumpingJack);
+ }
+
+ final VoidCallback onPerformedJumpingJack;
+ final _SecondPassedCallback onSecondPassed;
+
+ int seconds;
+
+ bool workingOut;
+
+ static const int _kTargetMillis = 1000 * 30;
+ int _startTimeMillis;
+ _ProgressCircle _progress;
+ _JumpingJack _jumpingJack;
+
+ void reset() {
+ seconds = 0;
+ workingOut = false;
+ }
+
+ void start() {
+ reset();
+ _startTimeMillis = new DateTime.now().millisecondsSinceEpoch;
+ workingOut = true;
+ _jumpingJack.animateJumping();
+ }
+
+ void stop() {
+ workingOut = false;
+ _jumpingJack.neutralPose();
+ }
+
+ @override
+ void update(double dt) {
+ if (workingOut) {
+ int millis = new DateTime.now().millisecondsSinceEpoch - _startTimeMillis;
+ int newSeconds = (millis) ~/ 1000;
+ if (newSeconds != seconds) {
+ seconds = newSeconds;
+ onSecondPassed(seconds);
+ }
+
+ _progress.value = millis / _kTargetMillis;
+ } else {
+ _progress.value = 0.0;
+ }
+ }
+}
+
+class _ProgressCircle extends NodeWithSize {
+ _ProgressCircle(Size size, [this.value = 0.0]) : super(size);
+
+ static const double _kTwoPI = math.PI * 2.0;
+ static const double _kEpsilon = .0000001;
+ static const double _kSweep = _kTwoPI - _kEpsilon;
+
+ double value;
+
+ @override
+ void paint(Canvas canvas) {
+ applyTransformForPivot(canvas);
+
+ Paint circlePaint = new Paint()
+ ..color = Colors.white30
+ ..strokeWidth = 24.0
+ ..style = PaintingStyle.stroke;
+
+ canvas.drawCircle(
+ new Point(size.width / 2.0, size.height / 2.0),
+ size.width / 2.0,
+ circlePaint
+ );
+
+ Paint pathPaint = new Paint()
+ ..color = Colors.purple[500]
+ ..strokeWidth = 25.0
+ ..style = PaintingStyle.stroke;
+
+ double angle = value.clamp(0.0, 1.0) * _kSweep;
+ Path path = new Path()
+ ..arcTo(Point.origin & size, -math.PI / 2.0, angle, false);
+ canvas.drawPath(path, pathPaint);
+ }
+}
+
+class _JumpingJack extends Node {
+ _JumpingJack(VoidCallback onPerformedJumpingJack) {
+ left = new _JumpingJackSide(false, onPerformedJumpingJack);
+ right = new _JumpingJackSide(true, null);
+ addChild(left);
+ addChild(right);
+ }
+
+ void animateJumping() {
+ left.animateJumping();
+ right.animateJumping();
+ }
+
+ void neutralPose() {
+ left.neutralPosition(true);
+ right.neutralPosition(true);
+ }
+
+ _JumpingJackSide left;
+ _JumpingJackSide right;
+}
+
+class _JumpingJackSide extends Node {
+ _JumpingJackSide(bool right, this.onPerformedJumpingJack) {
+ // Torso and head
+ torso = _createPart('torso.png', const Point(512.0, 512.0));
+ addChild(torso);
+
+ head = _createPart('head.png', const Point(512.0, 160.0));
+ torso.addChild(head);
+
+ if (right) {
+ torso.opacity = 0.0;
+ head.opacity = 0.0;
+ torso.scaleX = -1.0;
+ }
+
+ // Left side movable parts
+ upperArm = _createPart('upper-arm.png', const Point(445.0, 220.0));
+ torso.addChild(upperArm);
+ lowerArm = _createPart('lower-arm.png', const Point(306.0, 200.0));
+ upperArm.addChild(lowerArm);
+ hand = _createPart('hand.png', const Point(215.0, 127.0));
+ lowerArm.addChild(hand);
+ upperLeg = _createPart('upper-leg.png', const Point(467.0, 492.0));
+ torso.addChild(upperLeg);
+ lowerLeg = _createPart('lower-leg.png', const Point(404.0, 660.0));
+ upperLeg.addChild(lowerLeg);
+ foot = _createPart('foot.png', const Point(380.0, 835.0));
+ lowerLeg.addChild(foot);
+
+ torso.setPivotAndPosition(Point.origin);
+
+ neutralPosition(false);
+ }
+
+ _JumpingJackPart torso;
+ _JumpingJackPart head;
+ _JumpingJackPart upperArm;
+ _JumpingJackPart lowerArm;
+ _JumpingJackPart hand;
+ _JumpingJackPart lowerLeg;
+ _JumpingJackPart upperLeg;
+ _JumpingJackPart foot;
+
+ final VoidCallback onPerformedJumpingJack;
+
+ _JumpingJackPart _createPart(String textureName, Point pivotPosition) {
+ return new _JumpingJackPart(_sprites[textureName], pivotPosition);
+ }
+
+ void animateJumping() {
+ actions.stopAll();
+ actions.run(new ActionSequence(<Action>[
+ _createPoseAction(null, 0, 0.5),
+ new ActionCallFunction(_animateJumpingLoop)
+ ]));
+ }
+
+ void _animateJumpingLoop() {
+ actions.run(new ActionRepeatForever(
+ new ActionSequence(<Action>[
+ _createPoseAction(0, 1, 0.30),
+ _createPoseAction(1, 2, 0.30),
+ _createPoseAction(2, 1, 0.30),
+ _createPoseAction(1, 0, 0.30),
+ new ActionCallFunction(() {
+ if (onPerformedJumpingJack != null)
+ onPerformedJumpingJack();
+ })
+ ])
+ ));
+ }
+
+ void neutralPosition(bool animate) {
+ actions.stopAll();
+ if (animate) {
+ actions.run(_createPoseAction(null, 1, 0.5));
+ } else {
+ List<double> d = _dataForPose(1);
+ upperArm.rotation = d[0];
+ lowerArm.rotation = d[1];
+ hand.rotation = d[2];
+ upperLeg.rotation = d[3];
+ lowerLeg.rotation = d[4];
+ foot.rotation = d[5];
+ torso.position = new Point(0.0, d[6]);
+ }
+ }
+
+ ActionInterval _createPoseAction(int startPose, int endPose, double duration) {
+ List<double> d0 = _dataForPose(startPose);
+ List<double> d1 = _dataForPose(endPose);
+
+ List<ActionTween> tweens = <ActionTween>[
+ _tweenRotation(upperArm, d0[0], d1[0], duration),
+ _tweenRotation(lowerArm, d0[1], d1[1], duration),
+ _tweenRotation(hand, d0[2], d1[2], duration),
+ _tweenRotation(upperLeg, d0[3], d1[3], duration),
+ _tweenRotation(lowerLeg, d0[4], d1[4], duration),
+ _tweenRotation(foot, d0[5], d1[5], duration),
+ new ActionTween(
+ (Point a) => torso.position = a,
+ new Point(0.0, d0[6]),
+ new Point(0.0, d1[6]),
+ duration
+ )
+ ];
+
+ return new ActionGroup(tweens);
+ }
+
+ ActionTween _tweenRotation(_JumpingJackPart part, double r0, double r1, double duration) {
+ return new ActionTween(
+ (double a) => part.rotation = a,
+ r0,
+ r1,
+ duration
+ );
+ }
+
+ List<double> _dataForPose(int pose) {
+ if (pose == null)
+ return _dataForCurrentPose();
+
+ if (pose == 0) {
+ return <double>[
+ -80.0, // Upper arm rotation
+ -30.0, // Lower arm rotation
+ -10.0, // Hand rotation
+ -15.0, // Upper leg rotation
+ 5.0, // Lower leg rotation
+ 15.0, // Foot rotation
+ 0.0 // Torso y offset
+ ];
+ } else if (pose == 1) {
+ return <double>[
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ -70.0
+ ];
+ } else {
+ return <double>[
+ 40.0,
+ 30.0,
+ 10.0,
+ 20.0,
+ -20.0,
+ 15.0,
+ 40.0
+ ];
+ }
+ }
+
+ List<double> _dataForCurrentPose() {
+ return <double>[
+ upperArm.rotation,
+ lowerArm.rotation,
+ hand.rotation,
+ upperLeg.rotation,
+ lowerLeg.rotation,
+ foot.rotation,
+ torso.position.y
+ ];
+ }
+}
+
+class _JumpingJackPart extends Sprite {
+ _JumpingJackPart(Texture texture, this.pivotPosition) : super(texture);
+ final Point pivotPosition;
+
+ void setPivotAndPosition(Point newPosition) {
+ pivot = new Point(pivotPosition.x / 1024.0, pivotPosition.y / 1024.0);
+ position = newPosition;
+
+ for (Node child in children) {
+ _JumpingJackPart subPart = child;
+ subPart.setPivotAndPosition(
+ new Point(
+ subPart.pivotPosition.x - pivotPosition.x,
+ subPart.pivotPosition.y - pivotPosition.y
+ )
+ );
+ }
+ }
+}
+
+class _Fireworks extends StatefulWidget {
+ _Fireworks({ Key key }) : super(key: key);
+
+ @override
+ _FireworksState createState() => new _FireworksState();
+}
+
+class _FireworksState extends State<_Fireworks> {
+ @override
+ void initState() {
+ super.initState();
+ fireworks = new _FireworksNode();
+ }
+
+ _FireworksNode fireworks;
+
+ @override
+ Widget build(BuildContext context) {
+ return new SpriteWidget(fireworks);
+ }
+}
+
+class _FireworksNode extends NodeWithSize {
+ _FireworksNode() : super(const Size(1024.0, 1024.0));
+ double _countDown = 0.0;
+
+ @override
+ void update(double dt) {
+ if (_countDown <= 0.0) {
+ _addExplosion();
+ _countDown = randomDouble();
+ }
+
+ _countDown -= dt;
+ }
+
+ Color _randomExplosionColor() {
+ double rand = randomDouble();
+ if (rand < 0.25)
+ return Colors.pink[200];
+ else if (rand < 0.5)
+ return Colors.lightBlue[200];
+ else if (rand < 0.75)
+ return Colors.purple[200];
+ else
+ return Colors.cyan[200];
+ }
+
+ void _addExplosion() {
+ Color startColor = _randomExplosionColor();
+ Color endColor = startColor.withAlpha(0);
+
+ ParticleSystem system = new ParticleSystem(
+ _sprites['particle-0.png'],
+ numParticlesToEmit: 100,
+ emissionRate: 1000.0,
+ rotateToMovement: true,
+ startRotation: 90.0,
+ endRotation: 90.0,
+ speed: 100.0,
+ speedVar: 50.0,
+ startSize: 1.0,
+ startSizeVar: 0.5,
+ gravity: const Offset(0.0, 30.0),
+ colorSequence: new ColorSequence.fromStartAndEndColor(startColor, endColor)
+ );
+ system.position = new Point(randomDouble() * 1024.0, randomDouble() * 1024.0);
+ addChild(system);
+ }
+}
+
+void main() {
+ runApp(new MaterialApp(
+ title: 'Fitness',
+ home: new FitnessDemo()
+ ));
+}
diff --git a/examples/lib/weather_demo.dart b/examples/lib/weather_demo.dart
new file mode 100644
index 0000000..eea56d3
--- /dev/null
+++ b/examples/lib/weather_demo.dart
@@ -0,0 +1,566 @@
+// Copyright 2015 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'dart:async';
+import 'dart:ui' as ui show Image;
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_sprites/flutter_sprites.dart';
+
+ImageMap _images;
+SpriteSheet _sprites;
+
+enum WeatherType {
+ sun,
+ rain,
+ snow
+}
+
+class WeatherDemo extends StatefulWidget {
+ WeatherDemo({ Key key }) : super(key: key);
+
+ static const String routeName = '/weather';
+
+ @override
+ _WeatherDemoState createState() => new _WeatherDemoState();
+}
+
+class _WeatherDemoState extends State<WeatherDemo> {
+
+ Future<Null> _loadAssets(AssetBundle bundle) async {
+ _images = new ImageMap(bundle);
+ await _images.load(<String>[
+ 'packages/flutter_gallery_assets/weather_demo/clouds-0.png',
+ 'packages/flutter_gallery_assets/weather_demo/clouds-1.png',
+ 'packages/flutter_gallery_assets/weather_demo/ray.png',
+ 'packages/flutter_gallery_assets/weather_demo/sun.png',
+ 'packages/flutter_gallery_assets/weather_demo/weathersprites.png',
+ 'packages/flutter_gallery_assets/weather_demo/icon-sun.png',
+ 'packages/flutter_gallery_assets/weather_demo/icon-rain.png',
+ 'packages/flutter_gallery_assets/weather_demo/icon-snow.png'
+ ]);
+
+ String json = await bundle.loadString('packages/flutter_gallery_assets/weather_demo/weathersprites.json');
+ _sprites = new SpriteSheet(_images['packages/flutter_gallery_assets/weather_demo/weathersprites.png'], json);
+ }
+
+ @override
+ void initState() {
+ super.initState();
+
+ AssetBundle bundle = DefaultAssetBundle.of(context);
+ _loadAssets(bundle).then((_) {
+ setState(() {
+ assetsLoaded = true;
+ weatherWorld = new WeatherWorld();
+ });
+ });
+ }
+
+ bool assetsLoaded = false;
+
+ WeatherWorld weatherWorld;
+
+ @override
+ Widget build(BuildContext context) {
+ if (!assetsLoaded) {
+ return new Scaffold(
+ appBar: new AppBar(
+ title: new Text('Weather')
+ ),
+ body: new Container(
+ decoration: new BoxDecoration(
+ backgroundColor: const Color(0xff4aaafb)
+ )
+ )
+ );
+ }
+
+ return new Scaffold(
+ appBar: new AppBar(
+ title: new Text('Weather')
+ ),
+ body: new Material(
+ child: new Stack(
+ children: <Widget>[
+ new SpriteWidget(weatherWorld),
+ new Align(
+ alignment: new FractionalOffset(0.5, 0.8),
+ child: new Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: <Widget>[
+ new WeatherButton(
+ onPressed: () {
+ setState(() {
+ weatherWorld.weatherType = WeatherType.sun;
+ });
+ },
+ selected: weatherWorld.weatherType == WeatherType.sun,
+ icon: "packages/flutter_gallery_assets/weather_demo/icon-sun.png"
+ ),
+ new WeatherButton(
+ onPressed: () {
+ setState(() {
+ weatherWorld.weatherType = WeatherType.rain;
+ });
+ },
+ selected: weatherWorld.weatherType == WeatherType.rain,
+ icon: "packages/flutter_gallery_assets/weather_demo/icon-rain.png"
+ ),
+ new WeatherButton(
+ onPressed: () {
+ setState(() {
+ weatherWorld.weatherType = WeatherType.snow;
+ });
+ },
+ selected: weatherWorld.weatherType == WeatherType.snow,
+ icon: "packages/flutter_gallery_assets/weather_demo/icon-snow.png"
+ )
+ ]
+ )
+ )
+ ]
+ )
+ )
+ );
+ }
+}
+
+const double _kWeatherButtonSize = 56.0;
+const double _kWeatherIconSize = 36.0;
+
+class WeatherButton extends StatelessWidget {
+ WeatherButton({ this.icon, this.selected, this.onPressed, Key key }) : super(key: key);
+
+ final String icon;
+ final bool selected;
+ final VoidCallback onPressed;
+
+ @override
+ Widget build(BuildContext context) {
+ Color color;
+ if (selected)
+ color = Theme.of(context).primaryColor;
+ else
+ color = const Color(0x33000000);
+
+ return new Padding(
+ padding: const EdgeInsets.all(15.0),
+ child: new Material(
+ color: color,
+ type: MaterialType.circle,
+ elevation: 0,
+ child: new Container(
+ width: _kWeatherButtonSize,
+ height: _kWeatherButtonSize,
+ child: new InkWell(
+ onTap: onPressed,
+ child: new Center(
+ child: new Image.asset(
+ icon,
+ width: _kWeatherIconSize,
+ height: _kWeatherIconSize
+ )
+ )
+ )
+ )
+ )
+ );
+ }
+}
+
+const List<Color> _kBackgroundColorsTop = const <Color>[
+ const Color(0xff5ebbd5),
+ const Color(0xff0b2734),
+ const Color(0xffcbced7)
+];
+
+const List<Color> _kBackgroundColorsBottom = const <Color>[
+ const Color(0xff4aaafb),
+ const Color(0xff4c5471),
+ const Color(0xffe0e3ec)
+];
+
+class WeatherWorld extends NodeWithSize {
+ WeatherWorld() : super(const Size(2048.0, 2048.0)) {
+ _background = new GradientNode(
+ this.size,
+ _kBackgroundColorsTop[0],
+ _kBackgroundColorsBottom[0]
+ );
+ addChild(_background);
+
+ _cloudsSharp = new CloudLayer(
+ image: _images['packages/flutter_gallery_assets/weather_demo/clouds-0.png'],
+ rotated: false,
+ dark: false,
+ loopTime: 20.0
+ );
+ addChild(_cloudsSharp);
+
+ _cloudsDark = new CloudLayer(
+ image: _images['packages/flutter_gallery_assets/weather_demo/clouds-1.png'],
+ rotated: true,
+ dark: true,
+ loopTime: 40.0
+ );
+ addChild(_cloudsDark);
+
+ _cloudsSoft = new CloudLayer(
+ image: _images['packages/flutter_gallery_assets/weather_demo/clouds-1.png'],
+ rotated: false,
+ dark: false,
+ loopTime: 60.0
+ );
+ addChild(_cloudsSoft);
+
+ _sun = new Sun();
+ _sun.position = const Point(1024.0, 1024.0);
+ _sun.scale = 1.5;
+ addChild(_sun);
+
+ _rain = new Rain();
+ addChild(_rain);
+
+ _snow = new Snow();
+ addChild(_snow);
+ }
+
+ GradientNode _background;
+ CloudLayer _cloudsSharp;
+ CloudLayer _cloudsSoft;
+ CloudLayer _cloudsDark;
+ Sun _sun;
+ Rain _rain;
+ Snow _snow;
+
+ WeatherType get weatherType => _weatherType;
+
+ WeatherType _weatherType = WeatherType.sun;
+
+ set weatherType(WeatherType weatherType) {
+ if (weatherType == _weatherType)
+ return;
+
+ _weatherType = weatherType;
+
+ // Fade the background
+ _background.actions.stopAll();
+
+ _background.actions.run(new ActionTween(
+ (Color a) => _background.colorTop = a,
+ _background.colorTop,
+ _kBackgroundColorsTop[weatherType.index],
+ 1.0
+ ));
+
+ _background.actions.run(new ActionTween(
+ (Color a) => _background.colorBottom = a,
+ _background.colorBottom,
+ _kBackgroundColorsBottom[weatherType.index],
+ 1.0
+ ));
+
+ _cloudsDark.active = weatherType != WeatherType.sun;
+ _sun.active = weatherType == WeatherType.sun;
+ _rain.active = weatherType == WeatherType.rain;
+ _snow.active = weatherType == WeatherType.snow;
+ }
+
+ @override
+ void spriteBoxPerformedLayout() {
+ _sun.position = spriteBox.visibleArea.topLeft + const Offset(350.0, 180.0);
+ }
+}
+
+class GradientNode extends NodeWithSize {
+ GradientNode(Size size, this.colorTop, this.colorBottom) : super(size);
+
+ Color colorTop;
+ Color colorBottom;
+
+ @override
+ void paint(Canvas canvas) {
+ applyTransformForPivot(canvas);
+
+ Rect rect = Point.origin & size;
+ Paint gradientPaint = new Paint()..shader = new LinearGradient(
+ begin: FractionalOffset.topLeft,
+ end: FractionalOffset.bottomLeft,
+ colors: <Color>[colorTop, colorBottom],
+ stops: <double>[0.0, 1.0]
+ ).createShader(rect);
+
+ canvas.drawRect(rect, gradientPaint);
+ }
+}
+
+class CloudLayer extends Node {
+ CloudLayer({ ui.Image image, bool dark, bool rotated, double loopTime }) {
+ _sprites.add(_createSprite(image, dark, rotated));
+ _sprites[0].position = const Point(1024.0, 1024.0);
+ addChild(_sprites[0]);
+
+ _sprites.add(_createSprite(image, dark, rotated));
+ _sprites[1].position = const Point(3072.0, 1024.0);
+ addChild(_sprites[1]);
+
+ actions.run(new ActionRepeatForever(
+ new ActionTween(
+ (Point a) => position = a,
+ Point.origin,
+ const Point(-2048.0, 0.0),
+ loopTime)
+ ));
+ }
+
+ List<Sprite> _sprites = <Sprite>[];
+
+ Sprite _createSprite(ui.Image image, bool dark, bool rotated) {
+ Sprite sprite = new Sprite.fromImage(image);
+
+ if (rotated)
+ sprite.scaleX = -1.0;
+
+ if (dark) {
+ sprite.colorOverlay = const Color(0xff000000);
+ sprite.opacity = 0.0;
+ }
+
+ return sprite;
+ }
+
+ set active(bool active) {
+ double opacity;
+ if (active) opacity = 1.0;
+ else opacity = 0.0;
+
+ for (Sprite sprite in _sprites) {
+ sprite.actions.stopAll();
+ sprite.actions.run(new ActionTween(
+ (double a) => sprite.opacity = a,
+ sprite.opacity,
+ opacity,
+ 1.0
+ ));
+ }
+ }
+}
+
+const double _kNumSunRays = 50.0;
+
+class Sun extends Node {
+ Sun() {
+ _sun = new Sprite.fromImage(_images['packages/flutter_gallery_assets/weather_demo/sun.png']);
+ _sun.scale = 4.0;
+ _sun.transferMode = TransferMode.plus;
+ addChild(_sun);
+
+ _rays = <Ray>[];
+ for (int i = 0; i < _kNumSunRays; i += 1) {
+ Ray ray = new Ray();
+ addChild(ray);
+ _rays.add(ray);
+ }
+ }
+
+ Sprite _sun;
+ List<Ray> _rays;
+
+ set active(bool active) {
+ actions.stopAll();
+
+ double targetOpacity;
+ if (!active) targetOpacity = 0.0;
+ else targetOpacity = 1.0;
+
+ actions.run(
+ new ActionTween(
+ (double a) => _sun.opacity = a,
+ _sun.opacity,
+ targetOpacity,
+ 2.0
+ )
+ );
+
+ if (active) {
+ for (Ray ray in _rays) {
+ actions.run(new ActionSequence(<Action>[
+ new ActionDelay(1.5),
+ new ActionTween(
+ (double a) => ray.opacity = a,
+ ray.opacity,
+ ray.maxOpacity,
+ 1.5
+ )
+ ]));
+ }
+ } else {
+ for (Ray ray in _rays) {
+ actions.run(new ActionTween(
+ (double a) => ray.opacity = a,
+ ray.opacity,
+ 0.0,
+ 0.2
+ ));
+ }
+ }
+ }
+}
+
+class Ray extends Sprite {
+ double _rotationSpeed;
+ double maxOpacity;
+
+ Ray() : super.fromImage(_images['packages/flutter_gallery_assets/weather_demo/ray.png']) {
+ pivot = const Point(0.0, 0.5);
+ transferMode = TransferMode.plus;
+ rotation = randomDouble() * 360.0;
+ maxOpacity = randomDouble() * 0.2;
+ opacity = maxOpacity;
+ scaleX = 2.5 + randomDouble();
+ scaleY = 0.3;
+ _rotationSpeed = randomSignedDouble() * 2.0;
+
+ // Scale animation
+ double scaleTime = randomSignedDouble() * 2.0 + 4.0;
+
+ actions.run(new ActionRepeatForever(
+ new ActionSequence(<Action>[
+ new ActionTween((double a) => scaleX = a, scaleX, scaleX * 0.5, scaleTime),
+ new ActionTween((double a) => scaleX = a, scaleX * 0.5, scaleX, scaleTime)
+ ])
+ ));
+ }
+
+ @override
+ void update(double dt) {
+ rotation += dt * _rotationSpeed;
+ }
+}
+
+class Rain extends Node {
+ Rain() {
+ _addParticles(1.0);
+ _addParticles(1.5);
+ _addParticles(2.0);
+ }
+
+ List<ParticleSystem> _particles = <ParticleSystem>[];
+
+ void _addParticles(double distance) {
+ ParticleSystem particles = new ParticleSystem(
+ _sprites['raindrop.png'],
+ transferMode: TransferMode.srcATop,
+ posVar: const Point(1300.0, 0.0),
+ direction: 90.0,
+ directionVar: 0.0,
+ speed: 1000.0 / distance,
+ speedVar: 100.0 / distance,
+ startSize: 1.2 / distance,
+ startSizeVar: 0.2 / distance,
+ endSize: 1.2 / distance,
+ endSizeVar: 0.2 / distance,
+ life: 1.5 * distance,
+ lifeVar: 1.0 * distance
+ );
+ particles.position = const Point(1024.0, -200.0);
+ particles.rotation = 10.0;
+ particles.opacity = 0.0;
+
+ _particles.add(particles);
+ addChild(particles);
+ }
+
+ set active(bool active) {
+ actions.stopAll();
+ for (ParticleSystem system in _particles) {
+ if (active) {
+ actions.run(
+ new ActionTween(
+ (double a) => system.opacity = a,
+ system.opacity,
+ 1.0,
+ 2.0
+ ));
+ } else {
+ actions.run(
+ new ActionTween(
+ (double a) => system.opacity = a,
+ system.opacity,
+ 0.0,
+ 0.5
+ ));
+ }
+ }
+ }
+}
+
+class Snow extends Node {
+ Snow() {
+ _addParticles(_sprites['flake-0.png'], 1.0);
+ _addParticles(_sprites['flake-1.png'], 1.0);
+ _addParticles(_sprites['flake-2.png'], 1.0);
+
+ _addParticles(_sprites['flake-3.png'], 1.5);
+ _addParticles(_sprites['flake-4.png'], 1.5);
+ _addParticles(_sprites['flake-5.png'], 1.5);
+
+ _addParticles(_sprites['flake-6.png'], 2.0);
+ _addParticles(_sprites['flake-7.png'], 2.0);
+ _addParticles(_sprites['flake-8.png'], 2.0);
+ }
+
+ List<ParticleSystem> _particles = <ParticleSystem>[];
+
+ void _addParticles(Texture texture, double distance) {
+ ParticleSystem particles = new ParticleSystem(
+ texture,
+ transferMode: TransferMode.srcATop,
+ posVar: const Point(1300.0, 0.0),
+ direction: 90.0,
+ directionVar: 0.0,
+ speed: 150.0 / distance,
+ speedVar: 50.0 / distance,
+ startSize: 1.0 / distance,
+ startSizeVar: 0.3 / distance,
+ endSize: 1.2 / distance,
+ endSizeVar: 0.2 / distance,
+ life: 20.0 * distance,
+ lifeVar: 10.0 * distance,
+ emissionRate: 2.0,
+ startRotationVar: 360.0,
+ endRotationVar: 360.0,
+ radialAccelerationVar: 10.0 / distance,
+ tangentialAccelerationVar: 10.0 / distance
+ );
+ particles.position = const Point(1024.0, -50.0);
+ particles.opacity = 0.0;
+
+ _particles.add(particles);
+ addChild(particles);
+ }
+
+ set active(bool active) {
+ actions.stopAll();
+ for (ParticleSystem system in _particles) {
+ if (active) {
+ actions.run(
+ new ActionTween((double a) => system.opacity = a, system.opacity, 1.0, 2.0
+ ));
+ } else {
+ actions.run(
+ new ActionTween((double a) => system.opacity = a, system.opacity, 0.0, 0.5
+ ));
+ }
+ }
+ }
+}
+
+void main() {
+ runApp(new MaterialApp(
+ title: 'Weather',
+ home: new WeatherDemo()
+ ));
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index d73728b..f62afdc 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,14 +1,23 @@
name: flutter_sprites
description: A sprite toolkit built on top of Flutter
-version: 0.0.15
+version: 0.0.16
author: Flutter Authors <[email protected]>
-homepage: http://flutter.io
+homepage: https://flutter.io/
dependencies:
box2d: '>=0.3.0 <0.4.0'
flutter:
- path: ../flutter
+ sdk: flutter
dev_dependencies:
flutter_test:
- path: ../flutter_test
+ sdk: flutter
+
+ # For the examples.
+ flutter_gallery_assets:
+ git:
+ url: https://flutter.googlesource.com/gallery-assets
+ ref: ef928550119411358b8b25e16aecde6ace513526
+
+environment:
+ sdk: '>=1.19.0 <2.0.0'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment