Flutter предлагает различные виджеты для работы с определенным набором фигур, например, ClipRect, ClipRRect, ClipOval. Но также есть ClipPath, с помощью которого мы можем создавать любые типы фигур.
В данной статье мы сосредоточимся на том, что можно сделать, используя ClipPath и CustomClipper. Поехали!
Содержание:
- ClipPath
- lineTo
- moveTo
- quadraticBezierTo
- cubicTo
- arcToPoint
- arcTo
- addRect
- addRRect
- addOval
- addPolygon
- addPath
- relativeLineTo
ClipPath
Благодаря ClipPath мы можем создавать очень сложные и необычные фигуры. В этом нам поможет свойство clipper у ClipPath
@overrideWidget 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) с треугольником розового цвета.
@overridePath 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)
.
@overridePath 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)
.
@overridePath 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
.
@overridePath 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
, используя эллиптический радиус.
@overridePath 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) все типы дуг нарисованы с разным начальным углом.
@overridePath 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
.
@overridePath 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
Этот метод используется для добавления прямоугольника с закругленными углами. Можно скруглить, как все углы сразу, так и один.
@overridePath 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
является
обязательным.
@overridePath 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
Этот метод используется для добавления многоугольника путем определения нескольких точек.
@overridePath 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)
и все
остальные точки вычисляются с учетом указанного смещения.
@overridePath 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)
@overridePath 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