Alura > Cursos de Mobile > Cursos de Flutter > Conteúdos de Flutter > Primeiras aulas do curso Flutter: crie animações explícitas no seu app

Flutter: crie animações explícitas no seu app

AnimatedFoo x FooTransition - Apresentação

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!

Objetivo do projeto

O projeto desse curso será um fork (adaptação) do projeto do curso anterior, criado pelo Matheus.

Interface de usuário do projeto de aplicativo com fundo marrom escuro. Na parte superior, há um texto branco que diz 'Escolha uma categoria'. Abaixo, existem quatro quadrados igualmente dimensionados com ícones e texto brancos. No canto superior esquerdo, há um ícone de uma criatura semelhante a um unicórnio com a palavra 'Criaturas' embaixo. No canto superior direito, um ícone de espada com a palavra 'Equipamentos' abaixo dele. No canto inferior esquerdo, um ícone de uma maçã com a palavra 'Itens' embaixo. E no canto inferior direito, um ícone de um crânio com a palavra 'Monstros' abaixo. Cada ícone e texto está em um fundo marrom mais claro, diferenciando cada opção de categoria.

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.

O que vamos aprender?

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é?

Pré-requisitos

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!

AnimatedFoo x FooTransition - Animando com TweenAnimationBuilder

Ó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!

Adicionando comportamento de destaque

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.

Base: animação implícita com o 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!

Variação de tamanho do ícone

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.

Recebendo o destaque

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á!

AnimatedFoo x FooTransition - Problema do loop infinito

Ó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.

Destaque condicional

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!

Animação indeterminada

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?

Por que usar animações explícitas?

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á!

Sobre o curso Flutter: crie animações explícitas no seu app

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:

Aprenda Flutter acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas