DEV Community

Toshi Ossada for flutterbrasil

Posted on

Desenhe o que quiser com Custom Paint no Flutter

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.

Monorepo (Por Que Essa Estratégia Funciona em Grandes Empresas?)

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;
}
view raw lines hosted with ❤ by GitHub

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;
}
view raw page3.dart hosted with ❤ by GitHub

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;
}
view raw page4.dart hosted with ❤ by GitHub

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

Image description

Entre em nosso discord para interagir com a comunidade: flutterbrasil.com.br

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

Speedy emails, satisfied customers

Postmark Image

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

AWS Security LIVE!

Hosted by security experts, AWS Security LIVE! showcases AWS Partners tackling real-world security challenges. Join live and get your security questions answered.

Tune in to the full event

DEV is partnering to bring live events to the community. Join us or dismiss this billboard if you're not interested. ❤️