JavaScript preprocessors can help make authoring JavaScript easier and more convenient.
Babel includes JSX processing.
Search for and use JavaScript packages from npm here. By selecting a package, an import
statement will be added to the top of the JavaScript editor for this package.
Using packages here is powered by esm.sh, which makes packages from npm not only available on a CDN, but prepares them for native JavaScript ESM usage.
All packages are different, so refer to their docs for how they work.
If you're using React / ReactDOM, make sure to turn on Babel for the JSX processing.
If active, Pens will autosave every 30 seconds after being saved once.
If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.
If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.
Visit your global Editor Settings.
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'dart:math' as Math;
//My first pen made with CustomPainter
//Do experiments with Values in LiquidButton class like gap, tension etc.
void main() {
runApp(
MaterialApp(
home: MyWidget(),
),
);
}
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: LiquidButton(
backgroundColor: Colors.red,
height: 100,
child: Text(
"Liquid Button Flutter",
style: TextStyle(
fontSize: 30, color: Colors.white, fontWeight: FontWeight.bold),
),
gradientColor: Colors.orange,
width: 400,
),
),
);
}
}
// Liquid Button Class
class LiquidButton extends StatefulWidget {
final Widget child;
final double height;
final double width;
final Color backgroundColor;
final Color gradientColor;
final int gap;
final Duration duration;
final bool retainGradient;
final double tension;
final double expandFactor;
LiquidButton(
{Key key,
@required this.height,
@required this.width,
@required this.backgroundColor,
this.gradientColor,
this.gap = 1,
this.duration = const Duration(milliseconds: 300),
this.retainGradient = false,
this.tension = 1.0,
this.expandFactor = 10,
this.child})
: super(key: key) {
assert(expandFactor >= 1.0 && expandFactor <= 50.0);
assert(gap >= 1 && gap <= height / 2);
assert(tension >= 0.01 && tension <= 1.0);
}
@override
_LiquidButtonState createState() => _LiquidButtonState();
}
class _LiquidButtonState extends State<LiquidButton>
with TickerProviderStateMixin {
Offset position = Offset(0, 0);
Animation<double> animation;
AnimationController animationController;
@override
void initState() {
animationController =
new AnimationController(duration: widget.duration, vsync: this);
animation = new Tween<double>(begin: 1.0, end: widget.expandFactor)
.animate(animationController)
..addListener(() {
setState(() {});
});
animationController.forward(from: 0.0);
super.initState();
}
RenderBox renderBox;
@override
Widget build(BuildContext context) {
renderBox = context.findRenderObject();
if (!kIsWeb)
return GestureDetector(
onPanUpdate: onHoverM,
onPanDown: (de) => onEnter(null),
onPanEnd: (de) => onExit(null),
child: SizedBox(
width: widget.width,
height: widget.height,
child: CustomPaint(
painter: LiquidButtonCustomPainter(
canvasColor: widget.backgroundColor,
gap: widget.gap,
retainGradient: widget.retainGradient,
tension: widget.tension,
gradientColor: widget.gradientColor ?? widget.backgroundColor,
position: position,
maxExpansion: widget.expandFactor,
expandFactor: animation.value),
child: Center(child: widget.child),
),
),
);
else
return MouseRegion(
onHover: onHover,
onExit: onExit,
onEnter: onEnter,
child: SizedBox(
width: widget.width,
height: widget.height,
child: CustomPaint(
painter: LiquidButtonCustomPainter(
canvasColor: widget.backgroundColor,
gap: widget.gap,
retainGradient: widget.retainGradient,
tension: widget.tension,
gradientColor: widget.gradientColor ?? widget.backgroundColor,
position: position,
maxExpansion: widget.expandFactor,
expandFactor: animation.value),
child: Center(child: widget.child),
),
),
);
}
void onHover(PointerHoverEvent event) {
setState(() {
position = renderBox.globalToLocal(event.localPosition);
});
}
void onHoverM(DragUpdateDetails event) {
setState(() {
position = (event.localPosition);
});
}
void onEnter(PointerEnterEvent event) {
animationController.reverse(from: widget.expandFactor);
}
void onExit(PointerExitEvent event) {
animationController.forward(from: 0.0);
}
}
// Custom Painter
class LiquidButtonCustomPainter extends CustomPainter {
final double expandFactor;
final double maxExpansion;
List<Offset> points = [];
final Offset position;
final Color canvasColor;
final Color gradientColor;
final bool retainGradient;
final int gap;
final double tension;
LiquidButtonCustomPainter(
{@required this.expandFactor,
@required this.position,
@required this.gap,
@required this.tension,
@required this.maxExpansion,
@required this.canvasColor,
@required this.gradientColor,
@required this.retainGradient});
@override
void paint(Canvas canvas, Size size) {
double midTop = (size.width - doubleTilde(size.height / 2)) / 2;
for (var x = doubleTilde(size.height / 2); x < midTop * 2; x += this.gap) {
points.add(Offset(x, 0));
}
for (var alpha = doubleTilde(size.height * 1.25);
alpha >= 0;
alpha -= this.gap) {
var angle = (Math.pi / doubleTilde(size.height * 1.25)) * alpha;
points.add(Offset(
Math.sin(angle) * size.height / 2 + size.width - size.height / 2,
Math.cos(angle) * size.height / 2 + size.height / 2));
}
for (var x = size.width - doubleTilde(size.height / 2) - 1;
x >= doubleTilde(size.height / 2);
x -= this.gap) {
points.add(Offset(x, -0 + size.height));
}
for (var alpha = 0;
alpha <= doubleTilde(size.height * 1.25);
alpha += this.gap) {
var angle = (Math.pi / doubleTilde(size.height * 1.25)) * alpha;
points.add(Offset(
(size.height - Math.sin(angle) * size.height / 2) - size.height / 2,
Math.cos(angle) * size.height / 2 + size.height / 2));
}
Path path = Path();
var temp = attractedOffset(points[0]);
path.moveTo(temp.dx, temp.dy);
points.forEach((element) {
Offset temp = attractedOffset(element);
path.lineTo(temp.dx, temp.dy);
});
var gradient = RadialGradient(
radius: size.width / size.height,
colors: [
retainGradient
? gradientColor
: expandFactor == maxExpansion ? canvasColor : gradientColor,
canvasColor
],
center: Alignment.center);
final paint = Paint();
paint.shader = gradient.createShader(Rect.fromCenter(
center: position, height: size.height, width: size.width));
canvas.drawPath(path, paint);
}
double doubleTilde(double x) {
if (x < 0)
return x.ceilToDouble();
else
return x.floorToDouble();
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
Offset attractedOffset(Offset element) {
double dx = element.dx - position.dx;
double dy = element.dy - position.dy;
double dist = Math.sqrt(dx * dx + dy * dy);
double dist2 = Math.max(1, dist);
double d = Math.min(dist2, Math.max(12, (dist2 / 4) - dist2));
double D = dist2 * expandFactor;
return Offset(
element.dx + (d / D) * (dx * 2), element.dy + (d / D) * (dy * 2));
}
}
Also see: Tab Triggers