Skip to content

Instantly share code, notes, and snippets.

@flutter-clutter
Last active July 22, 2024 09:38
Show Gist options
  • Save flutter-clutter/66f5978d5056daa8265bc903db8c6c30 to your computer and use it in GitHub Desktop.
Save flutter-clutter/66f5978d5056daa8265bc903db8c6c30 to your computer and use it in GitHub Desktop.
A flutter widget that gives the given child a configurable gradient border (used and explained on https://www.flutterclutter.dev/flutter/tutorials/how-to-add-a-border-to-a-widget/2020/587/)
import 'package:flutter/material.dart';
class GradientBorderContainer extends StatelessWidget {
GradientBorderContainer({
@required gradient,
@required this.child,
@required this.onPressed,
this.strokeWidth = 4,
this.borderRadius = 64,
this.padding = 16,
this.outlineWidth = 1,
splashColor,
}) :
this.painter = _GradientPainter(
gradient: gradient, strokeWidth: strokeWidth, borderRadius: borderRadius, outlineWidth: outlineWidth
),
this.splashColor = splashColor ?? gradient.colors.first;
final _GradientPainter painter;
final Widget child;
final VoidCallback onPressed;
final double outlineWidth;
final double strokeWidth;
final double borderRadius;
final double padding;
final Color splashColor;
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: painter,
child: InkWell(
highlightColor: Colors.transparent,
splashColor: splashColor,
borderRadius: BorderRadius.circular(borderRadius),
onTap: onPressed,
child: Container(
padding: EdgeInsets.all(padding + strokeWidth + outlineWidth),
child: IntrinsicWidth(
child: child
)
),
),
);
}
}
class _GradientPainter extends CustomPainter {
_GradientPainter({this.gradient, this.strokeWidth, this.borderRadius, this.outlineWidth});
final Gradient gradient;
final double strokeWidth;
final double borderRadius;
final double outlineWidth;
final Paint paintObject = Paint();
@override
void paint(Canvas canvas, Size size) {
if (outlineWidth > 0) {
_paintOutline(outlineWidth, size, canvas);
}
Rect innerRect = Rect.fromLTRB(
strokeWidth, strokeWidth, size.width - strokeWidth, size.height - strokeWidth
);
RRect innerRoundedRect = RRect.fromRectAndRadius(innerRect, Radius.circular(borderRadius));
Rect outerRect = Offset.zero & size;
RRect outerRoundedRect = RRect.fromRectAndRadius(outerRect, Radius.circular(borderRadius));
paintObject.shader = gradient.createShader(outerRect);
Path borderPath = _calculateBorderPath(outerRoundedRect, innerRoundedRect);
canvas.drawPath(borderPath, paintObject);
}
void _paintOutline(double outlineWidth, Size size, Canvas canvas) {
Paint paint = Paint();
Rect innerRectB = Rect.fromLTRB(
strokeWidth + outlineWidth,
strokeWidth + outlineWidth,
size.width - strokeWidth - outlineWidth,
size.height - strokeWidth - outlineWidth
);
RRect innerRRectB = RRect.fromRectAndRadius(innerRectB, Radius.circular(borderRadius - outlineWidth));
Rect outerRectB = Rect.fromLTRB(-outlineWidth, -outlineWidth, size.width + outlineWidth, size.height + outlineWidth);
RRect outerRRectB = RRect.fromRectAndRadius(outerRectB, Radius.circular(borderRadius + outlineWidth));
Path borderBorderPath = _calculateBorderPath(outerRRectB, innerRRectB);
paint.color = Colors.black;
canvas.drawPath(borderBorderPath, paint);
}
Path _calculateBorderPath(RRect outerRRect, RRect innerRRect) {
Path outerRectPath = Path()..addRRect(outerRRect);
Path innerRectPath = Path()..addRRect(innerRRect);
return Path.combine(PathOperation.difference, outerRectPath, innerRectPath);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
class PartialBorderContainer extends StatelessWidget {
PartialBorderContainer({
@required gradient,
@required this.child,
@required this.onPressed,
this.strokeWidth = 4,
this.padding = 16,
splashColor,
}) :
this.painter = PartialPainter(
strokeWidth: strokeWidth, gradient: gradient
),
this.splashColor = splashColor ?? gradient.colors.first;
final PartialPainter painter;
final Widget child;
final VoidCallback onPressed;
final double strokeWidth;
final double padding;
final Color splashColor;
@override
Widget build(BuildContext context) {
return CustomPaint(
painter: painter,
child: InkWell(
highlightColor: Colors.transparent,
splashColor: splashColor,
onTap: onPressed,
child: Container(
padding: EdgeInsets.all(padding + strokeWidth),
child: IntrinsicWidth(
child: child
)
),
),
);
}
}
class PartialPainter extends CustomPainter {
PartialPainter({this.strokeWidth, this.gradient});
final Paint paintObject = Paint();
final double strokeWidth;
final Gradient gradient;
@override
void paint(Canvas canvas, Size size) {
Rect topLeftTop = Rect.fromLTRB(0, 0, size.height / 4, strokeWidth);
Rect topLeftLeft = Rect.fromLTRB(0, 0, strokeWidth, size.height / 4);
Rect bottomRightBottom = Rect.fromLTRB(size.width - size.height / 4, size.height - strokeWidth, size.width, size.height);
Rect bottomRightRight = Rect.fromLTRB(size.width - strokeWidth, size.height * 3 / 4, size.width, size.height);
paintObject.shader = gradient.createShader(Offset.zero & size);
Path topLeftPath = Path()
..addRect(topLeftTop)
..addRect(topLeftLeft);
Path bottomRightPath = Path()
..addRect(bottomRightBottom)
..addRect(bottomRightRight);
Path finalPath = Path.combine(PathOperation.union, topLeftPath, bottomRightPath);
canvas.drawPath(finalPath, paintObject);
}
@override
bool shouldRepaint(CustomPainter oldDelegate) => true;
}
import 'gradient_border_container.dart';
import 'partial_border_container.dart';
class Borders extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
SizedBox(width: 0, height: 16),
_getButtonWithGradientRect(),
SizedBox(width: 0, height: 16),
_getButtonWithGradientRRect(),
SizedBox(width: 0, height: 16),
_getDangerButton(),
SizedBox(width: 0, height: 16),
_getDangerButton2(),
SizedBox(width: 0, height: 16),
_getButtonWithPartial()
],
),
)
),
);
}
GradientBorderButtonFinal _getButtonWithGradientRect() {
return GradientBorderButtonFinal(
strokeWidth: 4,
radius: 0,
padding: 20,
gradient:
LinearGradient(colors: [Colors.redAccent, Colors.orangeAccent]),
child: Text('GRADIENT', style: TextStyle(fontSize: 32)),
onPressed: () {},
);
}
GradientBorderButtonFinal _getButtonWithGradientRRect() {
return GradientBorderButtonFinal(
strokeWidth: 4,
radius: 54,
padding: 20,
gradient:
LinearGradient(colors: [Colors.green, Colors.blue]),
child: Text('ROUNDED', style: TextStyle(fontSize: 32)),
onPressed: () {},
);
}
GradientBorderButtonFinal _getDangerButton() {
return GradientBorderButtonFinal(
strokeWidth: 16,
radius: 16,
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment(-0.2, -0.4),
stops: [0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1],
colors: [
Colors.yellow,
Colors.yellow,
Colors.black,
Colors.black,
Colors.yellow,
Colors.yellow,
Colors.black,
Colors.black,
],
tileMode: TileMode.repeated,
),
child: Text('DANGER!',
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold)),
onPressed: () {},
);
}
GradientBorderButtonFinal _getDangerButton2() {
return GradientBorderButtonFinal(
strokeWidth: 16,
radius: 16,
outlineWidth: 1,
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment(-0.2, -0.4),
stops: [0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1],
colors: [
Colors.pinkAccent,
Colors.pinkAccent,
Colors.white,
Colors.white,
Colors.pinkAccent,
Colors.pinkAccent,
Colors.white,
Colors.white,
],
tileMode: TileMode.repeated,
),
child: Text('TRAIN CROSSING!',
style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold)),
onPressed: () {},
);
}
Widget _getButtonWithPartial() {
return PartialBorderContainer(
strokeWidth: 4,
padding: 20,
gradient:
LinearGradient(colors: [Colors.green, Colors.blue]),
child: Text('PARTIAL BORDER', style: TextStyle(fontSize: 32)),
onPressed: () {},
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment