5 Widgets Flutter que provavelmente você ainda não ouviu falar (mas devia usar!)

Este post foi originalmente publicado no blogue DCM.dev. Para ter uma experiência melhor, especialmente o realce de sintaxe, confira o post original.

Já se passaram mais de 6 anos desde que comecei a desenvolver com Flutter e Dart, e mesmo assim ainda me deparo com funcionalidades ou widgets que não sabia que existiam. É fascinante como uma framework com a qual trabalhamos diariamente ainda nos pode surpreender com a sua profundidade e versatilidade.

Neste artigo, quero partilhar algumas dessas jóias escondidas que descobri, widgets e funcionalidades menos conhecidas que podem simplificar o seu processo de desenvolvimento e dar um toque único às suas aplicações. Além disso, vamos aprofundar a implementação e explorar como são implementados para aprender mais sobre os padrões.

Vamos descobrir alguns desses widgets juntos!

Widget de banner

Imagine que você está a construir uma funcionalidade em que certos itens na sua aplicação precisam de um indicador visual arrojado, por exemplo, uma etiqueta “VENDA”, “NOVO” ou “EM DESTAQUE” que chame a atenção. Você poderia seguir o caminho personalizado, mas o Flutter oferece um widget muito mais simples e fácil de usar: o widget Banner.

Isto parece-me familiar, não é?

É verdade! O CheckedModeBanner utiliza este widget para mostrar um banner a dizer “DEBUG” quando está a correr em modo de depuração, e o MaterialApp constrói um por padrão.

class CheckedModeBanner extends StatelessWidget {
  const CheckedModeBanner({super.key, required this.child});
final Widget child;
  @override
  Widget build(BuildContext context) {
    Widget result = child;
    assert(() {
      result = Banner( // <--- That's the Banner widget!
        message: 'DEBUG',
        textDirection: TextDirection.ltr,
        location: BannerLocation.topEnd,
        child: result,
      );
      return true;
    }());
    return result;
  }
  @override
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    // LOGIC
  }
}

Por baixo do capô, o widget Banner depende do CustomPaint para renderização. O CustomPainter dentro do widget desenha uma faixa diagonal usando um Path.

Vamos dar uma olhada mais de perto:

class _BannerState extends State<Banner> {
  BannerPainter? _painter;
  // ...
  @override
  Widget build(BuildContext context) {
    // ...
    _painter?.dispose();
    _painter = BannerPainter(
      message: widget.message,
      textDirection: widget.textDirection ?? Directionality.of(context),
      location: widget.location,
      layoutDirection: widget.layoutDirection ?? Directionality.of(context),
      color: widget.color,
      textStyle: widget.textStyle,
      shadow: widget.shadow,
    );
return CustomPaint(foregroundPainter: _painter, child: widget.child);
  }
  // ...
}

O widget CustomPaint aqui renderiza o banner e delega a lógica de desenho para a classe BannerPainter. Esse pintor usa as APIs de tela de baixo nível do Flutter para lidar tanto com a faixa diagonal quanto com o texto que a acompanha. A classe painter vai além com seu método paint, que orquestra tudo, desde rotações até o layout do texto:

class BannerPainter extends CustomPainter {
// …
@override
void paint(Canvas canvas, Size size) {
// …
tela
..translate(_translationX(size.width), _translationY(size.height))
..rotate(_rotation)
..drawRect(_kRect, _paintShadow)
..drawRect(_kRect, _paintBanner);
const double width = _kOffset * 2.0;
layout(minWidth: largura, maxWidth: largura);
Pintor de texto!.pintar(
canvas,
_kRect.topLeft + Offset(
0.0, (_kRect.height - _textPainter!.height) / 2.0,
),
);
}
// …
}

A API do widget Banner é simples como a que se segue:

  • Mensagem: Quer seja “BETA”, “VENDA” ou “NOVO”, esta propriedade define o objetivo do banner.
  • localização: Com valores como topStart, topEnd, bottomStart e bottomEnd, a posição do banner é totalmente personalizável.
enum BannerLocation {
  topStart,
  topEnd,
  bottomStart,
  bottomEnd,
}
  • color: Esta propriedade define a cor de fundo, garantindo que o banner se destaca sem entrar em conflito com o widget subjacente.
  • textStyle: Acompanha a mensagem e permite-lhe ajustar a aparência do texto, desde o tamanho da fonte à cor e ao peso.
  • child: Talvez a propriedade mais crucial, o child, representa o widget que está a ser envolvido pelo banner. Pode ser qualquer coisa – um cartão, um contentor, ou mesmo outro widget complexo.

Vejamos agora um exemplo completo:

Banner(
  message: 'BETA',
  location: BannerLocation.bottomStart,
  color: Colors.green,
  textStyle: TextStyle(
    fontSize: 12,
    color: Colors.white,
  ),
  child: Card(
    elevation: 4,
    child: Padding(
      padding: const EdgeInsets.all(16.0),
      child: Text('New Feature: Try Now!'),
    ),
  ),
);

Este widget era geralmente simples, mas tem muito a oferecer quando se olha para o código fonte, especialmente em termos de aprender a usar o Custom Painter.

Muito bem, vamos agora dar uma vista de olhos a outro widget, que talvez não tenha ouvido falar.


Widget MetaData

O widget MetaData no Flutter é um daqueles widgets que muitas vezes passam despercebidos.

À primeira vista, ele pode não parecer grande coisa – ele não renderiza nada visível na tela, nem modifica diretamente o comportamento de seu filho. No entanto, o MetaData anexa metadados personalizados a uma subárvore de widgets. Isso significa que, como desenvolvedores do Flutter, podemos definir informações semânticas ou estruturais adicionais.

Considere o seguinte exemplo:

MetaData(
  metaData: 'Custom tag for this widget',
  child: Container(
    height: 100,
    width: 100,
    color: Colors.blue,
  ),
);

Aqui, a propriedade MetaData permite-lhe marcar a subárvore de widgets com qualquer informação arbitrária. Estas informações não afectam a apresentação ou a interação, mas podem ser recuperadas e processadas por sistemas externos. Imagine utilizar estes metadados durante os testes automatizados para identificar widgets através de metadados personalizados ou anexar descrições para análises personalizadas.

Vamos aprofundar um pouco mais e responder em que cenários isto pode ser útil.

MetaData se torna aparente nos cenários de acessibilidade. Enquanto o Flutter fornece um rico suporte de acessibilidade embutido através de widgets como Semantics, há momentos em que você precisa adicionar anotações personalizadas. Usando MetaData, você pode marcar widgets com informações adicionais que ferramentas externas podem interpretar.

MetaData(
  metaData: {'role': 'button', 'label': 'Custom Button'},
  behavior: HitTestBehavior.translucent,
  child: GestureDetector(
    onTap: () => print('Button tapped'),
    child: Container(
      height: 50,
      width: 150,
      color: Colors.green,
      child: Center(child: Text('Click Me')),
    ),
  ),
);

Neste exemplo, o campo MetaData contém um mapa com funções e etiquetas personalizadas para o widget.

O widget MetaData também desempenha um papel nas ferramentas de depuração e de desenvolvimento. Por exemplo, se estiver a trabalhar numa hierarquia de IU grande e complexa, pode utilizar metadados para marcar secções específicas da árvore. Esta marcação pode ajudá-lo a localizar interações de widgets ou alterações de estado durante o tempo de execução.

Outra caraterística eficaz dos MetaDados reside na propriedade de comportamento. Essa propriedade define como o widget participa do teste de acerto. Por padrão, o widget MetaData não interfere nos testes de acerto, mas você pode configurá-lo usando um dos valores de HitTestBehavior:

  • Opaco: O widget captura todos os eventos de ponteiro, mesmo que o filho não o faça.
  • translúcido: Os eventos de ponteiro passam para os widgets abaixo do filho.

Por exemplo, considere um cenário em que você deseja adicionar metadados a um widget invisível que ainda participa de testes de acerto:

MetaData(
  metaData: 'Invisible button',
  behavior: HitTestBehavior.opaque,
  child: Container(
    height: 100,
    width: 100,
    color: Colors.transparent,
  ),
);

Widget RepaintBoundary

O objetivo do widget RepaintBoundary é limitar a área da tela que precisa ser redesenhada quando ocorrem atualizações visuais, reduzindo repinturas desnecessárias, o que melhora drasticamente o desempenho de um aplicativo, especialmente em cenários com elementos de interface do usuário complexos ou que mudam com frequência.

O widget RepaintBoundary funciona criando uma nova camada de composição no pipeline de renderização do Flutter. As camadas são uma parte essencial do modelo de renderização do Flutter, permitindo que partes da cena sejam desenhadas independentemente. Eu falei sobre o pipeline de renderização no Flutter extensivamente no meu livro FlutterEngineering.io.

Quando um RepaintBoundary é adicionado a um widget, esse widget e a sua subárvore são isolados na sua própria camada de composição. Isto garante que apenas essa camada tem de ser redesenhada quando ocorre uma atualização visual na subárvore, e não o ecrã inteiro.

RepaintBoundary(
  child: Container(
    width: 100,
    height: 100,
    color: Colors.blue,
    child: Text('Optimized Painting'),
  ),
);

Widget IntrinsicHeight

O objetivo do widget IntrinsicHeight consiste em resolver situações em que as alturas variáveis podem criar desequilíbrios visuais ou inconsistências na interface do utilizador.

O widget IntrinsicHeight pode parecer um widget de nicho, mas o seu valor torna-se óbvio quando se trabalha com layouts dinâmicos em que a manutenção da proporcionalidade é essencial. Deixe-me dar-lhe um exemplo: imagine uma fila de widgets que contêm texto, ícones ou outro conteúdo de diferentes comprimentos. Sem intervenção, a altura de cada widget filho seria determinada de forma independente, o que poderia levar a um desalinhamento.

Considere o código abaixo:

IntrinsicHeight(
  child: Row(
    crossAxisAlignment: CrossAxisAlignment.stretch,
    children: [
      Container(
        width: 100,
        color: Colors.blue,
        child: Text('Short text'),
      ),
      Container(
        width: 100,
        color: Colors.red,
        child: Text('This is a much longer piece of text'),
      ),
    ],
  ),
);

Neste exemplo, o widget IntrinsicHeight assegura que ambos os contentores na linha se estendem até à mesma altura, correspondendo ao filho mais alto. Este efeito é conseguido calculando a altura intrínseca de cada widget filho e alinhando-os em conformidade. Sem o IntrinsicHeight, as alturas seriam determinadas de forma independente, levando a um layout visualmente desequilibrado.

Widget IntrinsicWidth

O widget IntrinsicWidth no Flutter dimensiona seu filho para a largura intrínseca máxima do filho. É particularmente útil em cenários onde a largura ilimitada está disponível, e um widget filho poderia se expandir infinitamente. Em vez de esticar, o filho é limitado à sua largura intrínseca, criando um layout mais adequado.

Além disso, envolver uma coluna numa IntrinsicWidth garante que todos os seus filhos têm a mesma largura que o filho mais largo, permitindo um alinhamento consistente.

Se eu quiser mencionar as principais caraterísticas e comportamentos deste widget, posso categorizá-los em dois:

  • As restrições passadas pelo IntrinsicWidth ao seu filho seguem as restrições do pai. Se as restrições forem demasiado pequenas para satisfazer a largura intrínseca máxima do filho, este receberá menos largura. Por outro lado, se a largura mínima exceder a largura intrínseca máxima do filho, o filho será forçado a expandir-se.
  • Parâmetros opcionais, como stepWidth e stepHeight, ajustam a largura e a altura do filho a múltiplos destes valores, permitindo um controlo fino do alinhamento da disposição.
IntrinsicWidth(
  child: Row(
    children: [
      Container(
        height: 50,
        color: Colors.blue,
        child: Text('Short'),
      ),
      Container(
        height: 50,
        color: Colors.red,
        child: Text('Much longer text'),
      ),
    ],
  ),
);

Conclusão

Neste artigo, exploramos vários widgets Flutter menos conhecidos, mas eficazes, como AnimatedModalBarrier, FittedBox, SemanticsDebugger e muito mais. Esses widgets não apenas aprimoram a funcionalidade do seu aplicativo, mas também simplificam o processo de desenvolvimento. Também é bom ver o que está por trás desses widgets e aprender com eles para aplicar em nossas aplicações Flutter.

Isso é só o começo. Flutter e Dart estão cheios de jóias escondidas esperando para serem descobertas. Fiquem atentos ao próximo artigo, que irei revelar nas próximas 10 funcionalidades desconhecidas ou menos usadas em Dart e Flutter. Lembrem-se de subscrever a nossa newsletter, YouTube, ou redes sociais para receber as últimas actualizações.

Leave a Reply

Your email address will not be published. Required fields are marked *