Fala Devs,
Que o Flutter é uma poderosa ferramenta que nos traz uma gama de widgets, possibilitando a criação de layouts modernos e bonitos, isso já sabemos. Mas sabia que o Flutter também possui uma ferramenta muito legal que nos permite ter uma variedade de efeitos visuais, desde formas geométricas simples como linha, quadrado, arco, círculo, etc., até desenhos complexos e animados? Literalmente, podemos desenhar com código em nossos widgets. Estou falando do CustomPaint.
Como funciona?
Para começarmos a utilizá-lo é muito simples, basta usarmos um widget chamado CustomPaint(), e ele espera que passemos como parâmetro um CustomPainter e o tamanho que nosso painter terá.
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text('Square'), | |
), | |
backgroundColor: Colors.white, | |
body: Center( | |
child: CustomPaint( | |
painter: _MyPainter(), | |
size: const Size(300, 200), | |
), | |
), | |
); | |
} |
Nosso painter será uma classe que irá estender a classe CustomPainter e terá dois métodos principais. Um será o shouldRepaint(), que será o método responsável pela lógica que determina se nosso painter deve ser redesenhado ou não, semelhante ao didUpdateWidget() de um StatefulWidget. Por hora, podemos retornar sempre true.
O segundo método, que é o mais importante, é o paint(). Aqui é onde colocaremos todas as instruções do que queremos desenhar. Neste método, temos dois parâmetros importantes: o Size, que é a informação do tamanho da área que teremos para realizar nosso desenho, e o Canvas, que é responsável por realizar o desenho dentro do nosso ‘quadro’.
Neste exemplo criamos o Paint(), que é o responsável por conter todas as informações da propriedade do nosso desenho (Cor, espessura de linha, etc), no caso colocamos a cor roxa e com o estilo preenchido, isso significa que quando ele fechar todos os pontos de do desenho ele irá preencher com a cor selecionada. E por fim utilizamos o Rect.fromLTWH() que irá desenha um retângulo a partir dos pontos de par ordenado (lembra das aulas de matemática?) que passamos, lembrando que o ponto inicial é o [0, 0] e ele vai até ponto [largura_maxima, altura_maxima]. Por fim utilizando o Canvas simplesmente mando ele desenhar no meu painter utilizando o método drawRect().
Também temos a opção de passar o style como stroke.
Desta forma ele não preencherá o interior do desenho
Linhas
Também podemos desenhar linhas, basta utilizarmos a classe Path() para informar os pontos de ligação das linhas. Primeiramente, usamos o moveTo(0, 0) para informar o ponto inicial do desenho, e em seguida, utilizamos o lineTo() para informar o par ordenado dos pontos de ligação das linhas. Por fim, instruímos o canvas a desenhar nossos Paths utilizando o drawPath().
class _MyPainter extends CustomPainter { | |
@override | |
void paint(Canvas canvas, Size size) { | |
final paint = Paint() | |
..color = Colors.purple.withOpacity(.4) | |
..style = PaintingStyle.fill; | |
final rect = Rect.fromLTWH(0, 0, size.width, size.height); | |
canvas.drawRect(rect, paint); | |
} | |
@override | |
bool shouldRepaint(covariant _MyPainter oldDelegate) => true; | |
} |
Como resultado teremos
Curvas
Ainda utilizando os Paths também conseguimos fazer curvas, a primeira forma é utilizado o quadraticBezierTo ele adiciona um ponto “invisível” que vai flexionar a linha naquela direção.
Também temos o cubicTo que diferente do quadraticBezierTo ele adiciona dois pontos de flexão.
No CustomPainter também conseguimos usar em conjunto com o ClipPath, suponhamos que temos 2 retângulos sobrepostos e queiramos fazer um recorte em um deles para quer aparecer os dois retângulos ocupando metade da tela com um corte personalizado
Podemos adicionar o primeiro retângulo com o drawRect() e em seguida utilizar o clipPath do Canves passando o Path com as ordenadas do corte e logo em seguida adicionar o segundo retângulo.
import 'package:flutter/material.dart'; | |
class Page3 extends StatelessWidget { | |
const Page3({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text('Wave'), | |
), | |
backgroundColor: Colors.white, | |
body: Center( | |
child: CustomPaint( | |
painter: _MyPainter(), | |
size: Size(MediaQuery.of(context).size.width, | |
MediaQuery.of(context).size.height), | |
), | |
), | |
); | |
} | |
} | |
class _MyPainter extends CustomPainter { | |
@override | |
void paint(Canvas canvas, Size size) { | |
final paintPurple = Paint() | |
..color = Colors.purple | |
..style = PaintingStyle.fill; | |
canvas.drawRect(Offset.zero & size, paintPurple); | |
final paintGreen = Paint() | |
..color = Colors.green | |
..style = PaintingStyle.fill; | |
final path = Path() | |
..moveTo(size.width, 0) | |
..lineTo(size.width * .5, 0) | |
..quadraticBezierTo( | |
size.width * .2, size.height * .2, size.width * .5, size.height * .5) | |
..quadraticBezierTo( | |
size.width * .7, size.height * .7, size.width * .5, size.height) | |
..lineTo(size.width, size.height) | |
..lineTo(size.width, 0) | |
..close(); | |
canvas.clipPath(path); | |
canvas.drawRect(Offset.zero & size, paintGreen); | |
} | |
@override | |
bool shouldRepaint(covariant _MyPainter oldDelegate) => true; | |
} |
Como resultado teremos
Arcos
Lembra das aulas de Geometria? Aqui utilizaremos os conceitos aprendidos, sabemos que um círculo inteiro possui 2PI, no Paint e o ponto zero fica no meio do lado direito.
E com isso conseguimos desenhar um círculo ou parte dele utilizando os conceitos de seno.
import 'dart:math' as math; | |
import 'package:flutter/material.dart'; | |
class Page4 extends StatelessWidget { | |
const Page4({super.key}); | |
@override | |
Widget build(BuildContext context) { | |
return Scaffold( | |
appBar: AppBar( | |
title: const Text('Circles'), | |
), | |
body: SizedBox( | |
width: double.infinity, | |
child: Column( | |
mainAxisAlignment: MainAxisAlignment.spaceEvenly, | |
crossAxisAlignment: CrossAxisAlignment.center, | |
children: [ | |
CustomPaint( | |
painter: _MyPainter(), | |
size: const Size(50, 50), | |
), | |
CustomPaint( | |
painter: _MyPainter2(), | |
size: const Size(50, 50), | |
), | |
CustomPaint( | |
painter: _MyPainter3(), | |
size: const Size(50, 50), | |
), | |
CustomPaint( | |
painter: _MyPainter4(), | |
size: const Size(50, 50), | |
), | |
], | |
), | |
), | |
); | |
} | |
} | |
class _MyPainter extends CustomPainter { | |
@override | |
void paint(Canvas canvas, Size size) { | |
final paint = Paint() | |
..color = Colors.black | |
..strokeWidth = 5 | |
..style = PaintingStyle.stroke; | |
canvas.drawArc( | |
Rect.fromLTWH(0, 0, size.width, size.height), | |
0, | |
math.pi / 2, | |
false, | |
paint, | |
); | |
} | |
@override | |
bool shouldRepaint(covariant _MyPainter oldDelegate) => true; | |
} | |
class _MyPainter2 extends CustomPainter { | |
@override | |
void paint(Canvas canvas, Size size) { | |
final paint = Paint() | |
..color = Colors.black | |
..strokeWidth = 5 | |
..style = PaintingStyle.stroke; | |
canvas.drawArc( | |
Rect.fromLTWH(0, 0, size.width, size.height), | |
0, | |
math.pi, | |
false, | |
paint, | |
); | |
} | |
@override | |
bool shouldRepaint(covariant _MyPainter2 oldDelegate) => true; | |
} | |
class _MyPainter3 extends CustomPainter { | |
@override | |
void paint(Canvas canvas, Size size) { | |
final paint = Paint() | |
..color = Colors.black | |
..strokeWidth = 5 | |
..style = PaintingStyle.stroke; | |
canvas.drawArc( | |
Rect.fromLTWH(0, 0, size.width, size.height), | |
0, | |
3 * math.pi / 2, | |
false, | |
paint, | |
); | |
} | |
@override | |
bool shouldRepaint(covariant _MyPainter3 oldDelegate) => true; | |
} | |
class _MyPainter4 extends CustomPainter { | |
@override | |
void paint(Canvas canvas, Size size) { | |
final paint = Paint() | |
..color = Colors.black | |
..strokeWidth = 5 | |
..style = PaintingStyle.stroke; | |
canvas.drawArc( | |
Rect.fromLTWH(0, 0, size.width, size.height), | |
0, | |
2 * math.pi, | |
false, | |
paint, | |
); | |
} | |
@override | |
bool shouldRepaint(covariant _MyPainter4 oldDelegate) => true; | |
} |
Com esses conceitos podemos fazer um progresso circular.
Recebemos o progresso e o texto por parâmetro
Em seguida utilizando o drawArc passando o seno inicial 3PI/2 (imagine esta imagem invertida)
E passando o progresso como parâmetro para o drawArc
Também conseguimos pedir para o CustomPaint desenhar textos, utilizando o TextSpan e depois assando a posição onde ele ficará
Legal né? Da para fazer muita coisa com CustomPaint, nos próximos artigos vamos trabalhar um pouco mais com eles.
Os exemplos que utilizei no artigo se encotram no meu github :)
https://github.com/toshiossada/clippath
Entre em nosso discord para interagir com a comunidade: flutterbrasil.com.br
Top comments (0)