Pen Settings

JavaScript

Babel includes JSX processing.

Packages

Add Packages

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.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

Flutter

              
                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));
  }
}

              
            
!
999px

Console