DEV Community

Luís Gustavo
Luís Gustavo

Posted on

Entendendo o CustomPainter do Flutter

Recentemente a @fernandakipper fez uma live em seu canal, onde ela e o DevSoutinho fizeram uma batalha de CSS. Essa batalha tinha com o objetivo desenhar bandeiras dos países utilizando apenas CSS. Resolvi entrar na brincadeira e fazer esse desafio utilizando o CustomPainter do Flutter.

A bandeira escolhida para o desafio foi a bandeira do Brasil, claro!! rs.

Para iniciarmos o desafio primeiro precisamos criar um projeto no VSCode/Android Studio. No meu caso vou utilizar o VSCode, mas você pode utilizar a IDE/Editor de sua preferência.

Abra o VSCode e criei um novo projeto.

New Project

Depois escolha a opção Empty Application.

Empty Application

Selecione onde o projeto vai ser salvo

Folder Project

Agora precisamos dar um nome a esse projeto, coloque o nome que vc achar melhor. No meu caso coloquei o nome de custom_painter_flag

Project Name

Com o nosso projeto já criado, vamos dar inicio a construção da nossa bandeira. Vamos criar uma arquivo chamado de home_page.dart. Esse arquivo vai ser a página principal do nosso aplicativo.

Home Page File

Desenhando o fundo da bandeira

Agora vamos criar um arquivo chamado square.dart. Lembrado que nosso foco aqui é a apenas em entender o CustomPainter, então não se atente a arquitetura, estrutura de pastas, etc.. Estou fazendo da forma mais simples possível.

Square

Dentro do arquivo square.dart vamos criar uma classe e vamos chama-lá de Square. Essa classe vai estender a classe CustomPainter.

class Square extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // TODO: implement paint
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    // TODO: implement shouldRepaint
    throw UnimplementedError();
  }

}
Enter fullscreen mode Exit fullscreen mode

Voltamos no arquivo home_page.dart e criamos o que eu chamo de lousa/quadro, onde vamos fazer a nossa arte, rs. Definimos um tamanho de 200 de altura de 300 de largura para nosso quadro. E para organizar nossos elementos vamos utilizar o Widget Stack para posicionar nossos desenhos uns sobre os outros.

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Stack(
          alignment: Alignment.center,
          children: [
            CustomPaint(
              painter: Square(),
              child: const SizedBox(
                height: 200,
                width: 300,
              ),
            ),
          ],
        ),
      ),
    );
  }
Enter fullscreen mode Exit fullscreen mode

No arquivo square.dart vamos implementar os métodos shouldRepaint e paint. No método shouldRepaint vamos retornar false a principio. O método shouldRepaint é chamado quando uma nova instância da classe é fornecida, para verificar se a nova instância realmente representa informações diferentes. O método paint é chamado sempre que o objeto personalizado precisa ser repintado e é nesse método que vamos fazer o nosso desenho.

class Square extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // TODO: implement paint
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

O método paint fornece um parâmetro chamado Size size que o mesmo tamanho que definimos lá no nosso Widget SizedBox arquivo home_page.dart que foi de 300 de altura e 200 de largura.

 CustomPaint(
    painter: Square(),
    child: const SizedBox(
      height: 200,
      width: 300,
    ),
  ),
Enter fullscreen mode Exit fullscreen mode

Como já sabemos, precisamos desenhar um retângulo verde. Vou criar uma instância da classe Paint e definir a cor dela como verde e também um instância da classe Path onde vamos utilizar ela para desenhar nossas linhas. Agora precisamos definir os pontos que vamos percorrer para fazer as nossas linhas, como se fosse em um papel.

Vamos voltar as aulas de matemática e relembrar o plano cartesiano. Para pintar o nosso retângulo, vamos percorrer os eixos X e Y no nosso quadro.

Plano Cartesiano

A imagem abaixo representa os pontos que temos que percorrer para desenhar o retângulo

Rectangle Points

Agora vamos de fato começar a pintura!

Vamos mover para o ponto inicial [0,0]. A função moveTo(0, 0) recebe os parâmetros X e Y.

> Obs: A partir de agora quando ver a escrita [Número, Número] entenda como [eixoX, eixoY]

class Square extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.green;

    final path = Path()..moveTo(0, 0);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Point [0,0]

Agora vamos desenhar uma linha até o segundo ponto [0,200]. A função lineTo(0, size.height) também recebe os parâmetros X e Y, como eu disse anteriormente o parâmetro size fornecido pelo função paint são os mesmos definidos no Widget SizedBox então size.height = 200.

class Square extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.green;

    final path = Path()..moveTo(0, 0) 
    ..lineTo(0, size.height);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Point [0,200]

Vamos desenhar a próxima linha [300,200], lineTo(size.width, size.height)

class Square extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.green;

    final path = Path()..moveTo(0, 0) 
    ..lineTo(0, size.height)
    ..lineTo(size.width, size.height);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Point [300,200]

Se você chegou até aqui, provavelmente você deve estar vendo essa forma geométrica.

Print device Point [300,200]

Calmaa... ainda precisamos finalizar os outros 2 pontos. Então vamos lá!!!

Vamos desenhar a próxima linha [300,0], lineTo(size.width, 0)

class Square extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.green;

    final path = Path()
      ..moveTo(0, 0)
      ..lineTo(0, size.height)
      ..lineTo(size.width, size.height)
      ..lineTo(size.width, 0);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Point [300,0]

E por fim, finalizamos voltando ao ponto inicial [0,0], lineTo(0, 0)

class Square extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.green;

    final path = Path()
      ..moveTo(0, 0)
      ..lineTo(0, size.height)
      ..lineTo(size.width, size.height)
      ..lineTo(size.width, 0)
      ..lineTo(0, 0);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Point [0,0]

Agora sim você deve estar vendo o retângulo completo.

Print device Square

Primeira parte finalizada com sucesso!!! Agora vamos para criação do Losango.

Desenhando losango da bandeira

Vamos seguir a mesma linha do retângulo, primeiro vamos criar um arquivo chamado rhombus.dart e em seguida criar a classe Rhombus que estende CustomPainter e já colocar o retorno no método shouldRepaint como false .

Rhombus File

class Rhombus extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // TODO: implement paint
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

No arquivo home_page.dart vamos definir a largura de 250 e altura de 150 do nosso quadro para desenhar o losango.

 CustomPaint(
    painter: Rhombus(),
    child: const SizedBox(
      height: 150,
      width: 250,
    ),
  ),
Enter fullscreen mode Exit fullscreen mode
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Stack(
          alignment: Alignment.center,
          children: [
            CustomPaint(
              painter: Square(),
              child: const SizedBox(
                height: 200,
                width: 300,
              ),
            ),
            CustomPaint(
              painter: Rhombus(),
              child: const SizedBox(
                height: 150,
                width: 250,
              ),
            ),
          ],
        ),
      ),
    );
  }
Enter fullscreen mode Exit fullscreen mode

Segue imagem representando plano cartesiano do nosso quadro.

Lousa Losango

Segue imagem dos pontos que vamos ter que percorrer no quadro para desenhar o losango.

Points Losango

Bora colocar a mão na massa e iniciar a construção do nosso losango!!!

Primeiramente vamos criar a variável paint e path como já é de costume e movendo para ponto inicial [125,0] onde vamos começar a desenhar as linhas. Vamos criar também duas variáveis para definir onde está localizado o meio do eixo X(middleHorizontalFrame) e o meio do eixo Y(middleVerticalFrame).

class Rhombus extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.yellow;

    final middleHorizontalFrame = size.width / 2;
    final middleVerticalFrame = size.height / 2;

    final path = Path()..moveTo(middleHorizontalFrame, 0);
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Point [125,0]

Agora vamos desenhar uma linha até o ponto [0,75] lineTo(0, middleVerticalFrame)

class Rhombus extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.yellow;

    final middleHorizontalFrame = size.width / 2;
    final middleVerticalFrame = size.height / 2;

    final path = Path()
      ..moveTo(middleHorizontalFrame, 0)
      ..lineTo(0, middleVerticalFrame);
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Point [0,75]

O próximo passo é desenhar uma linha até o ponto [125,150] lineTo(middleHorizontalFrame, size.height)

class Rhombus extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.yellow;

    final middleHorizontalFrame = size.width / 2;
    final middleVerticalFrame = size.height / 2;

    final path = Path()
      ..moveTo(middleHorizontalFrame, 0)
      ..lineTo(0, middleVerticalFrame)
      ..lineTo(middleHorizontalFrame, size.height);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Point [125,150]

Agora vamos desenhar uma linha até o ponto [250,75] lineTo(size.width, middleVerticalFrame)

class Rhombus extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.yellow;

    final middleHorizontalFrame = size.width / 2;
    final middleVerticalFrame = size.height / 2;

    final path = Path()
      ..moveTo(middleHorizontalFrame, 0)
      ..lineTo(0, middleVerticalFrame)
      ..lineTo(middleHorizontalFrame, size.height)
      ..lineTo(size.width, middleVerticalFrame);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Point [250,75]

Finalmente finalizamos voltando para o ponto inicial [125,0] lineTo(middleHorizontalFrame, 0)

class Rhombus extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.yellow;

    final middleHorizontalFrame = size.width / 2;
    final middleVerticalFrame = size.height / 2;

    final path = Path()
      ..moveTo(middleHorizontalFrame, 0)
      ..lineTo(0, middleVerticalFrame)
      ..lineTo(middleHorizontalFrame, size.height)
      ..lineTo(size.width, middleVerticalFrame)
      ..lineTo(middleHorizontalFrame, 0);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Point [125,0]

Se tudo estiver certo, você deve estar vendo essa imagem abaixo.

Print device Rhombus

Desenhando globo da bandeira

Para desenhar o globo da bandeira vamos utilizar um recurso diferente, vamos utilizar o Widget Container para fazer isso.
Vamos criar um Container com a altura e largura de 100 pixels, e utilizar a propriedade decoration para decorar esse Container. Na propriedade shape do Widget BoxDecoration vamos definir ela como o tipo BoxShape.circle e a cor azul Colors.blue. A propriedade clipBehavior é pra "cortar" o Widget filho do Container para não ultrapassar o círculo.

  Container(
    height: 100,
    width: 100,
    clipBehavior: Clip.antiAlias,
    decoration: const BoxDecoration(
      shape: BoxShape.circle,
      color: Colors.blue,
    ),
  ),
Enter fullscreen mode Exit fullscreen mode

Agora vamos adicionar esse Container no método build do arquivo home_page.dart

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Stack(
          alignment: Alignment.center,
          children: [
            CustomPaint(
              painter: Square(),
              child: const SizedBox(
                height: 200,
                width: 300,
              ),
            ),
            CustomPaint(
              painter: Rhombus(),
              child: const SizedBox(
                height: 150,
                width: 250,
              ),
            ),
            Container(
              height: 100,
              width: 100,
              clipBehavior: Clip.antiAlias,
              decoration: const BoxDecoration(
                shape: BoxShape.circle,
                color: Colors.blue,
              ),
            ),
          ],
        ),
      ),
    );
  }
Enter fullscreen mode Exit fullscreen mode

Agora nossa bandeira já está tomando forma.

Print device globe

Criando a faixa da bandeira

Agora precisamos criar a nossa faixa da bandeira, essa parte requer um pouco mais de atenção, pois ela é um pouco mais complexa que as desenvolvidas até aqui.

Para desenhar nossa faixa vamos utilizar o que chamamos de
Curva de Bézier.
Vamos criar um arquivo e chamar de flag_banner.dart e criar a classe FlagBanner com o método shouldRepaint retornando false.

Flag Banner File

class FlagBanner extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // TODO: implement paint
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Dentro do arquivo home_page.dart iremos criar o nosso quadro onde vamos pintar nossa faixa com uma altura de 100 pixels e largura 100 pixels, para ficar em conformidade com o tamanho do nosso globo que também é de 100 pixel de altura e largura.

CustomPaint(
  painter: FlagBanner(),
  child: const SizedBox(
    height: 100,
    width: 100,
  ),
),
Enter fullscreen mode Exit fullscreen mode

Vamos envolver esse novo quadro em uma nova pilha de widgets com o Widget Stack.

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Stack(
          alignment: Alignment.center,
          children: [
            CustomPaint(
              painter: Square(),
              child: const SizedBox(
                height: 200,
                width: 300,
              ),
            ),
            CustomPaint(
              painter: Rhombus(),
              child: const SizedBox(
                height: 150,
                width: 250,
              ),
            ),
            Container(
              height: 100,
              width: 100,
              clipBehavior: Clip.antiAlias,
              decoration: const BoxDecoration(
                shape: BoxShape.circle,
                color: Colors.blue,
              ),
              child: Stack(
                alignment: Alignment.center,
                children: [
                  CustomPaint(
                    painter: FlagBanner(),
                    child: const SizedBox(
                      height: 100,
                      width: 100,
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
Enter fullscreen mode Exit fullscreen mode

Voltando ao arquivo flag_banner.dart, dentro do método paint vamos fazer diferente agora, vamos criar uma instância da da classe Paint e definir as propriedades color para Colors.white o style para PaintingStyle.stroke para pintar apenas as bordas do conteúdo e não o interior, o strokeWidth igual a 20 para definir largura do traçado.

class FlagBanner extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.white
      ..style = PaintingStyle.stroke
      ..strokeWidth = 20;


  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Para auxiliar na construção vamos utilizar o Figma para facilitar o entendimento. Vamos criar um Frame de dividir ele am 4 partes iguais na horizontal de na vertical. Pra isso iremos utilizar o LayoutGrid e alterar a propriedade size para 24.

Frame Flag Banner

Vá até o menu localizado no canto superior esquerdo do Figma e seleciono a opção pen para iniciar o desenho da faixa.

Pen Figma

Faça uma linha na diagonal da base o primeiro quadrado até o final do da base do último quadrado.

Line pen

Logo em seguida vá até o menu novamente e selecione a opção Bend Tools.

Bend Tools

Selecione a ponta e arraste até o final do quadro

Drag line

No menu lateral direito altere o stroke para 20.

Stroke 20

Aqui temos um noção de como deve ficar a nossa faixa.

Flag Banner Figma

Agora vamos transformar isso em código!!!

No arquivo flag_banner.dart vamos dividir o nosso quadro em 4 partes como fizemos no Figma, e posicionar no ponto inicial [0,25] onde iremos traçar a linha diagonal. Como definimos nosso quadro com altura de 100 pixels, cada quadrado deverá ter 25 pixels, ou seja o valor da variável verticalFrames será 25.

class FlagBanner extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.white
      ..style = PaintingStyle.stroke
      ..strokeWidth = 20;

    final verticalFrames = size.height / 4;

    final path = Path()
      ..moveTo(0, verticalFrames);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Para fazer a linha curva vamos utilizar o método cubicTo( double x1, double y1, double x2, double y2, double x3, double y3,). Segue exemplos dos pontos x1, y1, x2, y2... abaixo.

cubicTo example

Vamos definir o ponto X1 no ponto final do nosso eixo X size.width, que seria o mesmo que 100.

X1 Point

class FlagBanner extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.white
      ..style = PaintingStyle.stroke
      ..strokeWidth = 20;

    // 100 / 4 = 25
    final verticalFrames = size.height / 4;

    final path = Path()
      ..moveTo(0, verticalFrames)
      ..cubicTo(
        size.width // x1
       // y1, 
       // x2, 
       // y2, 
       // x3, 
       // y3,
      );

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Agora vamos posicionar o y1 a 25 pixels de distância

y1 point

class FlagBanner extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.white
      ..style = PaintingStyle.stroke
      ..strokeWidth = 20;

    // 100 / 4 = 25
    final verticalFrames = size.height / 4;

    final path = Path()
      ..moveTo(0, verticalFrames)
      ..cubicTo(
        size.width, // x1
        verticalFrames // y1, 
       // x2, 
       // y2, 
       // x3, 
       // y3,
      );

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

As demais posições vão fica no mesmo lugar, x2, y2, x3 e y3.

Point x2,  y2, x3 e y3

class FlagBanner extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = Colors.white
      ..style = PaintingStyle.stroke
      ..strokeWidth = 20;

    // 100 / 4 = 25
    final verticalFrames = size.height / 4;

    final path = Path()
      ..moveTo(0, verticalFrames)
      ..cubicTo(
        size.width, // x1
        verticalFrames, // y1,
        size.width, // x2,
        size.height, // y2,
        size.width, // x3,
        size.height, // y3,
      );

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Se tudo estiver certo teremos a seguinte imagem abaixo.

Print device flag banner

Estamos quase finalizando a nossa bandeira, agora precisamos criar as estrelas.

Criando as estrelas

Pra criar as nossas estrelas, primeiro como de costume vamos criar o nosso arquivo star.dart e a classe Star que estende CustomPainter e já implementar o método shouldRepaint que irá retornar false.

Star file

class Star extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    // TODO: implement paint
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

No arquivo home_page.dart vamos definir o tamanho do nosso quadro que a princípio será de 20 pixels de altura e largura.

CustomPaint(
  painter: Star(),
  child: SizedBox(
    height: 20,
    width: 20,
  ),
),
Enter fullscreen mode Exit fullscreen mode

Pra podermos visualizar a nossa estrela, vamos envolver nosso quadro em um Widget Positioned de definir as propriedades. bottom e left pra 70 pixels.

Nosso método build ficará assim.

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Stack(
          alignment: Alignment.center,
          children: [
            CustomPaint(
              painter: Square(),
              child: const SizedBox(
                height: 200,
                width: 300,
              ),
            ),
            CustomPaint(
              painter: Rhombus(),
              child: const SizedBox(
                height: 150,
                width: 250,
              ),
            ),
            Container(
              height: 100,
              width: 100,
              clipBehavior: Clip.antiAlias,
              decoration: const BoxDecoration(
                shape: BoxShape.circle,
                color: Colors.blue,
              ),
              child: Stack(
                alignment: Alignment.center,
                children: [
                  CustomPaint(
                    painter: FlagBanner(),
                    child: const SizedBox(
                      height: 100,
                      width: 100,
                    ),
                  ),
                  Positioned(
                    bottom: 70,
                    left: 70,
                    child: CustomPaint(
                      painter: Star(),
                      child: const SizedBox(
                        height: 20,
                        width: 20,
                      ),
                    ),
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
Enter fullscreen mode Exit fullscreen mode

Seguindo o mesmo modelo da faixa da bandeira vamos utilizar o Figma pra auxiliar na construção da nossa estrela. Vamos criar um Frame adicionar LayoutGrid a ele e definir a propriedade size para 12, assim vamos ter um quadro divido em 8 partes iguais.

Frame Star

Agora vamos traçar uma linha diagonal utilizando 3 quadrados na vertical e 3 na horizontal.

Point 1 star

Agora vamos fazer outra linha na horizontal também utilizando 3 quadrados na vertical e 3 na horizontal, e assim vamos seguindo os pontos que temos que percorrer em linhas para criarmos a nossa estrela.

Poin 2 star

Segue exemplo completo

Figma Star

Agora vamos transformar isso em código!!

No arquivo star.dart vamos implementar método paint da classe Star.
Primeiro passo é definir a cor da nossa estrela e dividir o nosso quadro em 8 partes iguais, igual ilustramos no Figma. Como definimos que o nosso quadro terá 20 pixels de altura e largura, cada parte(quadrado) terá 8 quadrados de 2,5 pixels.

class Star extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.white;

    // Divide o quadro em 8 partes iguais, na vertical e horizontal
    // 20 / 8 =  2,5, ou seja cada quadradinho do nosso quadro tem 2,5px
    final verticalFrames = size.height / 8;
    final horizontalFrames = size.width / 8;
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Para facilitar, vamos criar algumas variáveis onde já vamos deixar definido a quantidade de quadrados que vamos percorrer na vertical e horizontal no nosso quadro traçando as linhas.

class Star extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.white;

    // Divide o quadro em 8 partes iguais, na vertical e horizontal
    // 20 / 8=  2,5, ou seja cada quadradinho do nosso quadro tem 2,5px
    final verticalFrames = size.height / 8;
    final horizontalFrames = size.width / 8;

    final threeVerticalFrames = verticalFrames * 3;
    final fiveVerticalFrames = verticalFrames * 5;
    final sixVerticalFrames = verticalFrames * 6;

    final oneHorizontalFrames = horizontalFrames * 1;
    final twoHorizontalFrames = horizontalFrames * 2;
    final threeHorizontalFrames = horizontalFrames * 3;
    final middleHorizontalFrame = size.width / 2;
    final fiveHorizontalFrames = horizontalFrames * 5;
    final sixHorizontalFrames = horizontalFrames * 6;
    final sevenHorizontalFrames = horizontalFrames * 7;
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

O que fizemos basicamente foi, pegar o valor que representa um quadradinho do nosso quadro que é 2,5 e multiplicar pela quantidade de quadrados que queremos percorrer. Exemplo de 3 quadrados: 2,5px * 3 = 7,5px

Three square

Agora vamos dar inicio ao nosso desenho, primeiro vamos posicionar no ponto inicial do nosso quadro [10,0]. Lembrando que nosso quadro tem 20 pixels largura, logo 10 pixels representa o meio do nosso quadro, moveTo(middleHorizontalFrame, 0).

1 point star

class Star extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.white;

    // Divide o quadro em 8 partes iguais, na vertical e horizontal
    // 20 / 8=  2,5, ou seja cada quadradinho do nosso quadro tem 2,5px
    final verticalFrames = size.height / 8;
    final horizontalFrames = size.width / 8;

    final threeVerticalFrames = verticalFrames * 3;
    final fiveVerticalFrames = verticalFrames * 5;
    final sixVerticalFrames = verticalFrames * 6;

    final oneHorizontalFrames = horizontalFrames * 1;
    final twoHorizontalFrames = horizontalFrames * 2;
    final threeHorizontalFrames = horizontalFrames * 3;
    final middleHorizontalFrame = size.width / 2;
    final fiveHorizontalFrames = horizontalFrames * 5;
    final sixHorizontalFrames = horizontalFrames * 6;
    final sevenHorizontalFrames = horizontalFrames * 7;

    final path = Path()..moveTo(middleHorizontalFrame, 0);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Nosso próximo ponto vamos percorrer 3 quadrado na horizontal e vertical [7,5 , 7,5], lineTo(threeHorizontalFrames, threeVerticalFrames).

2 point star

No próximo não percorreremos nenhum(0) quadrado na horizontal e 3 na vertical [0, 7,5], lineTo(0, threeVerticalFrames)

3 point star

class Star extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.white;

    // Divide o quadro em 8 partes iguais, na vertical e horizontal
    // 20 / 8=  2,5, ou seja cada quadradinho do nosso quadro tem 2,5px
    final verticalFrames = size.height / 8;
    final horizontalFrames = size.width / 8;

    final threeVerticalFrames = verticalFrames * 3;
    final fiveVerticalFrames = verticalFrames * 5;
    final sixVerticalFrames = verticalFrames * 6;

    final oneHorizontalFrames = horizontalFrames * 1;
    final twoHorizontalFrames = horizontalFrames * 2;
    final threeHorizontalFrames = horizontalFrames * 3;
    final middleHorizontalFrame = size.width / 2;
    final fiveHorizontalFrames = horizontalFrames * 5;
    final sixHorizontalFrames = horizontalFrames * 6;
    final sevenHorizontalFrames = horizontalFrames * 7;

    final path = Path()
      ..moveTo(middleHorizontalFrame, 0)
      ..lineTo(threeHorizontalFrames, threeVerticalFrames)
      ..lineTo(0, threeVerticalFrames);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Seguindo a mesma lógica do ponto anterior o próximo vamos percorrer 2 quadrados na horizontal e 5 na vertical [5, 12,5], lineTo(twoHorizontalFrames, fiveVerticalFrames).

3 point star

class Star extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.white;

    // Divide o quadro em 8 partes iguais, na vertical e horizontal
    // 20 / 8=  2,5, ou seja cada quadradinho do nosso quadro tem 2,5px
    final verticalFrames = size.height / 8;
    final horizontalFrames = size.width / 8;

    final threeVerticalFrames = verticalFrames * 3;
    final fiveVerticalFrames = verticalFrames * 5;
    final sixVerticalFrames = verticalFrames * 6;

    final oneHorizontalFrames = horizontalFrames * 1;
    final twoHorizontalFrames = horizontalFrames * 2;
    final threeHorizontalFrames = horizontalFrames * 3;
    final middleHorizontalFrame = size.width / 2;
    final fiveHorizontalFrames = horizontalFrames * 5;
    final sixHorizontalFrames = horizontalFrames * 6;
    final sevenHorizontalFrames = horizontalFrames * 7;

    final path = Path()
      ..moveTo(middleHorizontalFrame, 0)
      ..lineTo(threeHorizontalFrames, threeVerticalFrames)
      ..lineTo(0, threeVerticalFrames)
      ..lineTo(twoHorizontalFrames, fiveVerticalFrames);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

E pra finalizar, vamos seguir a lógica dos pontos anteriores vamos percorrer os demais pontos.

class Star extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.white;

    // Divide o quadro em 8 partes iguais, na vertical e horizontal
    // 20 / 8=  2,5, ou seja cada quadradinho do nosso quadro tem 2,5px
    final verticalFrames = size.height / 8;
    final horizontalFrames = size.width / 8;

    final threeVerticalFrames = verticalFrames * 3;
    final fiveVerticalFrames = verticalFrames * 5;
    final sixVerticalFrames = verticalFrames * 6;

    final oneHorizontalFrames = horizontalFrames * 1;
    final twoHorizontalFrames = horizontalFrames * 2;
    final threeHorizontalFrames = horizontalFrames * 3;
    final middleHorizontalFrame = size.width / 2;
    final fiveHorizontalFrames = horizontalFrames * 5;
    final sixHorizontalFrames = horizontalFrames * 6;
    final sevenHorizontalFrames = horizontalFrames * 7;

    final path = Path()
      ..moveTo(middleHorizontalFrame, 0)
      ..lineTo(threeHorizontalFrames, threeVerticalFrames)
      ..lineTo(0, threeVerticalFrames)
      ..lineTo(twoHorizontalFrames, fiveVerticalFrames)
      ..lineTo(oneHorizontalFrames, size.height)
      ..lineTo(middleHorizontalFrame, sixVerticalFrames)
      ..lineTo(sevenHorizontalFrames, size.height)
      ..lineTo(sixHorizontalFrames, fiveVerticalFrames)
      ..lineTo(size.width, threeVerticalFrames)
      ..lineTo(fiveHorizontalFrames, threeVerticalFrames)
      ..lineTo(middleHorizontalFrame, 0);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}
Enter fullscreen mode Exit fullscreen mode

Se tudo estiver OK, nós teremos a seguinte imagem abaixo.

Print device star

E pra finalizar vamos criar uma método para gerar nossas estrelas de forma aleatórias.

  List<({double leftPositionStar, double bottomPositionStar, double starSize})>
      _generateStars() {
    final list = <({
      double leftPositionStar,
      double bottomPositionStar,
      double starSize
    })>[];
    var bottomPositionStar = 0.0;
    var leftPositionStar = 0.0;
    var starSize = 2.0;

    const spacingBetweenStars = 10;

    while (list.length != 26) {
      if (list.isEmpty) {
        bottomPositionStar = 70;
        leftPositionStar = 70;
        starSize = 8;
        final itemList = (
          leftPositionStar: leftPositionStar,
          bottomPositionStar: bottomPositionStar,
          starSize: starSize
        );
        list.add(itemList);
      } else {
        bottomPositionStar = double.parse(
          (Random.secure().nextInt(55) + 10).toString(),
        );
        leftPositionStar = double.parse(
          (Random.secure().nextInt(55) + 10).toString(),
        );
        starSize = double.parse(
          (Random.secure().nextInt(8) + 2).toString(),
        );

        final generatedItem = (
          leftPositionStar: leftPositionStar,
          bottomPositionStar: bottomPositionStar,
          starSize: starSize
        );

        for (final item in list) {
          if ((item.leftPositionStar - generatedItem.leftPositionStar).abs() <
                  spacingBetweenStars &&
              (item.bottomPositionStar - generatedItem.bottomPositionStar)
                      .abs() <
                  spacingBetweenStars) {
            continue;
          }
        }

        list.add(generatedItem);
      }
    }

    return list;
  }
}
Enter fullscreen mode Exit fullscreen mode

O método build do arquivo home_page.dart ficará da seguinte forma.

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Stack(
          alignment: Alignment.center,
          children: [
            CustomPaint(
              painter: Square(),
              child: const SizedBox(
                height: 200,
                width: 300,
              ),
            ),
            CustomPaint(
              painter: Rhombus(),
              child: const SizedBox(
                height: 150,
                width: 250,
              ),
            ),
            Container(
              height: 100,
              width: 100,
              clipBehavior: Clip.antiAlias,
              decoration: const BoxDecoration(
                shape: BoxShape.circle,
                color: Colors.blue,
              ),
              child: Stack(
                alignment: Alignment.center,
                children: [
                  CustomPaint(
                    painter: FlagBanner(),
                    child: const SizedBox(
                      height: 100,
                      width: 100,
                    ),
                  ),
                  ..._generateStars().map(
                    (star) {
                      return Positioned(
                        bottom: star.bottomPositionStar,
                        left: star.leftPositionStar,
                        child: CustomPaint(
                          painter: Star(),
                          child: SizedBox(
                            height: star.starSize,
                            width: star.starSize,
                          ),
                        ),
                      );
                    },
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }
Enter fullscreen mode Exit fullscreen mode

E o resultado final foi esse!!!!

Obrigado! Espero que tenham gostado 😃

Final result

Repositório do GitHub
https://github.com/luisgustavoo/custom_painter_test

Referência
https://medium.com/flutterando/desenhe-o-que-quiser-com-custom-paint-no-flutter-b8557d794823

Top comments (1)

Collapse
 
luizzlcs profile image
Luiz Carlos

Parabéns, excelente conteúdo apresentado forma prática e de fácil entendimento.