Skip to content

Instantly share code, notes, and snippets.

@roipeker
Last active December 3, 2022 10:03
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save roipeker/37374272d15539aa60c2bdc39001a035 to your computer and use it in GitHub Desktop.
Save roipeker/37374272d15539aa60c2bdc39001a035 to your computer and use it in GitHub Desktop.
GraphX splashscreen sample
/// copyright roipeker 2020
///
/// web demo:
/// https://roi-graphx-splash.surge.sh
///
/// source code (gists):
/// https://gist.github.com/roipeker/37374272d15539aa60c2bdc39001a035
///
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:graphx/graphx.dart';
import 'svg_utils.dart';
/// Sprite that will hold a SVG path drawing with a
/// filled and outlined version... and the ability to partially
/// draw the outlined based on percentage.
class LogoDrawerSprite extends Sprite {
List<PathMetric> _metricsList;
Path _rootPath;
Shape line;
Shape fill;
LogoDrawerSprite() {
_init();
}
void _init() {
line = Shape();
fill = Shape();
addChild(line);
addChild(fill);
fill.visible = false;
}
Future<void> parseSvg(word) async {
var svgElement = await SvgUtils.svgStringToSvgDrawable(word);
final letters = <Path>[];
if (svgElement.hasDrawableContent) {
for (var c in svgElement.children) {
if (c is DrawableShape) {
letters.add(c.path);
}
}
}
_rootPath = letters.reduce(
(path1, path2) => Path.combine(PathOperation.union, path1, path2),
);
_metricsList = _rootPath.computeMetrics(forceClosed: false).toList();
drawPercent(1);
}
void drawPercent(double percent) {
line.graphics.clear();
line.graphics.lineStyle(1, Colors.white.value);
_metricsList.forEach((m) {
line.graphics.drawPath(m.extractPath(0, m.length * percent));
});
line.graphics.endFill();
}
void drawFill(Color color) {
fill.graphics.clear();
fill.graphics.beginFill(color.value);
fill.graphics.drawPath(_rootPath);
fill.graphics.endFill();
}
}
/// copyright roipeker 2020
///
/// web demo:
/// https://roi-graphx-splash.surge.sh
///
/// source code (gists):
/// https://gist.github.com/roipeker/37374272d15539aa60c2bdc39001a035
///
import 'package:flutter/material.dart';
import 'package:graphx/graphx.dart';
import 'splash_scene.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'graphx splash',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title:
Text('path animations', style: TextStyle(color: Colors.black26)),
backgroundColor: Colors.black12,
elevation: 0),
body: SceneBuilderWidget(
/// paint the scene in front of the child widget.
builder: () => SceneController(front: SplashScene()),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"I'm a flutter widget.",
style: Theme.of(context).textTheme.headline4,
),
SizedBox(height: 24),
FlutterLogo(size: 120),
SizedBox(height: 24),
MPSBuilder<bool>(
topics: [DemoEvents.run],
mps: mps,
builder: (ctx, event, _) => IgnorePointer(
ignoring: event.data ?? true,
child: TextButton(
child: Text('Run animation'),
onPressed: () => mps.emit(DemoEvents.reset),
),
),
),
],
),
),
),
);
}
}
/// copyright roipeker 2020
///
/// web demo:
/// https://roi-graphx-splash.surge.sh
///
/// source code (gists):
/// https://gist.github.com/roipeker/37374272d15539aa60c2bdc39001a035
///
import 'package:flutter/material.dart';
import 'package:graphx/graphx.dart';
import 'logo_drawer.dart';
import 'svg_assets.dart';
abstract class DemoEvents {
static const run = 'runDemo';
static const reset = 'resetDemo';
}
class SplashScene extends Sprite {
Sprite logoContainer;
LogoDrawerSprite graphxLogo;
LogoDrawerSprite byLogo;
LogoDrawerSprite roipekerLogo;
LogoDrawerSprite flutterLogo;
Shape splashCircle;
@override
void addedToStage() {
stage.onResized.add(_handleStageResize);
mps.on(DemoEvents.reset, _showDemo);
loadSvg();
}
void _handleStageResize() {
/// keep the logo centered.
logoContainer?.x = stage.stageWidth / 2;
logoContainer?.y = stage.stageHeight / 2;
}
void _showDemo() {
mps.emit1(DemoEvents.run, true);
splashCircle.graphics.clear();
splashCircle.graphics.beginFill(Colors.redAccent.value);
splashCircle.graphics.drawCircle(0, 0, stage.stageWidth);
splashCircle.graphics.endFill();
splashCircle.scale = 0;
stage.color = Colors.transparent.value;
stage.addChild(splashCircle);
splashCircle.setPosition(stage.stageWidth / 2, stage.stageHeight / 2);
splashCircle.tween(
duration: 1,
ease: GEase.easeInOutExpo,
scale: 1,
onComplete: () {
splashCircle.graphics.clear();
_runDemo();
},
);
}
void loadSvg() async {
splashCircle = Shape();
logoContainer = Sprite();
addChild(logoContainer);
graphxLogo = await buildLogo(graphxSvgString);
byLogo = await buildLogo(bySvgString);
roipekerLogo = await buildLogo(roipekerSvgString);
flutterLogo = await buildLogo(flutterSvgString);
GTween.timeScale = 1.0;
graphxLogo.drawFill(Colors.white);
byLogo.drawFill(Colors.white);
roipekerLogo.drawFill(Colors.white);
roipekerLogo.alignPivot(Alignment.bottomRight);
byLogo.alignPivot(Alignment.bottomRight);
// flutterLogo.drawFill(Colors.blue);
GTween.delayedCall(1, _runDemo);
// _runDemo();
}
void _resetObjects() {
/// position elements.
var ty = graphxLogo.height;
/// flutter logo next to "graphx"
flutterLogo.alignPivot(Alignment.bottomRight);
flutterLogo.x = graphxLogo.x - 8;
flutterLogo.y = ty;
/// bottom elements are bottom aligned, so add that value to `ty`
ty += roipekerLogo.pivotY + 10;
roipekerLogo.scale = 1;
roipekerLogo.y = ty;
roipekerLogo.x = graphxLogo.width;
byLogo.y = ty - 2;
byLogo.x = roipekerLogo.x - roipekerLogo.width - 8;
byLogo.line.y = 0;
// byLogo.line.alpha = 1;
graphxLogo.drawPercent(0);
byLogo.drawPercent(0);
roipekerLogo.drawPercent(0);
flutterLogo.drawPercent(0);
graphxLogo.fill.visible = false;
byLogo.fill.visible = false;
roipekerLogo.fill.visible = false;
roipekerLogo.line.alpha = 1;
graphxLogo.line.visible = true;
byLogo.line.visible = true;
roipekerLogo.line.visible = true;
flutterLogo.line.visible = true;
logoContainer.alignPivot();
logoContainer.x = stage.stageWidth / 2;
logoContainer.y = stage.stageHeight / 2;
}
void _runDemo() {
mps.emit1(DemoEvents.run, true);
visible = true;
stage.color = Colors.redAccent.value;
_resetObjects();
var t1 = 0.0.twn;
var t2 = 0.0.twn;
var t3 = 0.0.twn;
var t4 = 0.1.twn;
/// increment the delay to simulate a global timeline.
var dly = 0.0;
t4.tween(
1.0,
duration: 1.8,
delay: dly,
ease: GEase.easeInOutCirc,
onUpdate: () => flutterLogo.drawPercent(t4.value),
);
flutterLogo.scaleX -= 1;
flutterLogo.tween(
duration: 1,
scaleX: 1,
delay: .2,
ease: GEase.easeOutBack,
);
t1.tween(1.0, duration: 2, ease: GEase.easeOut, onUpdate: () {
graphxLogo.drawPercent(t1.value);
}, onComplete: () {
_showGraphxFill();
});
dly += 1.9;
t2.tween(1.0, duration: 1, delay: dly, onUpdate: () {
byLogo.drawPercent(t2.value);
}, onComplete: _showByFill);
dly += .7;
t3.tween(1.0,
duration: 2,
delay: dly,
ease: GEase.easeInOutExpo,
onUpdate: () => roipekerLogo.drawPercent(t3.value),
onComplete: _showRoiFill);
}
void _showByFill() {
// byLogo.fill.y = -20;
byLogo.line.tween(
duration: .25,
y: -20,
ease: GEase.easeInCirc,
onComplete: () {
byLogo.line.alpha = .6;
},
);
// byLogo.fill.tween(
byLogo.line.tween(
duration: .4,
delay: .25,
ease: GEase.bounceOut,
y: 0,
);
}
void _showRoiFill() {
roipekerLogo.fill.visible = true;
roipekerLogo.fill.alpha = 0;
roipekerLogo.line.tween(duration: .25, alpha: 0);
roipekerLogo.fill.tween(duration: .7, alpha: 1);
roipekerLogo.tween(
duration: .7,
scale: .75,
delay: .25,
ease: GEase.easeOutBack,
);
byLogo.tween(
duration: .2,
skewX: -.7,
rotation: -.1,
delay: .7,
ease: GEase.easeOut);
var px = roipekerLogo.x - roipekerLogo.width * .75 - 8;
byLogo.tween(
duration: .8,
x: px,
delay: .8,
ease: GEase.bounceOut,
);
byLogo.tween(
duration: 2.2,
skewX: 0,
rotation: 0,
delay: .8,
ease: GEase.elasticOut,
);
GTween.delayedCall(2.5, _showGlobalMask);
}
void _showGlobalMask() {
splashCircle.graphics.clear();
splashCircle.graphics.beginFill(Colors.white.value);
splashCircle.graphics.drawCircle(0, 0, stage.stageWidth);
splashCircle.graphics.endFill();
stage.addChild(splashCircle);
splashCircle.scale = 0.1;
splashCircle.setPosition(stage.stageWidth / 2, stage.stageHeight / 2);
splashCircle.tween(
duration: .45,
scale: 1,
// x: stage.stageWidth / 2,
// y: stage.stageHeight / 2,
ease: GEase.easeInCirc,
onComplete: showFlutter,
);
}
void showFlutter() {
stage.addChild(splashCircle);
stage.color = Colors.transparent.value;
splashCircle.tween(
duration: .4,
scale: 0,
// x: stage.stageWidth,
// y: stage.stageHeight / 2,
ease: GEase.easeOutQuint,
onComplete: () {
splashCircle.removeFromParent();
mps.emit1(DemoEvents.run, false);
},
);
visible = false;
}
void _showGraphxFill() {
/// create a shape to hide the transition between line and fill.
var msk = Shape();
msk.x = -5;
msk.graphics
.beginFill(Colors.white.value)
.drawRect(0, -5, graphxLogo.width + 10, graphxLogo.height + 10)
.endFill();
msk.scaleX = 0;
msk.tween(duration: .8, scaleX: 1, ease: GEase.easeInOutExpo);
msk.tween(
duration: .8,
delay: .8,
scaleX: 0,
ease: GEase.easeInOutExpo,
onStart: () {
/// change line to fill.
graphxLogo.line.visible = false;
graphxLogo.fill.visible = true;
msk.pivotX = msk.width;
msk.x += msk.pivotX;
},
onComplete: () => msk.removeFromParent(true),
);
graphxLogo.addChild(msk);
}
Future<LogoDrawerSprite> buildLogo(word) async {
var logo = LogoDrawerSprite();
await logo.parseSvg(word);
logoContainer.addChild(logo);
return logo;
}
}
/// copyright roipeker 2020
///
/// web demo:
/// https://roi-graphx-splash.surge.sh
///
/// source code (gists):
/// https://gist.github.com/roipeker/37374272d15539aa60c2bdc39001a035
///
/// just raw SVG strings... I used Figma to get the outlined texts.
const graphxSvgString = '''
<svg width="240" height="40" viewBox="0 0 240 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 40V0H31.9922V8.00781H7.99805V32.002H23.9941V24.0039H15.9961V16.0059H31.9922V40H0Z" fill="black"/>
<path d="M40 40V0H71.9922V8.00781H47.998V40H40Z" fill="black"/>
<path d="M103.994 32.002V24.0039H87.998V32.002H103.994ZM80 40V16.0059H103.994V8.00781H111.992V40H80ZM80 8.00781V0H103.994V8.00781H80Z" fill="black"/>
<path d="M143.994 16.0059V8.00781H127.998V16.0059H143.994ZM120 40V0H151.992V24.0039H127.998V40H120Z" fill="black"/>
<path d="M160 40V0H167.998V16.0059H191.992V40H183.994V24.0039H167.998V40H160Z" fill="black"/>
<path d="M200 40V32.002H207.998V40H200ZM231.992 40V32.002H240V40H231.992ZM207.998 32.002V24.0039H215.996V32.002H207.998ZM223.994 32.002V24.0039H231.992V32.002H223.994ZM215.996 24.0039V16.0059H223.994V24.0039H215.996ZM207.998 16.0059V8.00781H215.996V16.0059H207.998ZM223.994 16.0059V8.00781H231.992V16.0059H223.994ZM200 8.00781V0H207.998V8.00781H200ZM231.992 8.00781V0H240V8.00781H231.992Z" fill="black"/>
</svg>
''';
const bySvgString = '''
<svg width="23" height="21" viewBox="0 0 23 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.41401 4.384C7.96868 4.384 9.20801 4.91933 10.132 5.99C11.0413 7.07533 11.496 8.52 11.496 10.324C11.496 12.1133 11.0413 13.5507 10.132 14.636C9.20801 15.7213 7.96868 16.264 6.41401 16.264C4.81534 16.264 3.60534 15.6333 2.78401 14.372V16H0.122009V0.335999H2.89401V6.122C3.70068 4.96333 4.87401 4.384 6.41401 4.384ZM3.62001 13.03C4.11868 13.6607 4.83001 13.976 5.75401 13.976C6.66334 13.976 7.38201 13.646 7.91001 12.986C8.42334 12.3407 8.68001 11.424 8.68001 10.236C8.68001 9.07733 8.42334 8.19733 7.91001 7.596C7.41134 6.98 6.69268 6.672 5.75401 6.672C4.83001 6.672 4.11134 6.98733 3.59801 7.618C3.08468 8.24867 2.82801 9.15066 2.82801 10.324C2.82801 11.512 3.09201 12.414 3.62001 13.03Z" fill="black"/>
<path d="M18.6974 7.508L19.6214 4.648H22.5254L18.1474 16.924C17.6781 18.2147 17.0914 19.102 16.3874 19.586C15.6687 20.0847 14.6274 20.334 13.2634 20.334C12.8087 20.334 12.4054 20.312 12.0534 20.268V18.09H13.1094C13.7107 18.09 14.1727 17.9213 14.4954 17.584C14.8181 17.2467 14.9794 16.8067 14.9794 16.264C14.9794 15.7213 14.8034 14.9587 14.4514 13.976L11.1514 4.648H14.1434L15.0674 7.486C15.7127 9.51 16.3141 11.622 16.8714 13.822C17.3114 12.1647 17.9201 10.06 18.6974 7.508Z" fill="black"/>
</svg>
''';
const roipekerSvgString = '''
<svg width="123" height="32" viewBox="0 0 123 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.118 7.18399C10.5033 7.18399 10.8547 7.20666 11.172 7.25199V11.196H10.084C8.45201 11.196 7.19401 11.6153 6.31001 12.454C5.44868 13.27 5.01801 14.4827 5.01801 16.092V25H0.734009V7.45599H4.88201V10.584C5.85668 8.31732 7.60201 7.18399 10.118 7.18399Z" fill="black"/>
<path d="M29.0751 16.228C29.0751 18.9933 28.2591 21.2147 26.6271 22.892C24.9951 24.5693 22.8418 25.408 20.1671 25.408C17.4925 25.408 15.3391 24.5693 13.7071 22.892C12.0525 21.2373 11.2251 19.016 11.2251 16.228C11.2251 13.44 12.0525 11.2187 13.7071 9.56399C15.3391 7.88666 17.4925 7.04799 20.1671 7.04799C22.8418 7.04799 24.9951 7.87532 26.6271 9.52999C28.2591 11.1847 29.0751 13.4173 29.0751 16.228ZM16.8011 20.41C17.6171 21.3847 18.7391 21.872 20.1671 21.872C21.5951 21.872 22.7058 21.3847 23.4991 20.41C24.3151 19.4127 24.7231 18.0187 24.7231 16.228C24.7231 14.4373 24.3151 13.0547 23.4991 12.08C22.7058 11.0827 21.5951 10.584 20.1671 10.584C18.7391 10.584 17.6171 11.0713 16.8011 12.046C16.0078 13.0207 15.6111 14.4147 15.6111 16.228C15.6111 18.0413 16.0078 19.4353 16.8011 20.41Z" fill="black"/>
<path d="M35.333 5.17799H31.117V1.16599H35.333V5.17799ZM35.367 25H31.083V7.45599H35.367V25Z" fill="black"/>
<path d="M47.8633 7.04799C50.266 7.04799 52.1813 7.87532 53.6093 9.52999C55.0147 11.2073 55.7173 13.44 55.7173 16.228C55.7173 18.9933 55.0147 21.2147 53.6093 22.892C52.1813 24.5693 50.266 25.408 47.8633 25.408C45.4833 25.408 43.67 24.5127 42.4233 22.722V31.664H38.1393V7.45599H42.2533V9.97199C43.5227 8.02266 45.3927 7.04799 47.8633 7.04799ZM43.5453 20.41C44.316 21.3847 45.4153 21.872 46.8433 21.872C48.2487 21.872 49.3593 21.362 50.1753 20.342C50.9687 19.3447 51.3653 17.928 51.3653 16.092C51.3653 14.3013 50.9687 12.9413 50.1753 12.012C49.4047 11.06 48.294 10.584 46.8433 10.584C45.4153 10.584 44.3047 11.0713 43.5113 12.046C42.718 13.0207 42.3213 14.4147 42.3213 16.228C42.3213 18.064 42.7293 19.458 43.5453 20.41Z" fill="black"/>
<path d="M74.2712 16.534V17.554H61.1472C61.3059 19.05 61.7706 20.1833 62.5412 20.954C63.3346 21.702 64.3886 22.076 65.7032 22.076C67.6526 22.076 68.9899 21.2373 69.7152 19.56H73.8292C73.3759 21.3507 72.4239 22.7787 70.9732 23.844C69.5226 24.8867 67.7546 25.408 65.6692 25.408C63.0626 25.408 60.9546 24.5693 59.3452 22.892C57.7359 21.2147 56.9312 18.9933 56.9312 16.228C56.9312 13.4627 57.7246 11.2413 59.3112 9.56399C60.9206 7.88666 63.0172 7.04799 65.6012 7.04799C68.2532 7.04799 70.3612 7.92066 71.9252 9.66599C73.4892 11.4113 74.2712 13.7007 74.2712 16.534ZM65.5672 10.38C63.0512 10.38 61.5892 11.774 61.1812 14.562H69.9872C69.8059 13.27 69.3299 12.25 68.5592 11.502C67.7886 10.754 66.7912 10.38 65.5672 10.38Z" fill="black"/>
<path d="M93.2214 25H88.3254L83.2254 16.84L80.5734 19.492V25H76.3574V0.791992H80.5734V14.732L87.7134 7.45599H92.8134L86.1834 14.086L93.2214 25Z" fill="black"/>
<path d="M109.601 16.534V17.554H96.4767C96.6353 19.05 97.1 20.1833 97.8707 20.954C98.664 21.702 99.718 22.076 101.033 22.076C102.982 22.076 104.319 21.2373 105.045 19.56H109.159C108.705 21.3507 107.753 22.7787 106.303 23.844C104.852 24.8867 103.084 25.408 100.999 25.408C98.392 25.408 96.284 24.5693 94.6747 22.892C93.0653 21.2147 92.2607 18.9933 92.2607 16.228C92.2607 13.4627 93.054 11.2413 94.6407 9.56399C96.25 7.88666 98.3467 7.04799 100.931 7.04799C103.583 7.04799 105.691 7.92066 107.255 9.66599C108.819 11.4113 109.601 13.7007 109.601 16.534ZM100.897 10.38C98.3807 10.38 96.9187 11.774 96.5107 14.562H105.317C105.135 13.27 104.659 12.25 103.889 11.502C103.118 10.754 102.121 10.38 100.897 10.38Z" fill="black"/>
<path d="M121.071 7.18399C121.456 7.18399 121.808 7.20666 122.125 7.25199V11.196H121.037C119.405 11.196 118.147 11.6153 117.263 12.454C116.402 13.27 115.971 14.4827 115.971 16.092V25H111.687V7.45599H115.835V10.584C116.81 8.31732 118.555 7.18399 121.071 7.18399Z" fill="black"/>
</svg>
''';
const flutterSvgString = '''
<svg width="33" height="40" viewBox="0 0 33 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20.4362 0L0 20.0008L6.29381 26.1678L32.9728 0.0200008H20.4583L20.4362 0ZM20.46 18.4541L9.45263 29.2162L20.4583 40H33L22.0113 29.2196L33 18.4524H20.4617L20.46 18.4541Z" fill="black"/>
</svg>
''';
/// copyright roipeker 2020
///
/// web demo:
/// https://roi-graphx-splash.surge.sh
///
/// source code (gists):
/// https://gist.github.com/roipeker/37374272d15539aa60c2bdc39001a035
///
import 'dart:ui';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:graphx/graphx.dart';
/// Utility functions to work with flutter_svg.
/// copy and paste in your project.
class SvgUtils {
static Future<DrawableRoot> svgStringToSvgDrawable(String rawSvg) async {
return await svg.fromSvgString(rawSvg, rawSvg);
}
static Future<Picture> svgStringToPicutre(String rawSvg) async {
final svgRoot = await svg.fromSvgString(rawSvg, rawSvg);
return svgRoot.toPicture();
}
static void svgStringToCanvas(
String rawSvg,
Canvas canvas, {
bool scaleCanvas = true,
bool clipCanvas = true,
Size scaleCanvasSize,
}) async {
final svgRoot = await svg.fromSvgString(rawSvg, rawSvg);
if (scaleCanvas) {
svgRoot.scaleCanvasToViewBox(canvas, scaleCanvasSize);
}
if (clipCanvas) {
svgRoot.clipCanvasToViewBox(canvas);
}
svgRoot.draw(canvas, null);
}
static Future<SvgData> svgDataFromString(String rawSvg) async {
final svgRoot = await svg.fromSvgString(rawSvg, rawSvg);
var obj = SvgData();
obj.hasContent = svgRoot.hasDrawableContent;
obj.picture = svgRoot.toPicture();
obj.viewBox = GxRect.fromNative(svgRoot.viewport.viewBoxRect);
obj.size =
GxRect(0, 0, svgRoot.viewport.size.width, svgRoot.viewport.size.height);
return obj;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment