Flutter — Caso de uso — Como saber e manter o tamanho de um widget?

Gabriel Araujo
tech.revelo
4 min readDec 3, 2021

--

Oi pessoal, tudo bem?

Nos últimos dias passamos por um desafio aqui na Revelo e notei que é uma pergunta comum para desenvolvedores Flutter. O desafio foi como obter o tamanho ou posição de um widget após sua renderização.

Um widget, por si só, não tem uma posição ou um tamanho pré-determinados. Estes são definidos após sua renderização e ficam “dentro” de um componente chamado RenderBox que é criado na construção da tela. Precisamos, portanto, acessar esse RenderBox após a renderização da tela e, com ele, pegar as dimensões necessárias.

O desafio

Podemos nos deparar com um desafio como esse em diversas situações e, no nosso caso, precisávamos manter o tamanho de um botão quando trocássemos seu estado (para fazer uma animação de carregamento dentro dele).

A animação tinha tamanho diferente do texto que recebemos da API e, como esse texto pode mudar, definir dimensões fixas não seria a melhor estratégia para manter o tamanho do botão.

Para ilustrar melhor:

Objetivo

O que já tínhamos

Nós já tínhamos um widget de botão feito e utilizado em várias partes do app, o ReveloContainedButton. Ele já era um Stateless Widget com um ElevatedButton e embutia espaçamentos, lógica de ativo/inativo, tema de cores e estilo de texto e elevação.

Para melhor entendimento, levem em conta que a classe Dimens é a nossa classe de dimensões padrão, sendo Dimens.base = 16.0.

Como podem ver, esse widget tem suas dimensões definidas pelo tamanho do Text recebido. Também não tínhamos a animação de carregamento (daqui para frente vou chamar de loading, belê?).

A solução inicial

A solução mais simples seria colocar nosso widget de loading, com renderização dependente de um booleano isLoading.

Essa solução, porém, faz o botão mudar suas dimensões para a mesma do widget de loading. Para evitar o pulo, colocamos um AnimatedSize na transição.

Botão animado, sem manter as dimensões originais

É claro que não ficamos muito satisfeitos por não seguirmos o design, então continuamos tentando!

E então como fazer para que o botão mantenha suas dimensões?

A primeira coisa que precisamos fazer é transformar nosso botão em um Stateful Widget. Com isso conseguimos executar funções logo após a renderização e definir variáveis fora do método build do widget.

Depois, ainda é necessário saber qual é o elemento, naquele contexto, que queremos utilizar.

O Flutter faz essa identificação utilizando Keys, então vamos criar uma GlobalKey() para o nosso botão.

Importante: Nosso ElevatedButton tem padding interno e, por isso, seria um pouquinho mais complicado manter seu tamanho com o widget interno diferente se utilizássemos uma Key nele. Por isso a atribuímos ao widget Text().

Até agora só identificamos qual é o elemento que precisamos. Vamos então pegar as dimensões dele?

  1. Começamos criando as variáveis que receberão as dimensões de altura e largura do nosso widget Text(): buttonTextHeight e buttonTextWidth.
  2. Depois disso, criamos uma função _getButtonTextSize(), que atribui valores a essas variáveis de acordo com o contexto atual. Podemos também criar funções para obter outras informações, como a posição do elemento na tela.

Então, vamos executar essa função somente no initState() do widget! Mas tem um detalhe importante: dependemos da renderização dos widgets estar completa (lembra daquele RenderBox? Ele precisa já estar disponível!).

Também vamos colocar uma condicional para que a função seja executada depois que isso aconteça, utilizando a instância atual do Mixin WidgetsBinding no initState() e o método addPostFrameCallback(), que nos permite executar funções uma vez assim que a fila de renderização acabar.

Ficou assim:

Agora que as variáveis buttonHeight e buttonWidth são atribuídas após a renderização do widget, precisaremos utilizá-las né?

Utilizando as dimensões

Nosso widget de loading não tem parâmetros de dimensões definidos, então criamos um container que tem buttonTextHeight e buttonTextWidth como height e width, respectivamente.

Bônus: no nosso caso, tivemos uma outra pegadinha! Precisamos ajustar o padding desse container de acordo com a buttonTextWidth e o tamanho do ícone para mantê-lo sempre centralizado horizontalmente e com o tamanho ideal.

Foi uma conta simples: subtraímos o tamanho do ícone da largura total e dividimos por 2!

Finalmente, conseguimos o resultado desejado! :)

Resultado

E aí, por quais desafios vocês tem passado? Estamos sempre disponíveis para ajudar!

--

--