Skip to content

Instantly share code, notes, and snippets.

@pskink
Last active October 5, 2023 16:03
Show Gist options
  • Star 22 You must be signed in to star a gist
  • Fork 8 You must be signed in to fork a gist
  • Save pskink/da43c327b75eec05d903fa1b4d0c4d3e to your computer and use it in GitHub Desktop.
Save pskink/da43c327b75eec05d903fa1b4d0c4d3e to your computer and use it in GitHub Desktop.
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
// sample code
/*
class CornerDecorationTest extends StatefulWidget {
@override
_CornerDecorationTestState createState() => _CornerDecorationTestState();
}
class _CornerDecorationTestState extends State<CornerDecorationTest> {
bool checked = false;
@override
Widget build(BuildContext context) {
return AnimatedContainer(
duration: Duration(milliseconds: 350),
decoration: CornerDecoration(
strokeWidth: checked? 4 : 1,
strokeColor: checked? Colors.orange : Colors.black,
insets: checked? EdgeInsets.all(16) : EdgeInsets.symmetric(vertical: 48 * 2.0, horizontal: 48),
cornerSide: checked? CornerSide.all(16, 48) : CornerSide.vertical(top: Size.square(16)),
fillTop: checked? 0.0 : 1.0,
fillBottom: checked? 0.0 : 1.0,
),
child: Container(
constraints: BoxConstraints.expand(),
decoration: FlutterLogoDecoration(style: FlutterLogoStyle.stacked),
child: OutlineButton(
onPressed: () => setState(() => checked = !checked),
child: Align(alignment: Alignment.centerLeft, child: Text('press me')),
),
),
);
}
}
*/
class CornerDecoration extends Decoration {
final double fillLeft;
final double fillTop;
final double fillRight;
final double fillBottom;
final double strokeWidth;
final Color strokeColor;
final EdgeInsets insets;
final double position;
final CornerSide cornerSide;
CornerDecoration({
this.fillLeft = 0.0,
this.fillTop = 0.0,
this.fillRight = 0.0,
this.fillBottom = 0.0,
this.strokeWidth = 3,
@required this.strokeColor,
EdgeInsets insets,
this.position = 0.5,
this.cornerSide = const CornerSide._all(Size.square(16)),
}) : insets = insets ?? EdgeInsets.all(strokeWidth),
assert(0 <= fillLeft && fillLeft <= 1),
assert(0 <= fillTop && fillTop <= 1),
assert(0 <= fillRight && fillRight <= 1),
assert(0 <= fillBottom && fillBottom <= 1);
@override
BoxPainter createBoxPainter([onChanged]) =>
_CornerBoxPainter(fillLeft, fillTop, fillRight, fillBottom, strokeWidth, strokeColor, insets, position, cornerSide);
@override
EdgeInsetsGeometry get padding => insets;
@override
Decoration lerpFrom(Decoration a, double t) {
if (a is CornerDecoration) {
return CornerDecoration(
fillLeft: lerpDouble(a.fillLeft, fillLeft, t),
fillTop: lerpDouble(a.fillTop, fillTop, t),
fillRight: lerpDouble(a.fillRight, fillRight, t),
fillBottom: lerpDouble(a.fillBottom, fillBottom, t),
strokeWidth: lerpDouble(a.strokeWidth, strokeWidth, t),
strokeColor: Color.lerp(a.strokeColor, strokeColor, t),
insets: EdgeInsetsGeometry.lerp(a.insets, insets, t),
position: lerpDouble(a.position, position, t),
cornerSide: CornerSide.lerp(a.cornerSide, cornerSide, t),
);
}
return null;
}
}
class CornerSide {
CornerSide.all(double x, [double y]) : this._all(Size(x, y ?? x));
CornerSide.vertical({
Size top = Size.zero,
Size bottom = Size.zero,
}) : this.only(
topLeft: top,
topRight: top,
bottomLeft: bottom,
bottomRight: bottom,
);
CornerSide.horizontal({
Size left = Size.zero,
Size right = Size.zero,
}) : this.only(
topLeft: left,
topRight: right,
bottomLeft: left,
bottomRight: right,
);
const CornerSide.only({
this.topLeft = Size.zero,
this.topRight = Size.zero,
this.bottomLeft = Size.zero,
this.bottomRight = Size.zero,
});
const CornerSide._all(Size size) : this.only(
topLeft: size,
topRight: size,
bottomLeft: size,
bottomRight: size,
);
final Size topLeft;
final Size topRight;
final Size bottomLeft;
final Size bottomRight;
static CornerSide lerp(CornerSide a, CornerSide b, double t) {
return CornerSide.only(
topLeft: Size.lerp(a.topLeft, b.topLeft, t),
topRight: Size.lerp(a.topRight, b.topRight, t),
bottomRight: Size.lerp(a.bottomRight, b.bottomRight, t),
bottomLeft: Size.lerp(a.bottomLeft, b.bottomLeft, t),
);
}
}
class _CornerBoxPainter extends BoxPainter {
final double fillLeft;
final double fillTop;
final double fillRight;
final double fillBottom;
final double strokeWidth;
final EdgeInsets insets;
final double position;
final CornerSide cornerSide;
Paint _p;
_CornerBoxPainter(this.fillLeft, this.fillTop, this.fillRight, this.fillBottom, this.strokeWidth, strokeColor, this.insets, this.position, this.cornerSide) {
_p = Paint()
..color = strokeColor
..style = PaintingStyle.stroke
// ..strokeCap = StrokeCap.square
..strokeWidth = strokeWidth;
}
@override
void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
var _rect = offset & configuration.size;
var a = _rect.deflate(strokeWidth / 2);
var b = insets.deflateRect(_rect).inflate(strokeWidth / 2);
var rect = Rect.lerp(a, b, position);
Path _path = Path();
var topLeftCornerRect = Alignment.topLeft.inscribe(cornerSide.topLeft, rect);
var topRightCornerRect = Alignment.topRight.inscribe(cornerSide.topRight, rect);
var bottomRightCornerRect = Alignment.bottomRight.inscribe(cornerSide.bottomRight, rect);
var bottomLeftCornerRect = Alignment.bottomLeft.inscribe(cornerSide.bottomLeft, rect);
var i = [
Offset.lerp(topLeftCornerRect.bottomLeft, rect.centerLeft, fillLeft),
rect.topLeft,
Offset.lerp(topLeftCornerRect.topRight, rect.topCenter, fillTop),
Offset.lerp(topRightCornerRect.topLeft, rect.topCenter, fillTop),
rect.topRight,
Offset.lerp(topRightCornerRect.bottomRight, rect.centerRight, fillRight),
Offset.lerp(bottomRightCornerRect.topRight, rect.centerRight, fillRight),
rect.bottomRight,
Offset.lerp(bottomRightCornerRect.bottomLeft, rect.bottomCenter, fillBottom),
Offset.lerp(bottomLeftCornerRect.bottomRight, rect.bottomCenter, fillBottom),
rect.bottomLeft,
Offset.lerp(bottomLeftCornerRect.topLeft, rect.centerLeft, fillLeft),
].iterator;
while (i.moveNext()) {
_path.moveTo(i.current.dx, i.current.dy); i.moveNext();
_path.lineTo(i.current.dx, i.current.dy); i.moveNext();
_path.lineTo(i.current.dx, i.current.dy);
}
canvas.drawPath(_path, _p);
}
}
class ConcaveDecoration extends Decoration {
final ShapeBorder shape;
final double depth;
final List<Color> colors;
final double opacity;
ConcaveDecoration({
@required this.shape,
@required this.depth,
this.colors = const [Colors.black87, Colors.white],
this.opacity = 1.0,
}) : assert(shape != null), assert(colors == null || colors.length == 2);
@override
BoxPainter createBoxPainter([onChanged]) => _ConcaveDecorationPainter(shape, depth, colors, opacity);
@override
EdgeInsetsGeometry get padding => shape.dimensions;
@override
Decoration lerpFrom(Decoration a, double t) {
if (a is ConcaveDecoration) {
t = Curves.easeInOut.transform(t);
return ConcaveDecoration(
shape: ShapeBorder.lerp(a.shape, shape, t),
depth: ui.lerpDouble(a.depth, depth, t),
colors: [
Color.lerp(a.colors[0], colors[0], t),
Color.lerp(a.colors[1], colors[1], t),
],
opacity: ui.lerpDouble(a.opacity, opacity, t),
);
}
return null;
}
}
class _ConcaveDecorationPainter extends BoxPainter {
ShapeBorder shape;
double depth;
List<Color> colors;
double opacity;
_ConcaveDecorationPainter(this.shape, this.depth, this.colors, this.opacity) {
if (depth > 0) {
colors = [
colors[1],
colors[0],
];
} else {
depth = -depth;
}
colors = [
colors[0].withOpacity(opacity),
colors[1].withOpacity(opacity),
];
}
@override
void paint(ui.Canvas canvas, ui.Offset offset, ImageConfiguration configuration) {
final shapePath = shape.getOuterPath(offset & configuration.size);
final rect = shapePath.getBounds();
final delta = 16 / rect.longestSide;
final stops = [0.5 - delta, 0.5 + delta];
final path = Path()
..fillType = PathFillType.evenOdd
..addRect(rect.inflate(depth * 2))
..addPath(shapePath, Offset.zero);
canvas.save();
canvas.clipPath(shapePath);
final paint = Paint()..maskFilter = MaskFilter.blur(BlurStyle.normal, depth);
final clipSize = rect.size.aspectRatio > 1? Size(rect.width, rect.height / 2) : Size(rect.width / 2, rect.height);
for (final alignment in [Alignment.topLeft, Alignment.bottomRight]) {
final shaderRect = alignment.inscribe(Size.square(rect.longestSide), rect);
paint..shader = ui.Gradient.linear(shaderRect.topLeft, shaderRect.bottomRight, colors, stops);
canvas.save();
canvas.clipRect(alignment.inscribe(clipSize, rect));
canvas.drawPath(path, paint);
canvas.restore();
}
canvas.restore();
}
}
@ajmaln
Copy link

ajmaln commented May 2, 2020

This is cool, thanks :)

@roked
Copy link

roked commented Jul 2, 2021

Thanks

@samuzaffar99
Copy link

Hi, is there a null safe version of this code available?
Above code is not compatible with flutter sdk version 2.8

@pskink
Copy link
Author

pskink commented Dec 25, 2021 via email

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment