什么是CustomClipper
CustomClipper
并不是一个组件,而是一个abstract(抽象)类,使用CustomClipper
可以绘制出任何我们想要的形状。
ClipPath
裁剪路径
ClipPath
用于创建任何形状的非常自定义的小部件。
ClipPath(
clipper: DiamondCurve(),
child: Container(
width: 300,
height: 200,
color: Colors.orange,
),
),
要创建自定义裁剪,需要创建一个扩展CustomClipper<Path>
的类,并且必须覆盖两个方法。
自定义裁剪需要更新时,就会调用getClip
方法,并且此方法有一个Size
参数,它提供小部件的高度和宽度值。
当提供该类的新实例时,将调用shouldReclip方法。如果新实例的信息与旧实例不同,则该方法应返回 true,否则应返回 false。
class DiamondCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path()
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return true;
}
}
lineTo
此方法用于绘制从当前点到给定点的线段。
如上图(a)所示,路径默认从点p1(0,0)开始。现在向 p2(0, h) 添加一个新行,然后向 p3(w, h) 添加一行。
我们不需要添加一条从端点 p3 到起点 p1 的线,它会默认绘制。
@override
Path getClip(Size size) {
Path path = Path()
..lineTo(0, size.height) //p1->p2
..lineTo(size.width, size.height) //p2->p3
..close();
return path;
}
moveTo
此方法用于将路径移动到绘图中的特定点。
如上图所示,起点从(0,0)移动到p1(w/2,0)。
@override
Path getClip(Size size) {
Path path = Path()
..moveTo(size.width/2, 0) // 移到 (w/2,0)
..lineTo(0, size.width)
..lineTo(size.width, size.height)
..close();
return path;
}
我们可以再画一个三角形:
///三角形
class TriangleCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path()
..moveTo(size.width / 2, 0)
..lineTo(0, size.height)
..lineTo(size.width,size.height);
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
quadraticBezierTo
我们还可以创建二次贝塞尔曲线,运用quadraticBezierTo
。
如上图(a)所示,使用控制点c(w/2,h/2)在点p2(0,h)到p3(w,h)绘制曲线。
class TriangleBezierCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path()
..moveTo(size.width / 2, 0)
..lineTo(0, size.height)
..quadraticBezierTo(
Offset(size.width/2, size.height/2).dx,
Offset(size.width/2, size.height/2).dy,
Offset(size.width, size.height).dx,
Offset(size.width, size.height).dy,
);
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
cubicTo
讲到二次贝塞尔曲线,那就缺少不了我们的三次贝塞尔曲线,我们需要通过指定 2 个控制点和端点来创建三次曲线,如下图:
class CubicToBezierCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path()
..moveTo(size.width / 2, 0)
..lineTo(0, size.height - 50)
..cubicTo(
Offset(50, size.height - 100).dx,
Offset(50, size.height - 100).dy,
Offset(size.width - 50, size.height).dx,
Offset(size.width - 50, size.height).dy,
Offset(size.width, size.height - 50).dx,
Offset(size.width, size.height - 50).dy,
);
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
弧度到点 arcToPoint
该方法用于绘制从起点到指定点的圆弧。我们可以通过设置半径、顺时针/逆时针方向来自定义圆弧。
有椭圆形和圆形类型的半径来绘制圆弧。如上图所示,椭圆半径使用(x,y)值绘制,圆半径使用radius(R)绘制。
如上图(a)所示,路径从p1点开始。第一条弧从起点 p2 绘制到终点 p3 并且未指定半径,因此默认情况下为零,所以它看起来像一条线。第二条圆弧使用圆半径从起点 p4 绘制到终点 p5,方向为顺时针方向(方向默认为顺时针方向)。第三条圆弧使用圆半径从起点 p6 绘制到终点 p7,方向为逆时针方向。第四个圆弧使用椭圆半径从起点 p8 绘制到终点 p1。
/// 弧度到点
class ArcToPointCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
double radius = 20.0;
Path path = Path()
..moveTo(radius, 0)
..lineTo(size.width - radius, 0)
..arcToPoint(Offset(size.width, radius))
..lineTo(size.width, size.height - radius)
..arcToPoint(Offset(size.width-radius, size.height),radius: Radius.circular(radius))
..lineTo(radius, size.height)
..arcToPoint(Offset(0, size.height - radius),radius: Radius.circular(radius),clockwise: false)
..lineTo(0, radius)
..arcToPoint(Offset(radius, 0),radius: const Radius.elliptical(40, 20))
..close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
arcTo
用于通过将Rect
、起始角和扫描(结束)角指定为弧度来绘制弧。
上图是有关弧度角的基本信息。它从 0 PI(PI 值为 3.14)开始到 2 PI。
有几种绘制矩形的方法,比如从点、从圆、从 LTRB(左、上、右、下)和 LTWH(左、上、宽、高)。在上图(a)中,所有类型的圆弧都以不同的起始角度绘制。
class ArcToCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
double radius = 50.0;
Path path = Path()
..lineTo(size.width-radius, 0)
..arcTo(Rect.fromPoints(
Offset(size.width - radius,0), Offset(size.width,radius)),
1.5 * pi,
0.5 * pi,
true
)
..lineTo(size.width, size.height - radius)
..arcTo(
Rect.fromCircle(
center: Offset(size.width-radius,size.height-radius),
radius: radius
),
0,
0.5 * pi,
false
)
..lineTo(radius, size.height)
..arcTo(
Rect.fromLTRB(0, size.height - radius, radius, size.height),
0.5 * pi,
0.5 * pi,
false
)
..lineTo(0, radius)
..arcTo(
Rect.fromLTWH(0, 0, 70, 100),
1 * pi,
0.5 * pi,
false
)
..close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
Rect.fromPoints
通过两个坐标点来绘制矩形。
Rect.fromPoints(Offset a, Offset b)
需要传递两个Offset
对象:
- a代表左上角点的坐标
- b代表右下角的点的坐标
Rect.fromCircle
构造一个以给定圆为边界的矩形。
Rect.fromCircle({required Offset center, required double radius})
需要传递2个对象:
- center用来设置圆的圆心坐标
- radius用来设置圆的半径
Rect.fromLTRB
从左、上、右和下边缘构造一个矩形。
Rect.fromLTRB(left,top,right,bottom)
需要传递4个参数:
- left代表左上角顶点的x坐标
- top代表左上角顶点的y坐标
- right代表右下角顶点的x坐标
- bottom代表右下角顶点的y坐标
Rect.fromLTWH
通过左上角点的坐标和宽度、高度来构造一个矩形
Rect.fromLTWH(left,top,width,height)
需要传递4个参数:
- left用来设置左上角的x坐标
- top用来设置左上角的y坐标
- width用来设置矩形的宽
- height用来设置矩形的高
addRect
用于添加带圆角的矩形。我们可以在所有边或特定边上做一个圆角。
// 画矩形addRRect
class AddRRectCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
double radius = 10.0;
Path path = Path()
..addRRect(
RRect.fromLTRBR(
0,
0,
60,
60,
Radius.circular(radius)
)
)
..addRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(0, size.height - 50, 50, 50),
Radius.circular(radius)
),
)
..addRRect(
RRect.fromRectAndCorners(
Rect.fromCircle(
center: Offset(size.width/2,size.height/2),
radius: 30
),
topLeft: Radius.circular(radius),
bottomRight: Radius.circular(radius)
)
)
..close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
RRect.fromLTRBR
RRect.fromLTRBR(left,top,right,bottom,radius)
需要传递5个参数,前4个参数和Rect.fromLTRB
一样:
- left代表左上角顶点的x坐标
- top代表左上角顶点的y坐标
- right代表右下角顶点的x坐标
- bottom代表右下角顶点的y坐标
- radius用来设置矩形的圆角半径
RRect.fromRectAndRadius
通过绘制一个矩形再设置圆角半径来绘制圆角矩形
RRect RRect.fromRectAndRadius(Rect rect, Radius radius)
需要传递2个参数:
- rect是一个矩形对象
- radius为矩形设置圆角
RRect.fromRectAndCorners
RRect.fromRectAndCorners(
Rect rect,
{ Radius topLeft = Radius.zero,
Radius topRight = Radius.zero,
Radius bottomRight = Radius.zero,
Radius bottomLeft = Radius.zero}
)
可以传递5个参数,1个必要的,4个可选的:
- rect用来绘制一个矩形
- topLeft用来设置左上角的圆角半径
- topRight用来设置右上角的圆角半径
- bottomRight用来设置右下角角的圆角半径
- bottomLeft用来设置左下角的圆角半径
addOval
画椭圆
用于在路径中添加椭圆。与addRect
相同,此方法需要Rect
参数。
/// `addOval` 画椭圆
class AddOvalCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path()
..addOval(Rect.fromPoints(Offset(0,0), Offset(60,60)))
..addOval(Rect.fromCircle(center: Offset(size.width/2,size.height/2), radius: 20))
..addOval(Rect.fromLTWH(0, size.height - 50, 100, 50))
..close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
addPolygon
用于通过指定多个点将多边形添加到路径中。
class AddPolygonCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
var points = [
Offset(size.width/2, 0),
Offset(0, size.height/2),
Offset(size.width / 2, size.height),
Offset(size.width, size.height/2)
];
Path path = Path()
..addPolygon(points, false)
..close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
addPath
用于通过指定偏移量在主路径中添加另一个子路径。偏移量是计算坐标的其他路径的原点。
如上图(a)所示,绘制了两条路径,路径1为主路径,路径2是在路径1中添加的。路径2分别绘制偏移量(w/2),所以以( 0,0) 和所有其他点分别绘制到偏移点。
class AddPathCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path1 = Path()
..lineTo(0, size.height)
..lineTo(size.width/2, size.height)
..lineTo(0, 0);
Path path2 = Path()
..lineTo(size.width/2, size.height)
..lineTo(size.width/2, 0)
..lineTo(0, 0);
path1.addPath(path2, Offset(size.width/2, 0));
return path1;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
relativeLineTo
与 lineTo 相同,但线点是从路径的最后一个点计算的。
如图 (a) 所示,线 p1p2 是使用relativeLineTo绘制的,因此是相对于最后一个点 p1 绘制的。很简单,如果你想绘制关于原点 (0,0) 的点,那么你可以这样做,最终点p2(x,y) = (p1.x + p2.x, p1.y +p2.y)
class RelativeLineToCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path()
..moveTo(size.width/2, 0)
..relativeLineTo(50, size.height)
..lineTo(size.width, size.height)
..close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
全部源码:
import 'dart:math';
import 'package:flutter/material.dart';
class CustomClipperCurve extends StatefulWidget {
const CustomClipperCurve({Key? key}) : super(key: key);
@override
State<CustomClipperCurve> createState() => _CustomClipperCurveState();
}
class _CustomClipperCurveState extends State<CustomClipperCurve> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('CustomClipper')),
body: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(15.0),
child: Column(
children: [
ClipPath(
clipper: DiamondCurve(),
child: Container(
width: 200,
height: 100,
color: Colors.orange,
),
),
const SizedBox(height: 20,),
ClipPath(
clipper: TriangleCurve(),
child: Container(
width: 100,
height: 100,
color: Colors.red,
),
),
const SizedBox(height: 20,),
ClipPath(
clipper: QuadraticBezierCurve(),
child: Container(
width: 100,
height: 100,
color: Colors.purple,
),
),
const SizedBox(height: 20,),
ClipPath(
clipper: CubicToBezierCurve(),
child: Container(
width: 100,
height: 100,
color: Colors.green,
),
),
const SizedBox(height: 20,),
ClipPath(
clipper: ArcToPointCurve(),
child: Container(
width: 100,
height: 100,
color: Colors.pink,
),
),
const SizedBox(height: 20,),
ClipPath(
clipper: ArcToCurve(),
child: Container(
width: 200,
height: 200,
color: Colors.pink,
),
),
const SizedBox(height: 20,),
ClipPath(
clipper: AddRRectCurve(),
child: Container(
width: 200,
height: 200,
color: Colors.pink,
),
),
const SizedBox(height: 40,),
ClipPath(
clipper: AddOvalCurve(),
child: Container(
width: 200,
height: 200,
color: Colors.pink,
),
),
const SizedBox(height: 40,),
ClipPath(
clipper: AddPolygonCurve(),
child: Container(
width: 200,
height: 200,
color: Colors.pink,
),
),
const SizedBox(height: 40,),
Container(
color: Colors.grey[200],
child: ClipPath(
clipper: AddPathCurve(),
child: Container(
width: 200,
height: 200,
color: Colors.pink,
),
),
),
const SizedBox(height: 40,),
ClipPath(
clipper: RelativeLineToCurve(),
child: Container(
width: 200,
height: 200,
color: Colors.pink,
),
),
],
),
),
),
);
}
}
///三角形
class TriangleCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path()
..moveTo(size.width / 2, 0)
..lineTo(0, size.height)
..lineTo(size.width,size.height);
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
///菱形
class DiamondCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path()
..moveTo(size.width / 2, 0)
..lineTo(0, size.height / 2)
..lineTo(size.width / 2, size.height)
..lineTo(size.width, size.height / 2);
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return true;
}
}
// 二次贝塞尔曲线
class QuadraticBezierCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path()
..moveTo(size.width / 2, 0)
..lineTo(0, size.height)
..quadraticBezierTo(
Offset(size.width/2, size.height/2).dx,
Offset(size.width/2, size.height/2).dy,
Offset(size.width, size.height).dx,
Offset(size.width, size.height).dy,
);
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
/// 三次贝塞尔曲线
class CubicToBezierCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path()
..moveTo(size.width / 2, 0)
..lineTo(0, size.height - 50)
..cubicTo(
Offset(50, size.height - 100).dx,
Offset(50, size.height - 100).dy,
Offset(size.width - 50, size.height).dx,
Offset(size.width - 50, size.height).dy,
Offset(size.width, size.height - 50).dx,
Offset(size.width, size.height - 50).dy,
);
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
/// 弧度到点
class ArcToPointCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
double radius = 20.0;
Path path = Path()
..moveTo(radius, 0)
..lineTo(size.width - radius, 0)
..arcToPoint(Offset(size.width, radius))
..lineTo(size.width, size.height - radius)
..arcToPoint(Offset(size.width-radius, size.height),radius: Radius.circular(radius))
..lineTo(radius, size.height)
..arcToPoint(Offset(0, size.height - radius),radius: Radius.circular(radius),clockwise: false)
..lineTo(0, radius)
..arcToPoint(Offset(radius, 0),radius: const Radius.elliptical(40, 20))
..close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
class ArcToCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
double radius = 50.0;
Path path = Path()
..lineTo(size.width-radius, 0)
..arcTo(Rect.fromPoints(
Offset(size.width - radius,0), Offset(size.width,radius)),
1.5 * pi,
0.5 * pi,
true
)
..lineTo(size.width, size.height - radius)
..arcTo(
Rect.fromCircle(
center: Offset(size.width-radius,size.height-radius),
radius: radius
),
0,
0.5 * pi,
false
)
..lineTo(radius, size.height)
..arcTo(
Rect.fromLTRB(0, size.height - radius, radius, size.height),
0.5 * pi,
0.5 * pi,
false
)
..lineTo(0, radius)
..arcTo(
Rect.fromLTWH(0, 0, 70, 100),
1 * pi,
0.5 * pi,
false
)
..close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
// 画矩形addRRect
class AddRRectCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
double radius = 10.0;
Path path = Path()
..addRRect(
RRect.fromLTRBR(
0,
0,
60,
60,
Radius.circular(radius)
)
)
..addRRect(
RRect.fromRectAndRadius(
Rect.fromLTWH(0, size.height - 50, 50, 50),
Radius.circular(radius)
),
)
..addRRect(
RRect.fromRectAndCorners(
Rect.fromCircle(
center: Offset(size.width/2,size.height/2),
radius: 30
),
topLeft: Radius.circular(radius),
bottomRight: Radius.circular(radius)
)
)
..close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
/// `addOval` 画椭圆
class AddOvalCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path()
..addOval(Rect.fromPoints(Offset(0,0), Offset(60,60)))
..addOval(Rect.fromCircle(center: Offset(size.width/2,size.height/2), radius: 20))
..addOval(Rect.fromLTWH(0, size.height - 50, 100, 50))
..close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
/// 多边形
class AddPolygonCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
var points = [
Offset(size.width/2, 0),
Offset(0, size.height/2),
Offset(size.width / 2, size.height),
Offset(size.width, size.height/2)
];
Path path = Path()
..addPolygon(points, false)
..close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
/// 添加路径
class AddPathCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path1 = Path()
..lineTo(0, size.height)
..lineTo(size.width/2, size.height)
..lineTo(0, 0);
Path path2 = Path()
..lineTo(size.width/2, size.height)
..lineTo(size.width/2, 0)
..lineTo(0, 0);
path1.addPath(path2, Offset(size.width/2, 0));
return path1;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
class RelativeLineToCurve extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path()
..moveTo(size.width/2, 0)
..relativeLineTo(50, size.height)
..lineTo(size.width, size.height)
..close();
return path;
}
@override
bool shouldReclip(covariant CustomClipper<Path> oldClipper) {
return false;
}
}
评论(0)