Olá, pessoal! Meu nome é Ricarth Lima e serei o seu instrutor em mais um curso no maravilhoso mundo do Flutter.
Audiodescrição: Ricarth se autodeclara como um homem de pele dourada, cabelo crespo alto e escuro e barba baixa. Usa óculos de armação preta e quadrada. Está de camiseta azul marinho e fones intra auriculares de fio preto. Ao fundo, uma parede lisa iluminada por um degradê que vai do roxo ao rosa.
Boas-vindas ao curso de Flutter com animações explícitas!
O projeto desse curso será um fork (adaptação) do projeto do curso anterior, criado pelo Matheus.
Temos as criaturas, os equipamentos, os monstros, os itens, está tudo aqui. E qual será a funcionalidade que vamos implementar?
Como já devem ter notado, no curso anterior não tínhamos a animação do ícone da categoria Monstros, em que o crânio pulsa, diminuindo e aumentando, e piscando entre as cores amarela e branca.
Nossa liderança pediu para que quando houver, por exemplo, uma categoria nova ou um monstro novo dentro da categoria, tenha essa animação que chame a atenção da pessoa usuária. É justamente isso que vamos criar: uma animação que se repete usando nossos widgets de animação explícita.
Para realizar o que foi proposto, primeiro, aprenderemos a diferença entre animação implícita e explícita, e quando é importante usar animação explícita. Isso é bem interessante aprender para que, quando estivermos no mercado, possamos tomar essa decisão e seguir adiante sem ter problemas com desempenho, com manutenção de código e assim por diante.
Vamos conhecer também os widgets pré-prontos de animação explícita que, assim como os de animação implícita, nos ajudam muito a não ter que criar código do zero quando estamos desenvolvendo.
Também aprenderemos sobre os controladores de animação e como eles estão intimamente ligados com o ciclo de vida do Flutter. Ou seja, quando estivermos criando nossa aplicação, precisamos ter bastante cuidado para não ocorrerem vazamentos e fazermos o melhor uso possível do dispositivo da pessoa usuária.
Claro, nem todos os problemas conseguimos resolver apenas com widgets pré-prontos, então vamos aprender também a usar as animações explícitas personalizadas.
E, por fim, vamos aprender como adicionar curvas nas nossas animações explícitas.
Muito bacana, né?
Para seguir adiante neste curso, recomendamos principalmente os cursos anteriores da formação de Animações com Flutter.
Também é recomendado que você tenha feito a formação de Dart e a formação básica de Flutter.
Estamos bem animados e animadas para começar esse curso. Esperamos que você também esteja!
Se estiver gostando do curso e aprendendo bastante, não se esqueça de usar a hashtag #AprendiNaAlura
nas suas postagens para podermos acompanhar o seu progresso.
Vamos começar esse curso?! Até o próximo vídeo!
Ótimo! Agora que já conhecemos nosso projeto, é hora de implementar uma funcionalidade requisitada pela liderança. Nos solicitaram que qualquer uma dessas categorias possa ter um movimento de destaque para quando se deseja chamar a atenção da pessoa usuária.
Imagine que chegou uma nova categoria. Quando uma nova funcionalidade é implementada, é importante que a pessoa usuária saiba que ela foi adicionada. Isso é importante tanto para ela quanto para nós que estamos desenvolvendo.
Outra situação é quando algo muda, por exemplo, dentro da categoria Monstro. Imagine que chegou um novo monstro que é do interesse da pessoa usuária visualizar. Ter uma maneira de chamar a atenção da pessoa usuária para essa informação pode ser muito útil.
Então, essa é a nossa missão: criar uma animação discreta, mas eficaz, para chamar a atenção da pessoa usuária para a categoria específica que desejarmos.
Vamos fazer isso!
A primeira parte é analisar o código, pois não fomos nós que o criamos, e tentar alcançar o objetivo que queremos.
Se desejarmos modificar os itens que aparecem na tela inicial, vamos diretamente no meu VSCode, no arquivo main.dart
, e procuramos por home
. Notaremos que home
possui um widget chamado Categories()
, ou seja, categorias. Perfeito! Isso faz sentido para nós. Vamos pressionar "Ctrl" e clicar nele.
Isso nos leva ao arquivo categories.dart
. Ele tem uma lista, mas isso é o drawer
, não importa muito para nós. Em seguida, temos um AppBar
, que também não nos interessa.
Então, chegamos ao corpo (body
) desse scaffold, que tem como filho um GridView
, e nós estamos procurando por elementos no GridView
. Ótimo! Ele gera, baseado na linha 74, nesse widget chamado Category
. Uma categoria apenas, o que parece fazer sentido com o que precisamos. Então vamos pressionar "Ctrl" mais uma vez e clicar em Category()
para abri-lo.
Agora, estamos no arquivo category.dart
. Começamos a notar que o nosso widget Category()
precisa de uma categoria que é uma String. Com essa categoria, ele vai construir na tela a imagem e o botão da forma que ela se comporta.
Na linha 51, notamos que a imagem está entre as linhas 51 a 53, então é exatamente aqui que precisamos animar:
category.dart
child: Center(
child: Image.asset("$iconPath$category.png"),
),
Nesse trecho de código, vamos dar um comportamento para essa imagem.
Mas, antes de fazer isso, precisamos proporcionar a opção de uma categoria estar ou não em destaque, o que será muito fácil fazer. Precisamos apenas modificar esse Category()
para receber um booleano que informa se está ou não em destaque.
Então, na linha 10, dentro dos atributos, após o construtor, vamos colocar um final bool isHighligh
. O construtor começa a dar erro, então, na linha 9, depois de Category
, vamos colocar uma vírgula e required this.isHighligh
.
category.dart
class Category extends StatelessWidget {
Category({Key? key, required this.category, required this.isHighligh}) : super(key: key);
final String category;
final bool isHighligh;
// código omitido
Mas, se fizermos isso, ele vai dar erro no categories
, porque ele não está implementando o highlight (destaque) em todos os casos. Mas, a maioria das categorias vai ter esse isHighlight
como falso; apenas algumas estarão como verdadeiro.
Nesse caso, vamos configurar que esse atributo não seja, de fato, nomeado obrigatório. Para fazer isso, vamos remover o required
e atribuir um valor padrão como falso (false
):
class Category extends StatelessWidget {
Category({Key? key, required this.category, this.isHighligh = false}) : super(key: key);
// código omitido
Então, se a pessoa quiser, ela muda esse valor para verdadeiro para ter um highlight. Se não quiser, mantém falso.
Tween
Agora vamos imaginar como faríamos, de fato, essa animação, com todo o conhecimento que temos até agora sobre animações.
Vamos dar um "Ctrl + X" nas linhas 53 a 55, para colocar os widgets Center
e Image
na área de transferência.
Trecho recortado de
category.dart
Center(
child: Image.asset("$iconPath$category.png"),
),
Agora, após o child
que ficou, vamos cercar com o TweenAnimationBuilder
. Vamos selecionar a segunda opção entre as sugestões do VS Code. Ao fazer isso, recebemos algumas propriedades como parâmetro.
Vale lembrar que o TweenAnimationBuilder
precisa ser um double. Então, após o TweenAnimationBuilder
, na linha 53, vamos adicionar <double>
, resultando em TweenAnimationBuilder<double>()
.
Sabemos que a propriedade Tween
vai variar, normalmente, entre 0 e 1, mas não queremos uma imagem que desapareça completamente e depois apareça gigante. Queremos que ela apenas aumente e diminua um pouco. Então, esse Tween
pode variar entre 0.8
e 1
. Então, teremos: tween: Tween(begin: 0.8, end: 1)
.
A duração (duration
) pode ser um segundo. Então, passamos const Duration(seconds: 1)
. No Builder
, vamos usar um construtor padrão que vem do callback. Para isso, damos "Ctrl + Espaço" e selecionamos a segunda opção, resultando em builder: (context, value, child)
.
Por fim, vamos adicionar o return
na linha 54 e pressionar "Ctrl + V" para pegar os widgets que estavam na área de transferência. Podemos substituir a última vírgula por um ponto e vírgula.
child: TweenAnimationBuilder<double>(
tween: Tween (begin: 0.8, end: 1),
duration: const Duration(seconds: 1),
builder: (context, value, child) {
return Center(
child: Image.asset("$iconPath$category.png"),
); // Center
Pronto. Já temos a base para uma animação legal!
Agora, falta variar o tamanho do ícone para que ele aumente e diminua. Para fazer isso, na imagem (Image()
, linha 58), vamos adicionar uma vírgula após o png
. Então, vamos configurar uma altura (height
) para 78, que talvez seja um bom tamanho, vezes esse valor (value
) que vem do nosso construtor.
Para que a imagem ocupe o tamanho da altura, precisamos fazer um fit
na linha 61, com o valor BoxFit.fitHeight
para ela se encaixar com a altura.
child: TweenAnimationBuilder<double>(
tween: Tween (begin: 0.8, end: 1),
duration: const Duration(seconds: 1),
builder: (context, value, child) {
return Center(
child: Image.asset("$iconPath$category.png",
height: 78 * value,
fit: BoxFit.fitHeight,
), //Image.asset
); // Center
Salvamos.
Para podermos testar essa animação, precisamos voltar em categories.dart
e fazer com que, pelo menos, uma das categorias tenha o Highligh
como True
.
Na linha 74, temos o Category(category: e)
, lembrando que esse e
é uma string que contém o ID da categoria. Após o e
, vamos adicionar isHighligh: e == "monsters"
.
categories.dart
children: categories.keys
.map((e) => Category(
category: e,
isHighligh: e == "monsters",
))
.toList(),
O que esse código está fazendo? Se for igual a monsters
, isHighligh
vai ser True
. Se não for, vai continuar sendo False
.
Vamos salvar e abrir o emulador para verificar o que acontece. Podemos recarregá-lo clicando no botão "Reload" no canto superior direito.
Todos os ícones de todas as categorias se movem um pouco, diminuindo e aumentando de tamanho apenas uma vez, e não apenas a categoria Monstros. Isso aconteceu porque, na verdade, não usamos o Highligh
para configurar se a animação vai acontecer ou não.
Esses detalhes e alguns outros para chegar ao final dessa animação (como, por exemplo, a animação acontecer mais de uma vez), implementaremos logo na sequência.
Até lá!
Ótimo! A animação começou, mas não exatamente da forma que queremos. Por exemplo, todas as categorias estão aumentando juntas, mas queremos que apenas a categoria que tem o highlight (destaque) como true
aumente. Além disso, a animação precisa aumentar e diminuir constantemente e pelo tempo que for necessário, ou seja, ela não pode acabar.
Vamos para o código tentar resolver isso.
No VS Code, dentro do arquivo category.dart
, vamos primeiramente resolver a questão do highlight. Se o highlight for true
, queremos que o valor influencie na altura. Se for false
, não queremos que influencie.
A forma mais simples de fazer isso talvez seja usar um operador ternário. Então, na linha 60, vamos multiplicar por valor ou por 1, dependendo do valor do isHighligh
.
Nosso operador ternário começará com (isHighligh)?
. Se for verdadeiro, vamos usar o valor do nosso TweenAnimationBuilder
, ou seja, value
. Se não for, vamos usar 1
. Por fim, isolamos toda essa expressão com parênteses.
Então, nossa animação ficará assim:
category.dart
child: TweenAnimationBuilder<double>(
tween: Tween (begin: 0.8, end: 1),
duration: const Duration(seconds: 1),
builder: (context, value, child) {
return Center(
child: Image.asset("$iconPath$category.png",
height: 78 * ((isHighligh)? value : 1),
fit: BoxFit.fitHeight,
), //Image.asset
); // Center
Perfeito!
78 é o valor base. Caso esteja com o highlight ativado, vai multiplicar com o valor que TweenAnimationBuilder
está variando. Se não estiver, multiplica por um - sabemos que qualquer número multiplicado por um, é ele mesmo.
Vamos salvar, pedir para reiniciar e abrir o emulador. Ótimo! Só o ícone da categoria Monstros se mexeu. Perfeito!
O próximo passo é fazer essa animação continuar indeterminadamente. Com tudo que já sabemos sobre animações, podemos entender que, se usarmos o onEnd
, conseguimos controlar isso. Então, vamos lá!
Primeiro, precisamos variar o valor de fim do Tween()
, porque sabemos que no TweenAnimationBuilder
é o valor de fim que determina se a animação vai acontecer ou não.
Se precisamos variar esse valor, não podemos mais usar um StatelessWidget()
. Então, na linha 8, vamos clicar na lâmpada para refatoração e pedir para converter para um Stateful
. Isso resulta em:
class Category extends StatefulWidget {
const Category({Key? key, required this.category, this.isHighligh = false})
: super(key: key);
final String category;
final bool isHighligh;
@override
State<Category> createState() => _CategoryState();
}
class _CategoryState extends State<Category> {
final ApiController apiController = ApiController();
Future<List<Entry>> getEntries() async {
return await apiController.getEntriesByCategory(category: widget.category);
}
// código omitido
Para conferir todas as mudanças, acesse o código completo no GitHub do curso.
Ótimo!
Agora, precisamos criar um valor que, de fato, vai variar. Nas linhas 24 e 25, após o Future<List<Entry>>
, vamos criar um double
chamado endValue
, que vai começar como 1
:
double endValue = 1;
Agora, vamos usar esse endValue
no nosso end
, na linha 61:
child: TweenAnimationBuilder<double>(
tween: Tween (begin: 0.8, end: endValue),
// código omitido
O próximo passo é, de fato, variar esse valor dependendo da condição do final da animação. Como fazemos isso?
Na linha 62, vamos dar um "Enter" e pedir um onEnd
, que recebe uma função. Nessa função, faremos um teste.
Se, ao terminar, endValue
for igual a 1
, sabemos que precisamos diminuir de volta. Então, faremos setState()
para configurar endValue
igual a 0.8
, o valor de início. Caso não seja igual a 1, ele terminou em 0.8. Então, faremos else
para mudar o valor de EndValue
para 1
com o setState()
.
child: TweenAnimationBuilder<double>(
tween: Tween (begin: 0.8, end: endValue),
duration: const Duration(seconds: 1),
onEnd: () {
if (endValue == 1) {
setState(() {
endValue = 0.8;
});
} else {
setState(() {
endValue = 1;
});
}
},
// código omitido
Ou seja: chegou no onEnd
, terminou a animação, o valor final é 1? Ótimo, vamos diminuir para 0.8. Chegou no onEnd
, se valor não é 1, significa que é 0.8. Então, volta para 1. E isso se repete sempre, continuando indeterminadamente.
Vamos reiniciar a aplicação e abrir o emulador para testar. Funcionou! O ícone de Monstros fica pulsando, aumentando e diminuindo enquanto estamos nessa tela. Isso parece resolver o nosso problema.
Em teoria, só precisaríamos adicionar a questão da cor, para ficar amarelo, o que seria simples de fazer.
Mas, é isso? Será que o curso já acabou? Ou será que tem algum problema nessa nossa abordagem?
Entendemos que, com criatividade e a nossa capacidade de resolução de problemas, podemos resolver um problema usando a ferramenta que já conhecemos, que, no caso, era o TweenAnimationBuilder
das animações implícitas.
Mas há um problema muito grande nisso. O próprio Flutter recomenda que você não use animações implícitas para os casos onde a animação se repete indeterminadamente. Nesses casos, é recomendado, por uma questão de desempenho, usar as animações explícitas.
Afinal, precisamos fazer muita "gambiarra" para fazer essa animação acontecer. Precisamos verificar o valor de fim e ficar variando esse valor para que a animação aconteça. Se precisarmos mudar esse valor no futuro, teremos um problema ainda maior.
Precisamos também criar uma variável externa para poder controlar esse valor, e até mudar o widget para Stateful. Precisamos ficar visualizando o onEnd
. Todas essas estratégias são artimanhas que conseguimos elaborar para usar o TweenAnimationBuilder
a fim de criar uma animação que se repete.
No entanto, existe uma ferramenta recomendada pelo Flutter para fazer esse tipo de animação, em que precisamos ter mais controle e também uma repetição por tempo indeterminado.
Foi bem importante entender que conseguimos resolver alguns problemas com a animação implícita, recurso que já conhecíamos, usando artifícios de lógica de programação - as tais "gambiarras". Mas é preciso aprender também a usar a ferramenta correta para fazer esse tipo de animação: as animações explícitas.
Faremos isso no próximo vídeo. Até lá!
O curso Flutter: crie animações explícitas no seu app possui 121 minutos de vídeos, em um total de 51 atividades. Gostou? Conheça nossos outros cursos de Flutter em Mobile, ou leia nossos artigos de Mobile.
Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:
Impulsione a sua carreira com os melhores cursos e faça parte da maior comunidade tech.
1 ano de Alura
Assine o PLUS e garanta:
Formações com mais de 1500 cursos atualizados e novos lançamentos semanais, em Programação, Inteligência Artificial, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.
A cada curso ou formação concluído, um novo certificado para turbinar seu currículo e LinkedIn.
No Discord, você tem acesso a eventos exclusivos, grupos de estudos e mentorias com especialistas de diferentes áreas.
Faça parte da maior comunidade Dev do país e crie conexões com mais de 120 mil pessoas no Discord.
Acesso ilimitado ao catálogo de Imersões da Alura para praticar conhecimentos em diferentes áreas.
Explore um universo de possibilidades na palma da sua mão. Baixe as aulas para assistir offline, onde e quando quiser.
Acelere o seu aprendizado com a IA da Alura e prepare-se para o mercado internacional.
1 ano de Alura
Todos os benefícios do PLUS e mais vantagens exclusivas:
Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos, corrige exercícios e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com a Luri até 100 mensagens por semana.
Aprenda um novo idioma e expanda seus horizontes profissionais. Cursos de Inglês, Espanhol e Inglês para Devs, 100% focado em tecnologia.
Transforme a sua jornada com benefícios exclusivos e evolua ainda mais na sua carreira.
1 ano de Alura
Todos os benefícios do PRO e mais vantagens exclusivas:
Mensagens ilimitadas para estudar com a Luri, a IA da Alura, disponível 24hs para tirar suas dúvidas, dar exemplos práticos, corrigir exercícios e impulsionar seus estudos.
Envie imagens para a Luri e ela te ajuda a solucionar problemas, identificar erros, esclarecer gráficos, analisar design e muito mais.
Escolha os ebooks da Casa do Código, a editora da Alura, que apoiarão a sua jornada de aprendizado para sempre.