spectrum 0.2.1+1 spectrum: ^0.2.1+1 copied to clipboard
A rainbow of Color and Gradient utilities. Interpolate gradients with a realized GradientTween. New type Steps & shaded varieties. Color generation & operators.
import 'package:flutter/material.dart';
import 'package:foil/foil.dart';
// Modular:
import 'package:spectrum/colors.dart';
import 'package:spectrum/gradients.dart';
// All in One:
// import 'package:spectrum/spectrum.dart';
import 'palette.dart';
// ignore_for_file: public_member_api_docs
// const color = Color(0xFFBB3399);
// const color = Color(0xFFFF0000);
const color = Color(0xFFFF61ED);
// const color = Color(0xFF00AA33);
// const color = Color(0xFF3F82D3);
// const color = Color(0x72A46565);
// const color = Color(0xFFFC5C74);
void main() => runApp(const ExampleFrame());
/// [MaterialApp] frame.
class ExampleFrame extends StatelessWidget {
/// [MaterialApp] frame.
const ExampleFrame({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) => MaterialApp(
debugShowCheckedModeBanner: false,
home: const Example(),
// home: const Palette(),
themeMode: ThemeMode.light,
theme: ThemeData(
primarySwatch: Spectrum.materialColor(
color.withOpacity(1),
mode: SwatchMode.shade,
),
),
);
}
/// Construct a [new Example] `Widget` to fill an [ExampleFrame].
class Example extends StatefulWidget {
/// Fill an [ExampleFrame] with a [Scaffold] and [AppBar] whose body is a
/// [PageView].
///
/// Also constructs the `final gradient`s to provide to other demo objects.
const Example({Key? key}) : super(key: key);
@override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
@override
Widget build(BuildContext context) {
// final gradient = LinearGradient(
final gradient = RadialGradient(
// final gradient = SweepGradient(
// final gradient = LinearSteps(
// final gradient = RadialSteps(
// final gradient = SweepSteps(
// final gradient = LinearShadedSteps(
// final gradient = RadialShadedSteps(
// final gradient = SweepShadedSteps(
//
center: const Alignment(0.5, -0.75),
// tileMode: TileMode.decal,
// stops: const [0.15, 0.25, 0.3, 0.75, 0.85],
// colors: color.asMaterialColor.asList,
colors: Spectrum.materialColor(color, mode: SwatchMode.shade, factor: 450)
.asList,
// colors: color.complementary(5),
// colors: color.complementary(18),
// colors: color.complementary(36),
);
final gradient2 = SweepShadedSteps(
center: const Alignment(-0.3, -1.25),
colors: color.complementary(18),
shadeFunction: Shades.withAlpha,
// softness: 0.005,
// shadeFactor: 175,
// shadeFactor: -150,
// shadeFactor: 125,
shadeFactor: 0.95,
);
final radialSteps = RadialSteps(colors: color.complementary(18));
final linearSteps =
LinearSteps(colors: (const Color(0xFF4BFF6F)).complementary(5));
final sweepSteps =
SweepSteps(colors: (const Color(0xFF71F2F0)).complementary(4));
return Scaffold(
backgroundColor: Colors.black,
appBar: AppBar(title: const Text('spectrum')),
body: PageView(
physics: const BouncingScrollPhysics(),
children: [
// /// Fast shading
// Container(
// decoration: BoxDecoration(
// gradient: RadialShadedSteps(
// colors: color.asMaterialColor.asList, // fast shading
// radius: 1.0,
// ),
// ),
// ),
/// Header "spectrum"
const Header(),
/// Full-page `Foil`-based `GradientTween` when tapped.
/// See [Foil] to understand why there should at least be a black
/// rectangle (`Container`) onto which to mask the `Foil`'s gradient.
TestFoilTween(
gradient,
gradient2,
isAgressive: true,
isSlow: false,
child: Container(color: Colors.black),
),
TestFoilTween(
gradient,
gradient2,
isAgressive: true,
isSlow: true,
child: Container(color: Colors.black),
),
/// Same as above, one fast and one slow, but flag `isAgressive` is disabled. //
// TestFoilTween(gradient0, gradient1, isAgressive: false, isSlow: false, child: Container(color: Colors.black),), //
// TestFoilTween(gradient0, gradient1, isAgressive: false, isSlow: true, child: Container(color: Colors.black),), //
/// Steps.softness demonstration
Column(
children: <Widget>[
Flexible(
child: AnimatedFoilDemo(
radialSteps,
isSlow: false,
isAgressive: true,
storyboard: _defaultStoryboard,
),
),
Flexible(
child: AnimatedFoilDemo(
linearSteps,
isSlow: false,
isAgressive: true,
storyboard: _defaultStoryboard,
),
),
Flexible(
child: AnimatedFoilDemo(
sweepSteps,
isSlow: false,
isAgressive: true,
storyboard: _defaultStoryboard,
),
),
],
),
/// FooShadedSteps demonstration
AnimatedFoilDemo(
LinearShadedSteps(
colors: (const Color(0xFF4BFF6F)).complementary(4),
shadeFunction: Shades.withWhite, // default
),
isSlow: false,
isAgressive: true,
storyboard: {
/// "tweenSpec" expects a [TweenSpec], which itself is a
/// `Map<GradientProperty, Tween<dynamic>>`.
GradientAnimation.tweenSpec: {
GradientProperty.shadeFactor: Tween<double>(begin: 0, end: 120),
GradientProperty.distance: Tween<double>(begin: 0.6, end: 0.4),
},
},
),
/// A column of color squares demonstrating the
/// `Color.complementary()` method.
const Palette(),
/// A column of color squares demonstrating the
/// `Color.complementary()` method.
///
/// Also consider getters such as `Color.complementPair` or
/// `Color.complementTetrad`.
SingleChildScrollView(
child: Column(children: const [
SizedBox(height: 25),
GenerateComplements(color, 1),
GenerateComplements(color, 2),
GenerateComplements(color, 3),
SizedBox(height: 25),
GenerateComplements(color, 4),
GenerateComplements(color, 5),
GenerateComplements(color, 6),
SizedBox(height: 25),
GenerateComplements(color, 36),
])),
/// A `GradientTween` frozen in time for granular keyframe inspection.
// Lerping(gradient, unwrappedGradient),
],
),
);
}
}
Animatable<Color?> colors = TweenSequence<Color?>([
TweenSequenceItem<Color?>(
weight: 1.0,
tween: ColorTween(begin: Colors.red, end: Colors.blue),
// tween: Tween<Color>(begin: Colors.red, end: Colors.blue),
),
TweenSequenceItem<Color?>(
weight: 1.0,
tween: ColorTween(begin: Colors.blue, end: Colors.green),
// tween: Tween<Color>(begin: Colors.blue, end: Colors.green),
),
TweenSequenceItem<Color?>(
weight: 1.0,
tween: ColorTween(begin: Colors.green, end: Colors.yellow),
// tween: Tween<Color>(begin: Colors.green, end: Colors.yellow),
),
TweenSequenceItem<Color?>(
weight: 1.0,
tween: ColorTween(begin: Colors.lightBlue, end: Colors.purple),
// tween: Tween<Color>(begin: Colors.blue, end: Colors.green),
),
TweenSequenceItem<Color?>(
weight: 1.0,
tween: ColorTween(begin: Colors.purple, end: Colors.amber),
// tween: Tween<Color>(begin: Colors.green, end: Colors.yellow),
),
TweenSequenceItem<Color?>(
weight: 1.0,
tween: ColorTween(begin: Colors.amber, end: Colors.red),
// tween: Tween<Color>(begin: Colors.yellow, end: Colors.red),
),
]);
class Header extends StatefulWidget {
const Header({Key? key}) : super(key: key);
@override
_HeaderState createState() => _HeaderState();
}
class _HeaderState extends State<Header> with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<Color?> _color;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: const Duration(seconds: 5),
vsync: this,
)
..addListener(() => setState(() {}))
..repeat(reverse: true);
_color = colors.animate(_controller);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return FittedBox(
child: ShaderMask(
// shaderCallback: (bounds) => GradientTween(
// begin: RadialShadedSteps(
// // colors: _color.value!.complementary(10),
// colors: _color.value!.asMaterialColor.asList,
// radius: 4,
// shadeFactor: 200,
// ),
// end: LinearGradient(
// colors: _color.value!.withWhite(50).complementary(6),
// ),
// isAgressive: false,
// ).evaluate(_controller)!.createShader(bounds),
shaderCallback: (bounds) => AnimatedGradient(
controller: _controller,
gradient: GradientTween(
begin: RadialShadedSteps(
// colors: _color.value!.complementary(10),
colors: _color.value!.asMaterialColor.asList,
radius: 4,
shadeFactor: 200,
),
end: LinearGradient(
colors: _color.value!.withWhite(50).complementary(6),
),
isAgressive: false,
).evaluate(_controller)!,
storyboard: {
// GradientAnimation.colorArithmetic: Shades.withOpacity,
// GradientAnimation.stopsArithmetic: Maths.addition,
GradientAnimation.tweenSpec: {
GradientProperty.center: Tween<AlignmentGeometry?>(
begin: const Alignment(-0.75, -0.75),
end: const Alignment(5.75, 5.75),
),
// GradientProperty.distance: Tween<double>(
// begin: 0,
// end: 1,
// ),
}
},
).observe.createShader(bounds),
child: const Text(
' spectrum ',
style: TextStyle(
fontSize: 400,
fontWeight: FontWeight.w900,
color: Colors.grey,
),
),
),
);
}
}
final _defaultStoryboard = {
// GradientAnimation.none: null,
/// "colorArithmetic" expects a [ColorArithmetic].
// GradientAnimation.colorArithmetic: Shades.withOpacity,
/// "stopsArithmetic" expects a [StopsArithmetic].
// GradientAnimation.stopsArithmetic: Maths.subtraction,
// GradientAnimation.stopsArithmetic:Maths.addition,
// GradientAnimation.stopsArithmetic:Maths.division,
/// "tweenSpec" expects a [TweenSpec], which itself is a
/// `Map<GradientProperty, Tween<dynamic>>`.
GradientAnimation.tweenSpec: {
// GradientProperty.center: Tween<AlignmentGeometry>(
// begin: const Alignment(1, 1),
// end: const Alignment(-1, -1)),
// GradientProperty.focal: Tween<AlignmentGeometry>(
// begin: const Alignment(3, -1),
// end: const Alignment(-3, -1)),
// GradientProperty.radius:
//Tween<double>(begin: 0.5, end: 0),
// GradientProperty.focalRadius:
// Tween<double>(begin:2,end:0),
// // GradientProperty.startAngle: Tween<double>(
// // begin: -1.0 * 3.1415927, end: 0.0 * 3.1415927),
// // GradientProperty.endAngle:
// // Tween<double>(begin: 2.0 * 3.1415927,end: 4.0 * 3.1415927),
// GradientProperty.shadeFactor:
//StepTween(begin: -200,end:0),
GradientProperty.softness: Tween<double>(begin: 0.0, end: 0.14),
},
};
class AnimatedFoilDemo extends StatefulWidget {
/// Tapping this `Foil` widget (that renders the gradient) will
/// tween the gradient to `gradient.reversed`.
const AnimatedFoilDemo(
this.gradient, {
Key? key,
this.storyboard,
required this.isSlow,
required this.isAgressive,
this.duration = const Duration(seconds: 2),
this.interval = const Interval(0.3, 1.0, curve: Curves.fastOutSlowIn),
this.child,
}) : super(key: key);
final Gradient gradient;
// final GradientStoryboard? storyboard;
final Map<GradientAnimation, dynamic>? storyboard;
final Duration duration;
final Interval interval;
final bool isSlow, isAgressive;
final Widget? child;
@override
_AnimatedFoilDemoState createState() => _AnimatedFoilDemoState();
}
class _AnimatedFoilDemoState extends State<AnimatedFoilDemo>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
bool isUnwrapped = false;
@override
void initState() {
super.initState();
_controller = AnimationController(duration: widget.duration, vsync: this)
..addListener(() => setState(() {}))
..addStatusListener((status) {
if (status == AnimationStatus.completed) _controller.reverse();
if (status == AnimationStatus.dismissed) _controller.forward();
})
..forward();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final controller = CurvedAnimation(
// Default duration is 2 seconds
parent: _controller,
// Default interval will hold state at initial position for
// 30% of the duration as `Interval(0.3, 1.0, curve...)`
curve: widget.interval,
);
return Foil(
isUnwrapped: isUnwrapped,
isAgressive: widget.isAgressive,
useSensor: false,
duration: widget.isSlow
? const Duration(milliseconds: 3000)
: const Duration(milliseconds: 1500),
curve: widget.isSlow ? Curves.linear : Curves.elasticOut,
speed: widget.isSlow
? const Duration(milliseconds: 200)
: const Duration(milliseconds: 1500),
gradient: widget.gradient.animate(
controller: controller,
/// A [GradientStoryboard] is a
/// `Map<GradientAnimation, dynamic>` where dynamic values are
/// description objects that are type-checked according to
/// their key.
storyboard: widget.storyboard ?? _defaultStoryboard,
),
unwrappedGradient:
widget.gradient.reversed.copyWith(radius: 0.025).animate(
controller: controller,
storyboard: widget.storyboard ?? _defaultStoryboard,
),
child: GestureDetector(
onTap: () => setState(() => isUnwrapped = !isUnwrapped),
child: Center(
child: widget.child ??
Container(color: Colors.white), // White for Foil masking
),
),
);
}
}
class TestFoilTween extends StatefulWidget {
const TestFoilTween(
this.gradient,
this.unwrappedGradient, {
Key? key,
required this.isSlow,
required this.isAgressive,
this.child,
}) : super(key: key);
final Gradient gradient, unwrappedGradient;
final bool isSlow, isAgressive;
final Widget? child;
@override
_TestFoilTweenState createState() => _TestFoilTweenState();
}
class _TestFoilTweenState extends State<TestFoilTween> {
bool isUnwrapped = false;
@override
Widget build(BuildContext context) {
var foil = Foil(
isUnwrapped: isUnwrapped,
isAgressive: widget.isAgressive,
gradient: widget.gradient,
unwrappedGradient: widget.unwrappedGradient,
blendMode: BlendMode.plus,
speed: widget.isSlow
? const Duration(milliseconds: 200)
: const Duration(milliseconds: 1500),
duration: widget.isSlow
? const Duration(milliseconds: 3000)
: const Duration(milliseconds: 1500),
curve: widget.isSlow ? Curves.linear : Curves.elasticOut,
child: widget.child ??
const DecoratedBox(
decoration: BoxDecoration(color: Colors.white),
),
);
return SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: GestureDetector(
onTap: () => setState(() => isUnwrapped = !isUnwrapped),
child: Stack(fit: StackFit.expand, children: [foil]),
),
);
}
}
class Lerping extends StatelessWidget {
const Lerping(this.gradient1, this.gradient2, {Key? key}) : super(key: key);
final Gradient gradient1, gradient2;
@override
Widget build(BuildContext context) {
// final test = gradient;
// final test = gradient.asGradient;
// final test = unwrappedGradient.asGradient;
const t = 0.0;
// ignore: unused_local_variable
final interpolated =
PrimitiveGradient.fromStretchLerp(gradient1, gradient2, t);
// ignore: unused_local_variable
final intermediate = IntermediateGradient(
PrimitiveGradient.fromStretchLerp(gradient1, gradient2, t),
// PrimitiveGradient.interpolateFrom(gradient, unwrappedGradient, 1.0),
GradientPacket(gradient1, gradient2, t),
// GradientPacket(gradient, unwrappedGradient, 1.0),
// PrimitiveGradient.interpolateFrom(test, unwrappedGradient, 0.0),
// GradientPacket(test, unwrappedGradient, 0.0),
);
final tween = GradientTween(
begin: gradient1,
// begin: intermediate,
// begin: test,
// end: const LinearGradient(colors: [Colors.green, Colors.blue]),
end: gradient2,
// isAgressive: false,
);
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Center(
// child: Container(
// width: 1460,
// height: 825 / 5,
// decoration: BoxDecoration(gradient: gradient),
// ),
// ),
Center(
child: Container(
width: 1460,
height: 825 / 3,
// height: 825 / 5,
decoration: BoxDecoration(gradient: gradient1),
),
),
Center(
child: Container(
width: 1460,
height: 825 / 3,
// height: 825 / 5,
// decoration:
// BoxDecoration(gradient: unwrappedGradient.asGradient),
decoration: BoxDecoration(gradient: gradient2),
),
),
Center(
child: Container(
width: 1460,
height: 825 / 3,
// height: 825 / 5,
decoration: BoxDecoration(gradient: tween.lerp(0)),
),
),
// Center(
// child: Container(
// width: 1460,
// height: 825 / 5,
// decoration: BoxDecoration(gradient: tween.lerp(0.0)),
// // decoration: BoxDecoration(
// // gradient: Gradient.lerp(gradient, unwrappedGradient, 0)),
// // gradient.asGradient, unwrappedGradient.asGradient, 0)),
// ),
// ),
],
);
}
}