Flutter предлагает различные виджеты для работы с определенным набором фигур, например, ClipRect, ClipRRect, ClipOval. Но также есть ClipPath, с помощью которого мы можем создавать любые типы фигур.
В данной статье мы сосредоточимся на том, что можно сделать, используя ClipPath и CustomClipper. Поехали!
Содержание:
- ClipPath
- lineTo
- moveTo
- quadraticBezierTo
- cubicTo
- arcToPoint
- arcTo
- addRect
- addRRect
- addOval
- addPolygon
- addPath
- relativeLineTo
ClipPath
Благодаря ClipPath мы можем создавать очень сложные и необычные фигуры. В этом нам поможет свойство clipper у ClipPath
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey,
body: Center(
child: ClipPath(
clipper: MyCustomClipper(), // <--
child: Container(
width: 200,
height: 200,
color: Colors.pink,
),
),
),
);
}
В качестве значения для clipper необходимо указать экземпляр класса, который наследуют CustomClipper<Path>
и переопределяет два метода.
class MyCustomClipper extends CustomClipper<Path> {
@override
Path getClip(Size size) {
Path path = Path();
return path;
}
@override
bool shouldReclip(CustomClipper<Path> oldClipper) => false
}
Метод getClip вызывается каждый раз, когда требуется обновить нашу описанную фигуру. В качестве параметра метод получает Size
, который содержит значения высоты и ширины виджета, переданного в сhild
у ClipPath
.
shouldReclip вызывается, когда в clipper
передается новый экземпляр класса. Если новый экземпляр отличается от старого, то метод должен возвращать true
, в противном случае – false
.
Нам нужно описать фигуру внутри CustomClipper
. Это не очень сложно, но сначала надо разобраться с основами графики.
Как показано на приведенном выше рисунке, каждая точка на графике описывается через координату (x
,y
). x
представляет собой горизонтальную ось, а y
– вертикальную ось. Построение фигуры начинается с верхнего левого угла, чья координата (0
, 0
).
Давайте рассмотрим доступные методы для построения фигур. С помощью этих методов вы можете создавать собственные фигуры.
lineTo
Данный метод используется для построения отрезка от текущей точки до заданной.
Как показано выше на рисунке (a), путь по умолчанию начинается с точки p1(0, 0)
. Теперь добавим новый отрезок к p2(0, h)
, а затем – p3(w, h)
. Нам не нужно определять линию от конечной точки p3
до начальной p1
, она будет нарисована по умолчанию.
Результат можно увидеть на рисунке (b) с треугольником розового цвета.
@override
Path getClip(Size size) {
Path path = Path()
..lineTo(0, size.height) // Добавить отрезок p1p2
..lineTo(size.width, size.height) // Добавить отрезок p2p3
..close();
return path;
}
moveTo
Этот метод нужен для перемещения точки отрисовки.
Как показано на рисунке выше, начальная точка перемещена из (0, 0)
в точку p1(w/2, 0)
.
@override
Path getClip(Size size) {
Path path = Path() // Начальная точка в (0, 0)
..moveTo(size.width/2, 0) // передвигаем точку в (width/2, 0)
..lineTo(0, size.width)
..lineTo(size.width, size.height)
..close();
return path;
}
quadraticBezierTo
Этот метод используется для построения квадратичной кривой Безье.
Источник: Wikipedia
Как показано на приведенном выше рисунке, мы можем нарисовать квадратичную кривую Безье, используя контрольную и конечную точки. P0 – это начальная точка, P1 – контрольная точка, а P2 – конечная точка.
Как показано выше на рисунке (а), кривая рисуется от точки p2(0, h)
до p3(w, h)
с использованием контрольной точки c(w/2, h/2)
.
@override
Path getClip(Size size) {
// Эта переменная определена для лучшего понимания, какое значение указать в методе quadraticBezierTo
var controlPoint = Offset(size.width / 2, size.height / 2);
var endPoint = Offset(size.width, size.height);
Path path = Path()
..moveTo(size.width / 2, 0)
..lineTo(0, size.height)
..quadraticBezierTo(
controlPoint.dx, controlPoint.dy, endPoint.dx, endPoint.dy)
..close();
return path;
}
cubicTo
Данный метод используется для построения кубической кривой путем указания 2 контрольных и конечной точек.
На рисунке выше представлена иллюстрация различных кубических кривых с разным расположением контрольных точек.
Как показано на рисунке (a), кубическая кривая рисуется между начальной точкой p2
и конечной точкой p3
с использованием контрольных точек c1
и c2
.
@override
Path getClip(Size size) {
var controlPoint1 = Offset(50, size.height - 100);
var controlPoint2 = Offset(size.width - 50, size.height);
var endPoint = Offset(size.width, size.height - 50);
Path path = Path()
..moveTo(size.width / 2, 0)
..lineTo(0, size.height - 50)
..cubicTo(controlPoint1.dx, controlPoint1.dy, controlPoint2.dx,
controlPoint2.dy, endPoint.dx, endPoint.dy)
..close();
return path;
}
arcToPoint
Данный метод нужен для рисования дуги от начальной точки до указанной точки. Мы можем настроить дугу, установив радиус, указав направление (по часовой стрелке / против часовой стрелки).
Существуют эллиптический и круговой типы радиуса для построения дуги. Как показано на приведенном выше рисунке, эллиптический радиус рисуется с использованием значения (x, y)
, а круговой радиус – радиуса R
.
Как показано на рисунке (a) выше, построение фигуры начинается с точки p1
. Первая дуга рисуется от точки p2
до точки p3
, при этом радиус не задан, поэтому по умолчанию равен нулю, соответственно наша дуга выглядит, как прямая. Вторая дуга тянется от начальной точки p4
до конечной точки p5
с использованием кругового радиуса и направления по часовой стрелке (прим. по часовой – направление по умолчанию). Третья дуга тянется от точки p6
до точки p7
, используя круговой радиус и направление против часовой стрелки. Четвертая дуга проходит от начальной точки p8
до конечной точки p1
, используя эллиптический радиус.
@override
Path getClip(Size size) {
double radius = 20;
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: Radius.elliptical(40, 20))
..close();
return path;
}
arcTo
Этот метод используется, чтобы нарисовать дугу, задав в качестве значения в радианах Rect, начальный угол (startAngle) и конечный угол (sweepAngle).
Приведенное выше изображение предназначено для предоставления основной информации об углах в радианах. Минимальный угол равен 0 PI (значение PI равно ~3.14), полный – 2 PI.
Существует несколько способов построения Rect, как с помощью точек, окружности, LTRB (Left, Top, Right, Bottom) и LTWH (Left, Top, Width, Height). На вышеприведенном рисунке (a) все типы дуг нарисованы с разным начальным углом.
@override
Path getClip(Size size) {
double radius = 50;
Path path = Path()
..lineTo(size.width - radius, 0)
..arcTo(
Rect.fromPoints(
Offset(size.width - radius, 0), Offset(size.width, radius)), // Rect
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;
}
addRect
Данный метод нужен для построения прямоугольников. Есть несколько различных методов для создания Rect
: fromPoints
, fromLTWH
, fromCircle
, fromLTRB
и fromCircle
.
@override
Path getClip(Size size) {
Path path = Path()
..addRect(Rect.fromPoints(Offset(0, 0), Offset(60, 60)))
..addRect(Rect.fromLTWH(0, size.height - 50, 50, 50))
..addRect(Rect.fromCircle(center: Offset(size.width / 2, size.height / 2), radius: 20))
..close();
return path;
}
addRRect
Этот метод используется для добавления прямоугольника с закругленными углами. Можно скруглить, как все углы сразу, так и один.
@override
Path getClip(Size size) {
double radius = 10;
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)))
..close();
return path;
}
addOval
Данный метод используется для описания овала. Как и для addRect
, параметр типа Rect
является обязательным.
@override
Path getClip(Size size) {
Path path = Path()
..addOval(Rect.fromPoints(Offset(0, 0), Offset(60, 60)))
..addOval(Rect.fromLTWH(0, size.height - 50, 100, 50))
..addOval(Rect.fromCircle(center: Offset(size.width / 2, size.height / 2), radius: 20))
..close();
return path;
}
addPolygon
Этот метод используется для добавления многоугольника путем определения нескольких точек.
@override
Path getClip(Size size) {
var points = [
Offset(size.width / 2, 0), // точка p1
Offset(0, size.height / 2), // точка p2
Offset(size.width / 2, size.height), // точка p3
Offset(size.width, size.height / 2) // точка p4
];
Path path = Path()
..addPolygon(points, false)
..close();
return path;
}
addPath
Данный метод пригодится, если надо добавить ещё одну фигуру на отображение к уже имеющейся. Для этого необходимо указать описание фигуры и сдвиг относительно начальной позиции точки для вычислениям координат новой фигуры.
Как показано на рисунке (a), есть две фигуры (path 1
и path 2
), path 1
является основной, а path 2
добавляется в path 1
. path 2
строится в соответсвии со сдвигом (w/2, 0)
, поэтому начало координат (0, 0)
и все остальные точки вычисляются с учетом указанного смещения.
@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;
}
relativeLineTo
Этот метод аналогичен методу lineTo
, но конечная точка линии задается не точной координатой, а смещением из начальной.
На рисунке (a) линия p1p2
рисуется с помощью relativeLineTo
, поэтому координата точки p2
вычисляется относительно p1
. Можно записать в виде формулы p2(x, y) = (p1.x + 50, p1.y + h)
@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;
}
Примечание:relativeMoveTo
,relativeQuadraticBezierTo
,relativeArcToPoint
,relativeCubicTo
будут работать по сравнению сquadraticBezierTo
,arcToPoint
,cubicTo
по тому же принципу, что иrelativeLineTo
по отношению кlineTo
.
Надеюсь, что данный материал будет вам полезен и поможет создавать новые классные фигуры.
- Исходный код на Github
- Проект flutter_chat_bubble, созданный с помощью
CustomClipper
mrDevGo
Отличная статья, спасибо за труд.