I need to achieve this in flutter:
I tried making a Row and setting a background to the Text to handle the ractangle, then a ClipPath to make the triangle. The problem with this is that the edge is not precise (you can see a thin line between the rectangle and the triangle in the image below), also the ClipPath has fixed size so if I want to change the text size at some point I'll have to fine-tune the triangle again.
here's my code:
class TriangleClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
final path = Path();
path.lineTo(0.0, size.height);
path.lineTo(size.width / 3, size.height);
path.close();
return path;
}
#override
bool shouldReclip(TriangleClipper oldClipper) => false;
}
Row(
children: [
Container(
color: Colors.orange,
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Text(
"a label",
),
),
),
ClipPath(
clipper: TriangleClipper(),
child: Container(
color: Colors.orange,
height: 23,
width: 20,
),
)
],
)
I though using double.infinity instead of fixed size in ClipPath's child would solve it but doing that makes the triangle disappear (probably it becomes too big that I can't see it or goes behind something).
What is the correct way to approache this problem? I guess I could draw the trapezoid using a ClipPath, but how to make it so the width of it adapts to the lenght of the Text?
One way to achieve this is like so.
class TriangleClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
final w = size.width;
final h = size.height;
final triangleWidth = 10;
final path = Path();
path.lineTo(0, 0);
path.lineTo(0, h);
path.lineTo(w, h);
path.lineTo(w - triangleWidth, 0);
path.close();
return path;
}
#override
bool shouldReclip(TriangleClipper oldClipper) => false;
}
and use it like so.
ClipPath(
clipper: TriangleClipper(),
child: Container(
padding: EdgeInsets.fromLTRB(4, 4, 14, 4), // 4 + triangleWidth for right padding
color: Colors.orange,
child: Text('A label'),
),
),
You can try the following code:
import 'dart:ui' as ui;
child: CustomPaint(
size: Size(WIDTH,(WIDTH*0.625).toDouble()), //You can Replace [WIDTH] with your desired width for Custom Paint and height will be calculated automatically
painter: RPSCustomPainter(),
),
class RPSCustomPainter extends CustomPainter{
#override
void paint(Canvas canvas, Size size) {
Paint paint_0 = new Paint()
..color = Color.fromARGB(255, 33, 150, 243)
..style = PaintingStyle.fill
..strokeWidth = 1;
paint_0.shader = ui.Gradient.linear(Offset(size.width*0.29,size.height*0.28),Offset(size.width*0.29,size.height*0.28),[Color(0xff7c1010),Color(0xffffffff)],[0.00,1.00]);
Path path_0 = Path();
path_0.moveTo(size.width*0.2937500,size.height*0.2780000);
canvas.drawPath(path_0, paint_0);
Paint paint_1 = new Paint()
..color = Color.fromARGB(255, 33, 150, 243)
..style = PaintingStyle.fill
..strokeWidth = 1;
Path path_1 = Path();
path_1.moveTo(size.width*0.3750000,size.height*0.4000000);
path_1.lineTo(size.width*0.5000000,size.height*0.4000000);
path_1.lineTo(size.width*0.5375000,size.height*0.5000000);
path_1.lineTo(size.width*0.3750000,size.height*0.5000000);
path_1.lineTo(size.width*0.3750000,size.height*0.4000000);
path_1.close();
canvas.drawPath(path_1, paint_1);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return true;
}
}
You can try it by yourself: https://shapemaker.web.app/#/
Related
I made a custom shape of a crooked square for music artwork images, now I'm trying to make the image fill it completely whilst not leaking out of it.
I tried using a stack, but it is bugging the painted square and the image still would leak and not fill it completely.
SizedBox(
width: 50,
height: 50,
child: CustomPaint(
painter: ImperfectRectangleBorder(strokeColor: Colors.white),
child: QueryArtworkWidget(//from on_audio_query package
artworkBorder: BorderRadius.zero,
artworkFit: BoxFit.fill,
id: item.data![index].id,
type: ArtworkType.AUDIO,
),
),
),
class ImperfectRectangleBorder extends CustomPainter {
final Color strokeColor;
ImperfectRectangleBorder({this.strokeColor = Colors.white});
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()
..color = strokeColor
..strokeWidth = 4.0
..style = PaintingStyle.stroke;
var path = Path();
path.moveTo(0, -5);
path.lineTo(size.width * (0.2), size.height + 10);
path.lineTo(size.width, size.height - 5);
path.lineTo(size.height * (0.8), -1);
path.close();
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(covariant CustomPainter oldDelegate) {
return false;
}
}
I'm trying to handle taps for three painted "quartercirlces" using the CustomPaint widget. I've tried adding GestureDetectors around the QuarterCirclePainter class. Even tried using using a GestureRecognizer for the TextSpan without any success. Tried wrapping it in containers wrapped in gestureDetectors. Looked at similar posts about adding GestureDetectors around the CustomPaint, but none of them seems to work in this case.
How can this be achieved?
class CategoryCircle extends StatelessWidget {
final Color color;
final double startAngle;
final String category;
final Offset textOffset;
CategoryCircle({this.color, this.startAngle, this.category, this.textOffset});
Widget build(BuildContext context) {
FocusScope.of(context).nextFocus();
return FractionallySizedBox(
widthFactor: 1,
heightFactor: 1,
child: Center(
child: CustomPaint(
painter: QuarterCirclePainter(
context: context,
color: color,
startAngle: startAngle,
sweepAngle: math.pi * 2 / 3,
text: category,
textOffset: textOffset)),
),
);
}
}
class QuarterCirclePainter extends CustomPainter {
final BuildContext context;
final Color color;
final double startAngle;
final double sweepAngle;
final String text;
final Offset textOffset;
const QuarterCirclePainter(
{this.context,
this.color,
this.startAngle,
this.sweepAngle,
this.text,
this.textOffset});
#override
void paint(Canvas canvas, Size size) {
Offset center = Offset(size.width / 2, size.height / 2);
Rect rect = Rect.fromCircle(center: center, radius: 130);
Path path = Path()
// set the "current point"
..moveTo(center.dx, center.dy)
..arcTo(rect, startAngle, (math.pi * 2) / 3, false);
canvas.drawPath(path, Paint()..color = color);
TextSpan span = new TextSpan(
style: GoogleFonts.overpass(
textStyle: TextStyle(fontSize: 22, fontWeight: FontWeight.bold)),
text: text);
TextPainter tp = new TextPainter(
text: span,
textAlign: TextAlign.center,
textDirection: TextDirection.ltr);
tp.layout();
tp.paint(canvas, textOffset);
}
#override
bool shouldRepaint(QuarterCirclePainter oldDelegate) {
return false;
}
}
I made a CustomClipper with what you did in the QuarterCirclePainter, it looks like this:
class QuaterCircleClipper extends CustomClipper<Path>{
double startAngle, sweepAngle;
QuaterCircleClipper({#required this.startAngle, #required this.sweepAngle});
#override
Path getClip(Size size){
Offset center = Offset(size.width / 2, size.height / 2);
Rect rect = Rect.fromCircle(center: center, radius: 130);
Path path = Path()
// set the "current point"
..moveTo(center.dx, center.dy)
..arcTo(rect, startAngle, (math.pi * 2) / 3, false);
return path;
}
#override
bool shouldReclip(oldCliper) => false;
}
And used a ClipPath with the above CustomClipper as clipper to wrap a GestureDetector which has a blue Container as its child
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: AspectRatio(
aspectRatio: 1,
child: ClipPath(
clipper: QuaterCircleClipper(startAngle: 0, sweepAngle: 120),
child: GestureDetector(
onTap: (){showDialog(context: context, builder: (_) => AlertDialog(content: Text("Tap Detected"),));},
child: Container(
color: Colors.blueAccent,
)
),
),
)
),
I have TextField i need to change the border Corners Cut, I have try to do with wrap TextField on Container and apply BeveledRectangleBorder but is not proper.
Please suggest me how can do this.
Update
After searching a little i've found the source code of that design you shared with me. They have build a custom input border (CutCornersBorder) for that particular style. You can use that file and add that to your theme to get that look of your TextField without adding any extra code.
CutCornersBorder
return MaterialApp(
home: HomePage(),
theme: ThemeData(
inputDecorationTheme: InputDecorationTheme(border: CutCornersBorder()),
),
);
Full Preview
OLD
You can do it by creating your own custom painter implementation.
FlatCorneredBackgroundPainter
class FlatCorneredBackgroundPainter extends CustomPainter {
double radius, strokeWidth;
Color strokeColor;
FlatCorneredBackgroundPainter(
{this.radius = 10, this.strokeWidth = 4, this.strokeColor = Colors.blue});
#override
void paint(Canvas canvas, Size size) {
double w = size.width;
double h = size.height;
Paint paint = Paint()
..style = PaintingStyle.stroke
..strokeWidth = strokeWidth
..color = strokeColor;
Path path = Path()
..addPolygon([
Offset(radius, 0),
Offset(w - radius, 0),
Offset(w, radius),
Offset(w, h - radius),
Offset(w - radius, h),
Offset(radius, h),
Offset(0, h - radius),
Offset(0, radius),
], true);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
}
Usage
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CustomPaint(
painter: FlatCorneredBackgroundPainter(radius: 10.0, strokeColor: Colors.red, strokeWidth: 2),
child: Container(
width: 300,
height: 70,
padding: const EdgeInsets.all(8.0),
child: TextField(),
),
),
),
);
}
}
I want to build an application where for one of my designs I would like to populate shapes with dynamic data. These will be custom shapes where I have two different shapes and they alternate one below the other. So I have a left shape and then the next one will be a right shape and so on. Is is possible to create this in Flutter and how would I do it?
Here is one way of doing it. I have simplify the shape with my custom triangle shape created with CustomPainter so you will have to modify it to your needs.
ListView(
children: <Widget>[
OverflowTitle(color: Colors.green),
OverflowTitle(color: Colors.blue),
OverflowTitle(color: Colors.red)
],
);
and custom overflow title
class OverflowTitle extends StatelessWidget {
final Color color;
const OverflowTitle({this.color});
#override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
height: 50,
child: OverflowBox(
alignment: Alignment.bottomCenter,
minHeight: 50,
maxHeight: 70,
child: Container(
height: 60,
child: CustomPaint(
painter: TrianglePainter(
strokeColor: color,
),
child: Text(
'NO1',
textAlign: TextAlign.center,
),
),
),
),
);
}
}
Here is output
Let me know if you need more help with it...
UPDATE
here is my custom triangle painter
import 'package:flutter/material.dart';
enum Direction { Up, Down, Left, Right }
class TrianglePainter extends CustomPainter {
final Color strokeColor;
final Direction direction;
TrianglePainter({this.strokeColor = Colors.white, this.direction});
#override
void paint(Canvas canvas, Size size) {
Paint paint = Paint()
..color = strokeColor
..style = PaintingStyle.fill;
canvas.drawPath(getTrianglePath(size.width, size.height), paint);
}
Path getTrianglePath(double x, double y) {
if (direction == Direction.Right) {
return Path()
..moveTo(0, y)
..lineTo(x, y / 2)
..lineTo(0, 0)
..lineTo(0, y);
} else if (direction == Direction.Left) {
return Path()
..moveTo(x, 0)
..lineTo(0, y / 2)
..lineTo(x, y)
..lineTo(x, 0);
} else if (direction == Direction.Down) {
return Path()
..moveTo(0, 0)
..lineTo(x / 2, y)
..lineTo(x, 0)
..lineTo(0, 0);
} else {
return Path()
..moveTo(0, y)
..lineTo(x / 2, 0)
..lineTo(x, y)
..lineTo(0, y);
}
}
#override
bool shouldRepaint(TrianglePainter oldDelegate) {
return oldDelegate.strokeColor != strokeColor;
}
}
I want to design a widget of shape of a chat bubble where one corner is pinned and its height should adjust to the lines of the text? For now I'm using ClipRRect widget with some borderRadius. But I want one corner pinned. Any suggestions ?
UPDATE
I know this can be done using a stack but I'm looking for a better solution since I have to use it many times in a single view and using many stacks might affect the performs. ( correct me here if I'm wrong )
For someone who want this get done with library. You can add bubble: ^1.1.9+1 (Take latest) package from pub.dev and wrap your message with Bubble.
Bubble(
style: right ? styleMe : styleSomebody,
//Your message content child here...
)
Here right is boolean which tells the bubble is at right or left, Write your logic for that and add the style properties styleMe and styleSomebody inside your widget as shown below. Change style according to your theme.
double pixelRatio = MediaQuery.of(context).devicePixelRatio;
double px = 1 / pixelRatio;
BubbleStyle styleSomebody = BubbleStyle(
nip: BubbleNip.leftTop,
color: Colors.white,
elevation: 1 * px,
margin: BubbleEdges.only(top: 8.0, right: 50.0),
alignment: Alignment.topLeft,
);
BubbleStyle styleMe = BubbleStyle(
nip: BubbleNip.rightTop,
color: Colors.grey,
elevation: 1 * px,
margin: BubbleEdges.only(top: 8.0, left: 50.0),
alignment: Alignment.topRight,
);
Hi im also searching for a chat bubble shaped widget finally i made one.
I have made this in custom Painter and i'm not good at it.
import 'package:flutter/material.dart';
class ChatBubble extends CustomPainter {
final Color color;
final Alignment alignment;
ChatBubble({
#required this.color,
this.alignment,
});
var _radius = 10.0;
var _x = 10.0;
#override
void paint(Canvas canvas, Size size) {
if (alignment == Alignment.topRight) {
canvas.drawRRect(
RRect.fromLTRBAndCorners(
0,
0,
size.width - 8,
size.height,
bottomLeft: Radius.circular(_radius),
topRight: Radius.circular(_radius),
topLeft: Radius.circular(_radius),
),
Paint()
..color = this.color
..style = PaintingStyle.fill);
var path = new Path();
path.moveTo(size.width - _x, size.height - 20);
path.lineTo(size.width - _x, size.height);
path.lineTo(size.width, size.height);
canvas.clipPath(path);
canvas.drawRRect(
RRect.fromLTRBAndCorners(
size.width - _x,
0.0,
size.width,
size.height,
topRight: Radius.circular(_radius),
),
Paint()
..color = this.color
..style = PaintingStyle.fill);
} else {
canvas.drawRRect(
RRect.fromLTRBAndCorners(
_x,
0,
size.width,
size.height,
bottomRight: Radius.circular(_radius),
topRight: Radius.circular(_radius),
topLeft: Radius.circular(_radius),
),
Paint()
..color = this.color
..style = PaintingStyle.fill);
var path = new Path();
path.moveTo(0, size.height);
path.lineTo(_x, size.height);
path.lineTo(_x, size.height-20);
canvas.clipPath(path);
canvas.drawRRect(
RRect.fromLTRBAndCorners(
0,
0.0,
_x,
size.height,
topRight: Radius.circular(_radius),
),
Paint()
..color = this.color
..style = PaintingStyle.fill);
}
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
copy and paste the above code and paste in your project
Align(
alignment: alignment, //Change this to Alignment.topRight or Alignment.topLeft
child: CustomPaint(
painter: ChatBubble(color: Colors.blue, alignment: alignment),
child: Container(
margin: EdgeInsets.all(10),
child: Stack(
children: <Widget>[
TextView("Hello World"),
],
),
),
),
)
paste this code where you have to show chatBubble Widget.
And i have also upload this code in bitbucket ChatBubble Widget Please be free if you have anything to contribute.
Sorry I am not able to show you the code for it but I can present an idea that might work if you implement it correctly. Suppose the Widget you made with ClipRect is called MyChatBubbleRect. Now, make another widget that draws a triangle using CustomPainter, let's call it MyChatBubbleTriangle, of course fill it with same color as the chat bubble but you can use a different color initially for debugging. Now that we have two widgets we can stack 'em together on top of each other and using Positioned widget over the MyChatBubbleTriangle. Something like this:
Stack(
children : [
MyChatBubbleRect(), // Maybe decrease the width a bit
Positioned(
top: 0,
right: 0,
child: MyChatBubbleTriangle()
)
]
)
This is just an idea I think you can pursue. Sorry couldn't provide the proper source code.
Chat Body
DecoratedBox(
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(8.0),
),
child: Text("your message goes here"),
);
Make a custom Triangle
class ChatBubbleTriangle extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
var paint = Paint()..color = Colors.blue;
var path = Path();
path.lineTo(-10, 0);
path.lineTo(0, 10);
path.lineTo(10, 0);
canvas.drawPath(path, paint);
}
#override
bool shouldRepaint(CustomPainter oldDelegate) {
return true;
}
}
Use both of them within a stack wrapping ChatBubbleTriangle with Positioned() widget