Flutter - Apresentando Layouts - VI


Hoje veremos o conceito de Layouts no Flutter. (baseado na documentação oficial)

O Flutter é um SDK de aplicativo móvel do Google que ajuda a criar aplicativos móveis modernos para iOS e Android usando uma única (quase) base de código.

Se você não conhece o Flutter veja o meu artigo :  Flutter - Primeiros contatos e impressões

Continuando a quinta parte do artigo vamos tratar dos detectores de gestos.

Detectores de gestos

Nos exemplos do artigo anterior, vimos como responder à entrada do usuário usando alguns dos widgets comuns disponíveis. Esses widgets fornecem propriedades de retorno de chamada como onPressed e onChanged.

Outros widgets (como o Text ou o Container) não possuem uma maneira integrada de interagir com eles.

O Flutter nos oferece uma maneira fácil de torná-los interativos. Tudo o que você precisa fazer é envolver qualquer widget com um widget GestureDetector.

GestureDetector é um widget que detecta gestos e tenta reconhecer gestos que correspondem aos seus callbacks não nulos.

Se esse widget tiver um filho, ele será transferido para esse filho por seu comportamento de dimensionamento. Se não tiver um filho, ele se ajustará ao pai.

Por padrão, um GestureDetector com um filho invisível ignora os toques; esse comportamento pode ser controlado com behavior.

O GestureDetector também escuta eventos de acessibilidade e os mapeia para os callback. Para ignorar eventos de acessibilidade, defina excludeFromSemantics como true.

Abaixo temos um exemplo de código onde temos um widget Text envolvido com um widget GestureDetector.

GestureDetector(
      child: Text('Ola Flutter'),
      onTap: () {
        // fazer alguma coisa
      },
    );

Quando o texto for tocado, o callback onTap() será executado. Simples não ?

Vejamos um exemplo usando um detector de gesto onde vamos alterar a cor de um texto conforme o usuário toca no texto.

Para isso precisamos importar o pacote 'dart:math' no arquivo main.dart para poder usar a função Random() que gera valores int, bool ou double.

import 'dart:math';

Substitua o código da classe _MeuWidgetState pelo código a seguir:

  class MeuWidget extends StatefulWidget {
      @override
      _MeuWidgetState createState() => _MeuWidgetState();
    }
 class _MeuWidgetState extends State<MeuWidget> {
      Color textColor = Colors.black;
      @override
      Widget build(BuildContext context) {
        return GestureDetector(
          child: Text(
            'Toque aqui para mudar a cor',
            style: TextStyle(
              fontSize: 25,
              color: textColor,
            ),
          ),
          onTap: () {
            _fazerAlgo();
          },
        );
      }
      void _fazerAlgo() {
        setState(() {
          // Para usar Random() tem que importar 'dart:math' 
          int randomHexColor = Random().nextInt(0xFFFFFF);
          int opaqueColor = 0xFF000000 + randomHexColor;
          textColor = Color(opaqueColor);
        });
      }
    }
 

Não estamos limitado a detectar apenas um toque.

Existem muitos outros gestos que são fáceis de detectar.

Abaixo temos uma lista de gestos que podemos detectar:

  • onDoubleTap
  • onLongPress
  • onLongPressUp
  • onPanDown
  • onPanStart
  • onPanUpdate
  • onPanEnd
  • onPanCancel
  • onScaleStart
  • onScaleUpdate
  • onScaleEnd
  • onTap
  • onTapDown
  • onTapUp
  • onTapCancel
  • onHorizontalDragDown
  • onHorizontalDragUpdate
  • onHorizontalDragEnd
  • onHorizontalDragCancel
  • onVerticalDragStart
  • onVerticalDragDown
  • onVerticalDragUpdate
  • onVerticalDragEnd
  • onVerticalDragCancel

Podemos substituir cada um desses gestos e ver o seu comportamento.

Navigation

Para concluir o assunto sobre como tratar a iteração do usuário vamos falar sobre navegação ou Navigation.

Como podemos navegar para uma outra tela no Flutter ?

E como voltar para a tela anterior ?

Bem, como você poderia esperar, uma nova tela no Flutter é apenas um novo widget.

A maneira de obter esses widgets é chamada de rota, e o Flutter usa uma classe chamada Navigator para gerenciar as rotas.

Para mostrar uma nova tela, você usa o Navigator para empurrar(push) uma rota para uma pilha. Para descartar uma tela e voltar para a tela anterior, você tira a rota(pop) do topo da pilha.

Abaixo temos um trecho de código que mostra como você navegaria para um novo widget chamado SegundaTela:

Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => SeguntaTela(),
        ));

Veja na figura a seguir o resultado para a exibição do nosso layout:

A seguite temos o código que permite retornar para a tela anterior:


   Navigator.pop(context);

A seguir vamos criar um exempo de navegação entre duas telas.

Substitua todo o código de main.dart pelo código abaixo:

import 'package:flutter/material.dart';
    void main() {
      runApp(MaterialApp(
        title: 'Flutter',
        home: PrimeiraTela(),
      ));
    }
    class PrimeiraTela extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('## Primeira Tela ##')),
          body: Center(
            child: RaisedButton(
              child: Text(
                'Ir para a Segunda Tela',
                style: TextStyle(fontSize: 24),
              ),
              onPressed: () {
                _navegarParaSegundaTela(context);
              },
            )
          ),
        );
      }
      void _navegarParaSegundaTela(BuildContext context) {
        Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => SegundaTela(),
            ));
      }
    }
    class SegundaTela extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text('## Segunda Tela ##')),
          body: Center(
            child: RaisedButton(
              child: Text(
                'Retornar para a Primeira Tela',
                style: TextStyle(fontSize: 24),
              ),
              onPressed: () {
                _voltarParaPrimeiraTela(context);
              },
            ),
          ),
        );
      }
      void _voltarParaPrimeiraTela(BuildContext context) {
        Navigator.pop(context);
      }
    }

Passando dados para a próxima tela

Às vezes, você precisa enviar dados para a nova tela que está exibindo.

Isso é fácil de fazer, basta passar os dados como um parâmetro no construtor do widget SegundaTela.


class SegundaTela extends StatelessWidget {
      final String text;
      SegundaTela({Key key, @required this.text}) : super(key: key);
     ...
}

Essa sintaxe do construtor Dart pode parecer um pouco estranha para você.

Vamos entender..

Agora que o construtor está configurado, você pode passar dados quando ele for chamado partir de PrimeiraTela.

No exemplo a seguir estamos enviados dados digitados pelo usuário em um TextField na primeira tela e recebidos e exibidos na segunda tela.

Substitua todo o código de main.dart pelo código abaixo:

import 'package:flutter/material.dart';
void main() {
  runApp(MaterialApp(
    title: 'Flutter',
    home: PrimeiraTela(),
  ));
}
class PrimeiraTela extends StatefulWidget {
  @override
  _PrimeiraTelaState createState() {
    return _PrimeiraTelaState();
  }
}
class _PrimeiraTelaState extends State<PrimeiraTela> {
  // permite acessar o texto do TextField
  TextEditingController textFieldController = TextEditingController();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Primeira Tela')),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Padding(
            padding: const EdgeInsets.all(32.0),
            child: TextField(
              controller: textFieldController,
              style: TextStyle(
                fontSize: 24,
                color: Colors.black,
              ),
            ),
          ),
          RaisedButton(
            child: Text(
              'Ir para Segunda Tela',
              style: TextStyle(fontSize: 24),
            ),
            onPressed: () {
              _enviaDadosParaSegundaTela(context);
            },
          )
        ],
      ),
    );
  }
  // pega o texto no TextField e inicia a segunda tela
  void _enviaDadosParaSegundaTela(BuildContext context) {
    String textoAEnviar = textFieldController.text;
    Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => SegundaTela(text: textoAEnviar,),
        ));
  }
}
class SegundaTela extends StatelessWidget {
  final String text;
  // recebe os dados da PrimeiraTela como um parâmetro
  SegundaTela({Key key, @required this.text}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Segunda Tela')),
      body: Center(
        child: Text(
          text,
          style: TextStyle(fontSize: 24),
        ),
      ),
    );
  }
}

Muito bem...

Como passar dados de volta ?

Em outras ocasiões, você vai precisa enviar dados de volta para a tela anterior. O Flutter faz isso de uma maneira interessante.

A primeira tela inicia a segunda tela e, em seguida, aguarda um resultado, que pode ser usado após a conclusão da segunda tela.

Você vai notar o uso das palavras chaves async e await. (Se você conhece C# esta em casa)

O Dart facilita a execução de tarefas que você precisa esperar (como solicitações da Web ou tarefas de execução demoradas) usando await:

void iniciaSegundaTela(BuildContext context) async {
        // inicia SegundaTela e aguarda (await) ate o resultado terminar
        final resultado = await Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => SegundaTela(),
            ));
        // após o resultado da SegundaTela voltar
        // atualiza o widget Text com ele
        setState(() {
          text = resultado;
        });
  }

Na segunda tela, você passa os dados de volta, fornecendo-os como um parâmetro no método pop.


    Navigator.pop(context, 'Como vai você ?');

A seguir temos um exemplo onde mostramos como enviar dados de volta para a primeira tela.

Substitua todo o código de main.dart pelo código abaixo:

import 'package:flutter/material.dart';
void main() {
  runApp(MaterialApp(
    title: 'Flutter',
    home: PrimeiraTela(),
  ));
}
class PrimeiraTela extends StatefulWidget {
  @override
  _PrimeiraTelaState createState() {
    return _PrimeiraTelaState();
  }
}
class _PrimeiraTelaState extends State<PrimeiraTela> {
  String text = 'Texto primeira tela...';
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Primeira Tela')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Padding(
              padding: const EdgeInsets.all(32.0),
              child: Text(
                text,
                style: TextStyle(fontSize: 24),
              ),
            ),
            RaisedButton(
              child: Text(
                'Ir para Segunda Tela',
                style: TextStyle(fontSize: 24),
              ),
              onPressed: () {
                _awaitRetornaValorDaSegundaTela(context);
              },
            )
          ],
        ),
      ),
    );
  }
  void _awaitRetornaValorDaSegundaTela(BuildContext context) 
async {
    // inicia a SegundaTela e aguarda(await)
    // ate o resultado terminar
    final result = await Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => SegundaTela(),
        ));
    // Depois do resultado da segunda tela retornar
    // atualiza o widget Text
    setState(() {
      text = result;
    });
  }
}
class SegundaTela extends StatefulWidget {
  @override
  _SegundaTelaState createState() {
    return _SegundaTelaState();
  }
}
class _SegundaTelaState extends State<SegundaTela> {
  // permite acessar o texto em TextField
  TextEditingController textFieldController=TextEditingController();
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Segunda Tela')),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Padding(
            padding: const EdgeInsets.all(32.0),
            child: TextField(
              controller: textFieldController,
              style: TextStyle(
                fontSize: 24,
                color: Colors.black,
              ),
            ),
          ),
          RaisedButton(
            child: Text(
              'Envia texto de volta',
              style: TextStyle(fontSize: 24),
            ),
            onPressed: () {
              _enviaDadosDeVolta(context);
            },
          )
        ],
      ),
    );
  }
  // pega o texto em TextField e envia de volta para a PrimeiraTela
  void _enviaDadosDeVolta(BuildContext context) {
    String textoParaEnviarDeVolta = textFieldController.text;
    Navigator.pop(context, textoParaEnviarDeVolta);
  }
}

Assim nesta série de artigos, abordamos layouts estáticos e layouts dinâmicos com widgets que respondem à entrada do usuário. Criar widgets responsivos significa que precisamos lidar com as coisas que mudam, seja texto, cor, tamanho ou qualquer outra coisa que afete a interface do usuário. O valor dessas variáveis ​​é conhecido como state e os widgets que possuem estado são conhecidos como StatefulWidgets.

Gerenciar adequadamente o estado em Flutter é um grande tópico. Vimos duas maneiras de fazer. Uma delas usava uma variável de método na classe State e estava disponível para todos os widgets da classe.

À medida que a complexidade aumenta, não é prático incluir todo o layout em um único método build(), nem é uma boa prática permitir variáveis ​​globais.

Outra maneira de gerenciar o estado foi passar dados como um parâmetro para outro widget. Isso funciona muito bem quando um widget está chamando diretamente outro, mas pode ser complicado quando você precisa do estado de outro widget em algum lugar distante na árvore de widgets.

Aguarde em breve mais artigos sobre Flutter.

"Louvai ao Senhor dos senhores; porque a sua benignidade dura para sempre."
Salmos 136:3

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

Referências:


José Carlos Macoratti