Custom Shape for Container - flutter

Idea is to make a custom shaped container by giving it ShapeDecoration().
I used this thread as a reference.
Eventialy I achieved the shape I wanted, but once I wrap this custom shaped container with margin\padding\SizedBoxes around it - it's content is being pushed out of the bounds of the container. Check out screenshots.
Normaly it should be smth like this:
but with paddings between elements.
So the problem is that my customized container behavers realy wierd once it has any margin\padding arround it.
return Padding(
padding: const EdgeInsets.symmetric(vertical: 40),
child: Container(
padding: const EdgeInsets.all(8),
height: 88,
alignment: Alignment.center,
decoration: ShapeDecoration(
shape: UnitListItemBorderShape(
color: _isCurrent ? theme.listItemBorderColor : theme.listItemBackgroundColor,
),
color: theme.listItemBackgroundColor,
),
child: Stack(
children: [
//custom container's ineer content
] ,
),
);
UnitListItemBorderShape class:
class UnitListItemBorderShape extends ShapeBorder {
const UnitListItemBorderShape({color}) : _color = color;
final Color _color;
#override
EdgeInsetsGeometry get dimensions => const EdgeInsets.all(0);
#override
Path getInnerPath(Rect rect, {TextDirection textDirection}) => null;
#override
Path getOuterPath(Rect rect, {TextDirection textDirection}) => UnitListItemShape.getShape(
rect.width,
rect.height,
const Radius.circular(16),
const Radius.circular(20),
);
#override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
final Paint borderPaint = Paint()
..color = _color
..style = PaintingStyle.stroke
..strokeWidth = 2;
canvas.drawPath(getOuterPath(rect), borderPaint);
}
#override
ShapeBorder scale(double t) => null;
}
UnitListItemShape class:
class UnitListItemShape {
static Path getShape(double width, double height, Radius borderRadius, Radius circleRadius) {
final double rightOffset = circleRadius.x;
final Rect rect = Rect.fromCenter(center: Offset(width - rightOffset - 4, height / 2), width: 48, height: 48);
return Path()
..moveTo(borderRadius.x, 0)
..lineTo(width - borderRadius.x - rightOffset, 0)
..arcToPoint(Offset(width - rightOffset, borderRadius.x), radius: borderRadius)
..lineTo(width - rightOffset, (height / 2) - rightOffset - 4)
// ..addRect(rect)
..arcTo(rect, -70 * math.pi / 180, 150 * math.pi / 180, false)
..lineTo(width - rightOffset, height - borderRadius.x)
..arcToPoint(Offset(width - borderRadius.x - rightOffset, height), radius: borderRadius)
..lineTo(borderRadius.x, height)
..arcToPoint(Offset(0, height - borderRadius.x), radius: borderRadius)
..lineTo(0, borderRadius.x)
..arcToPoint(Offset(borderRadius.x, 0), radius: borderRadius)
..fillType = PathFillType.evenOdd
..close();
}
}

Related

Flutter Segmented Ring Around Number

In Flutter I try to make a segmented ring (or radial gauge) around a number to indicate its value. Like here
The example of in the link is already a solution itself, however, I read syncfusion widgets are not free. (I am using it commercially)
Any alternatives or ideas how to set this into practice?
You can use CustomPainter to achieve the same,
class RingPainter extends CustomPainter {
const RingPainter({
required this.percentage,
this.startColor,
this.endColor,
this.width,
}) : assert(percentage >= 0 && percentage <= 100,
"Percentage must be in the range 0-100!");
final double percentage;
final Color? startColor;
final Color? endColor;
final double? width;
double get progress => percentage / 100;
#override
void paint(Canvas canvas, Size size) {
var angle = math.pi / 180 * 230;
canvas.rotateAroundCenter(size, angle);
canvas.drawRing(
size,
1,
startColor: Colors.black12,
endColor: Colors.black12,
width: width,
);
canvas.drawRing(
size,
progress,
startColor: startColor,
endColor: endColor,
width: width,
);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
extension on Canvas {
rotateAroundCenter(Size size, double angleInRadians) {
final double r =
math.sqrt(size.width * size.width + size.height * size.height) / 2;
final alpha = math.atan(size.height / size.width);
final beta = alpha + angleInRadians;
final shiftY = r * math.sin(beta);
final shiftX = r * math.cos(beta);
final translateX = size.width / 2 - shiftX;
final translateY = size.height / 2 - shiftY;
translate(translateX, translateY);
rotate(angleInRadians);
}
drawRing(
Size size,
double value, {
Color? startColor,
Color? endColor,
double? width,
}) {
final rect = Rect.fromLTWH(-15, 0.0, size.width, size.height);
final gradient = SweepGradient(
startAngle: 3 * math.pi / 2,
endAngle: 7 * math.pi / 2,
tileMode: TileMode.repeated,
colors: [
startColor ?? Colors.pink,
endColor ?? Colors.blueAccent,
],
);
final paint = Paint()
..shader = gradient.createShader(rect)
..strokeCap = StrokeCap.round
..style = PaintingStyle.stroke
..strokeWidth = width ?? 24;
final center = Offset(size.width / 2, size.height / 2);
final radius =
math.min(size.width / 2, size.height / 2) - ((width ?? 24) / 2);
const startAngle = -math.pi / 2;
final sweepAngle = 2 * math.pi * value * 0.723;
drawArc(
Rect.fromCircle(center: center, radius: radius),
startAngle,
sweepAngle,
false,
paint,
);
}
}
Use it with CustomPaint widget,
CustomPaint(
painter: RingPainter(
percentage: 70,
width: 20,
),
)
In case, you want text inside ring,
Center(
child: SizedBox(
width: 200,
height: 200,
child: Stack(
fit: StackFit.expand,
children: [
const Center(
child: Text(
"70%",
style: TextStyle(
fontSize: 48,
fontWeight: FontWeight.w900,
color: Colors.purple,
),
),
),
CustomPaint(
painter: RingPainter(
percentage: 70,
width: 20,
),
),
],
),
),
)

How to draw an Arc in the middle of CustomPainter shape

I am trying to draw the following shape using CustomPainter, I am looking for a way
to make this shape as one shape (union), so for example using Stack with Container and half circle shape is not desired.
I have managed to paint the base rectangular shape as the following image shows, is it possible to build on top of this code to draw a half a circle in the middle ?
Container(
height: 85,
color: Colors.blueGrey,
child: Stack(
children: [
CustomPaint(
size: Size(size.width, 85),
painter: MyCustomPainter(),
)
],
),
),
class MyCustomPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = Colors.red
..style = PaintingStyle.fill;
var path = Path()..moveTo(0, 20);
path.quadraticBezierTo(0, 0, 0, size.height * 0.5);
path.quadraticBezierTo(0, 0, size.width * 0.2, 0);
path.quadraticBezierTo(size.width, 0, size.width * 0.8, 0);
path.quadraticBezierTo(size.width, 0, size.width, size.height * 0.5);
path.lineTo(size.width, size.height);
path.lineTo(0, size.height);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
While the question about using union operation, I am focusing on
canvas.drawPath(
Path.combine(PathOperation.union, path1, circlePath), paint);
The current paint takes full height and doesnt maintain the ration based on your desire result. I am using Rect on Path to demonstrating the union operation. Change the size/value according to your needs.
class MyCustomPainter extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = Colors.red
..style = PaintingStyle.fill;
final double base = size.height * .4;
const corner = Radius.circular(16);
final path1 = Path()
..addRRect(
RRect.fromLTRBAndCorners(0, base, size.width, size.height,
topLeft: corner, topRight: corner),
);
final center = Offset(size.width / 2, size.height / 2);
final radius = size.height / 2;
final circlePath = Path()
..addOval(Rect.fromCircle(center: center, radius: radius));
canvas.drawPath(
Path.combine(PathOperation.union, path1, circlePath), paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) => false;
}
More about CustomPaint

How can i reverse container radius to specific form

i have the simple following code
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
width: 300,
height: 100,
decoration: const BoxDecoration(
color: Colors.black,
borderRadius: BorderRadius.only(topRight: Radius.circular(30.0),topLeft: Radius.circular(30.0),)
),
),
),
);
}
the outputs looks like following
but i am trying to reverse it to be like following (sorry for the bad drawing)
How could i achieve this with simple way ? best regards :)
This can be done with a CustomPainter:
class InvertedRoundedRectanglePainter extends CustomPainter {
InvertedRoundedRectanglePainter({
required this.radius,
required this.color,
});
final double radius;
final Color color;
#override
void paint(Canvas canvas, Size size) {
final cornerSize = Size.square(radius * 2);
canvas.drawPath(
Path()
..addArc(
// top-left arc
Offset(0, -radius) & cornerSize,
// 180 degree startAngle (left of circle)
pi,
// -90 degree sweepAngle (counter-clockwise to the bottom)
-pi / 2,
)
..arcTo(
// top-right arc
Offset(size.width - cornerSize.width, -radius) & cornerSize,
// 90 degree startAngle (bottom of circle)
pi / 2,
// -90 degree sweepAngle (counter-clockwise to the right)
-pi / 2,
false,
)
// bottom right of painter
..lineTo(size.width, size.height)
// bottom left of painter
..lineTo(0, size.height),
Paint()..color = color,
);
}
#override
bool shouldRepaint(InvertedRoundedRectanglePainter oldDelegate) =>
oldDelegate.radius != radius || oldDelegate.color != color;
}
You can play with this example on DartPad: https://dartpad.dartlang.org/?id=18cfbcc696b43c7c002a5ac3c94dd520

Flutter How to create box container with arrow on top?

Hi how do i create box container with arrow on top without using any pub packages.
Like below.
You can customize ShapeBorder similar to this post. Then instead of using rect.bottomCenter for Path(), change it to rect.TopCenter to place the drawn Rect at the top.
Path()dart
..addRRect(RRect.fromRectAndRadius(rect, Radius.circular(rect.height / 2)))
..moveTo(rect.topCenter.dx - 10, rect.topCenter.dy)
..relativeLineTo(10, 20)
..relativeLineTo(20, -20)
..close();
For my case, I customized a shapeborder for flutter tooltip.
class TooltipShippingFeeBorder extends ShapeBorder{
#override
EdgeInsetsGeometry get dimensions => const EdgeInsets.only(top:20);
#override
Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
return Path()
..fillType = PathFillType.evenOdd
..addPath(getOuterPath(rect), Offset.zero);
}
#override
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
rect = Rect.fromPoints(rect.topLeft, rect.bottomRight - Offset(0, 20));
return Path()
..addRRect(RRect.fromRectAndRadius(rect, const Radius.circular(6)))
..moveTo(rect.topCenter.dx - 10, rect.topCenter.dy)
..relativeLineTo(10, -20)
..relativeLineTo(20, 20)
..close();
}
#override
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {
}
#override
ShapeBorder scale(double t) => this;
}
And use it on tooltip like this:
Tooltip(
triggerMode: TooltipTriggerMode.tap,
padding: const EdgeInsets.only(bottom: 16, left: 10, right: 10),
decoration: ShapeDecoration(
shape: TooltipShippingFeeBorder(),
color: WalletTheme.primaryColor),
message: '$shippingFeeMessage\n',
child: const Icon(
Icons.info_outline,
size: 16,
color: WalletTheme.grey2,
),
)
So in your case, you also create the same border and use ShapreDecoration for Container.
ShapeDecoration(
shape: TooltipShippingFeeBorder(),
color: WalletTheme.primaryColor)

Unable to set gradient color to my custom shape border in flutter app

I have created a custom shape using CustomBorder class, and I want to set a gradient color to my custom shape. Please see the below code that I have tried.
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.bottomLeft,
end: Alignment.topRight,
colors: [Colors.orangeAccent, Colors.orange]
)
),
child: Material(
color: Colors.deepOrangeAccent,
shape: CustomShapeBorder(),
child: IconButton(
icon: FaIcon(FontAwesomeIcons.instagram),
iconSize: 30.0,
padding: const EdgeInsets.only(top: 60.0, left: 30.0),
color: Colors.white,
),
),
)
My CustomShapeBorder() class :
class CustomShapeBorder extends ShapeBorder {
final double distanceFromWall = 12;
final double controlPointDistanceFromWall = 5;
#override
// TODO: implement dimensions
EdgeInsetsGeometry get dimensions => null;
#override
Path getInnerPath(Rect rect, {TextDirection textDirection}) {
// TODO: implement getInnerPath
return null;
}
#override
Path getOuterPath(Rect rect, {TextDirection textDirection}) {
// TODO: implement getOuterPath
return getClip(Size(260.0,180.0));
}
#override
void paint(Canvas canvas, Rect rect, {TextDirection textDirection}) {
// TODO: implement paint
}
#override
ShapeBorder scale(double t) {
// TODO: implement scale
return null;
}
Path getClip(Size size) {
Path clippedPath = new Path();
clippedPath.lineTo(0, size.height);
clippedPath.quadraticBezierTo(30, size.height + 10, size.width * 0.20, size.height - 50);
clippedPath.quadraticBezierTo(70, size.height - 120, size.width * 0.40, size.height * 0.35);
clippedPath.quadraticBezierTo(180, (size.height - (size.height* 0.6)), size.width - 40 , 32 );
clippedPath.quadraticBezierTo(250, 0, size.width, 0);
clippedPath.lineTo(size.width, 0);
clippedPath.close();
return clippedPath;
}
}
Output:(Just refer to the upper custom shape, forget the bottom one)
If I remove the color: Colors.deepOrangeAccent from child: Material then the output is :
BoxDecoration is optimized for rectangular widgets. Since the widget here that you'd like to apply gradient is similar to a blob. It's best to use ShapeDecoration instead. Also, Material should be used as a parent Widget and not a child of a Widget.
Container(
decoration: ShapeDecoration(...),
child: IconButton(...),
)