I would like to operate some perspective on a ClipPath overlay with Flutter.
I reproduced inverted Clip Oval from Flutter: inverted ClipOval which works fine.
Then i would like to operate a perspective on this overlay:
For now i use a Transform widget but the "grey background" gets also rotated.I would like the background to expand on all screen left.
I think i should rotate only in InvertedRectClipper but i can't find a way to do something similar as alignment: FractionalOffset.center which tell where is the origin to rotate on.
Anyone have a tips for this ?
Actual screenshot :
Background is not entirely grey
Full code to test :
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: SafeArea(
child: Stack(
children: <Widget>[
Container(
color: Colors.white,
),
Transform(
alignment: FractionalOffset.center,
transform: Matrix4.identity()
..setEntry(3, 2, 0.0005) // perspective
..rotateX(-0.9),
child: ClipPath(
clipper: InvertedRectClipper(),
child: Container(
color: Color.fromRGBO(0, 0, 0, 0.5),
),
),
)
],
),
),
));
}
class InvertedRectClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
return Path()
..addRect(Rect.fromCenter(
center: Offset(size.width / 2, size.height / 2),
width: size.width / 2,
height: size.height / 2))
..addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height))
..fillType = PathFillType.evenOdd;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}
The key is to only transform the path you need to transform, not the whole widget.
Here is a sample :
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: SafeArea(
child: Stack(
children: <Widget>[
Container(
color: Colors.blue,
),
ClipPath(
clipper: InvertedRectClipper(),
child: Container(
color: Colors.black.withOpacity(0.5),
),
)
],
),
),
));
}
class InvertedRectClipper extends CustomClipper<Path> {
#override
Path getClip(Size size) {
// First rectangle, to be transformed
var path = Path()
..fillType = PathFillType.evenOdd
..addRect(Rect.fromCenter(
center: Offset(size.width / 2, size.height / 2),
width: size.width / 2,
height: size.height / 2));
// Transform matrix
final matrix = Matrix4.identity()
..setEntry(3, 2, 0.0005)
..rotateX(-0.9);
path = path.transform(matrix.storage);
// Outer rectangle, straight
path.addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height));
// Return path
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}
Here is the result
You'll 'just' have to adapt the transformation to your convenance.
** EDIT **
If you need the transformation to be centered relative to your rectangle, an easy way is to draw the rectangle centered on the origin, apply the transformation, then translate it to be centered to the screen :
Path getClip(Size size) {
// First rectangle, to be transformed, centered at the origin
var path = Path()
..fillType = PathFillType.evenOdd
..addRect(Rect.fromCenter(
center: Offset(0, 0),
width: size.width / 2,
height: size.height / 2));
// Rotation X
final matrix = Matrix4.identity()
..setEntry(3, 2, 0.001)
..rotateX(-0.9);
path = path.transform(matrix.storage);
// Translation to center back the rectangle
path = path.transform(Matrix4.translationValues(size.width / 2, size.height / 2, 0).storage);
// Outer rectangle, straight
path.addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height));
// Return path
return path;
}
Related
I want to design this appbar :
and my code in main.dart is :
#override
Widget build(BuildContext context) {
return Column(
children: [
ClipPath(
clipper: WaveClip(),
child: Container(
height: 200,
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF7CB342), Color(0xFFDCEDC8)],
),
),
child: childWidget),
),
],
);
and the WaveClip is:
class WaveClip extends CustomClipper<Path> {
#override
Path getClip(Size size) {
var path = new Path();
path.lineTo(0, 0); //start path with this if you are making at bottom
path.lineTo(0, size.height); //start path with this if you are making at bottom
var controlpoint = Offset(size.width / 5 , size.height - 100);
var endpoint = Offset(size.width / 3 , size.height +50);
path.quadraticBezierTo(
controlpoint.dx, controlpoint.dy, endpoint.dx, endpoint.dy);
var controlpoint2 = Offset(size.width /2 , size.height +90 );
var endpoint2 = Offset(size.width /4 , size.height -50);
path.quadraticBezierTo(
controlpoint2.dx, controlpoint2.dy, endpoint2.dx, endpoint2.dy);
path.lineTo(size.width, size.height);
path.lineTo(size.width, 0.0);
path.close();
return path;
}
#override
bool shouldReclip(CustomClipper<Path> oldClipper) => false;
}
but i cant handle it and my code does not works true, how can I fix it?
Should i use path.cubicto to design it?
It's better to use custompainter instead of customclipper, and there is a website that automatically generates custompainter code base on your drawing
https://shapemaker.web.app
And this is the guide to use this auto shape maker:
https://youtu.be/AnKgtKxRLX4
I hope this help u to solve your problem
I wanted to create the curved container like widget in the picture below
I used the Custom clipper class and created a similar one which is shown below
Below is the WaveClipper class I used to create the curved widget
class WaveClipper extends CustomClipper<Path> {
#override
getClip(Size size) {
var path = new Path();
path.lineTo(0, size.height / 5.25);
var firstControlPoint = Offset(size.width / 120, size.height / 2);
var firstEndPoint = Offset(size.width / 1.5, size.height / 2.5 );
var thirdControlPoint = Offset(size.width/1.025, size.height / 2.8 );
var thirdEndPoint = Offset(size.width, size.height / 1.8 );
path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy, firstEndPoint.dx,
firstEndPoint.dy);
path.quadraticBezierTo(thirdControlPoint.dx, thirdControlPoint.dy, thirdEndPoint.dx,
thirdEndPoint.dy);
path.lineTo(size.width, size.height/3 );
path.lineTo(size.width, 0);
path.close();
return path;
}
#override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return true;
}
}
Please help me to achieve the desired output !!!
By wrapping the ClipPath widget with the container you can create your design see below code.
Container(
decoration: BoxDecoration(
border: Border.all(width: 2, color: Colors.blue),
color: Colors.blue),
child: ClipPath(
child: Container(
height: 200,
width: 200,
color: Colors.white,
),
clipper: WaveClipper()),
),
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 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(...),
)
I have a use case in which I've to draw a rectangle using the 4 coordinates. I don't want to use drawLine() as later I need a GestureDetector on the rectangle. How can I draw using drawRect()?
canvas.drawLine(
new Offset(rectLeft, rectBottom - lineWidth / 2),
new Offset(rectRight, rectBottom - lineWidth / 2),
rectPaint
); // bottom
canvas.drawLine(
new Offset(rectLeft, rectTop + lineWidth / 2),
new Offset(rectRight, rectTop + lineWidth / 2),
rectPaint
); // top
canvas.drawLine(
new Offset(rectLeft + lineWidth / 2, rectBottom),
new Offset(rectLeft + lineWidth / 2, rectTop),
rectPaint
); //left
canvas.drawLine(
new Offset(rectRight - lineWidth / 2, rectBottom),
new Offset(rectRight - lineWidth / 2, rectTop),
rectPaint
); //right
You need to create a CustomPainter
class YourRect extends CustomPainter {
#override
void paint(Canvas canvas, Size size) {
canvas.drawRect(
new Rect.fromLTRB(0.0, 0.0, 50.0, 50.0),
new Paint()..color = new Color(0xFF0099FF),
);
}
#override
bool shouldRepaint(YourRect oldDelegate) {
return false;
}
}
then use it like this:
new GestureDetector(
onTap: () {
debugPrint("hello");
},
child: Container(
width: 50,
height: 50,
child: CustomPaint(
painter: (YourdrawRect()),
),
),
),
You can simply use create a polygon and use drawPath() function of the canvas
canvas.drawPath(
Path()..addPolygon([
Offset(0, 0),
Offset(10, 0),
Offset(10, 10),
Offset(0, 10),
], true),
paint);