Aplicativos responsivos Flutter
Descompactando a capacidade de resposta: Flutter e a arte de criar aplicativos que se encaixam como uma luva em celulares, tablets e na Web! 🚀
Conteudo
Iniciando o projeto
Vamos criar um novo projeto, executando o seguinte comando:
flutter create responsive_ui
Estrutura
Com seu projeto criado, vamos organizar a estrutura de pastas do projeto:
- lib - app // Crie as pastas a partir de agora. - modules - mobile - mobile_scaffold.dart - desktop - desktop_scaffold.dart - tablet - tablet_scaffold.dart - core/theme/ // No mesmo nível dos "módulos - colors.dart // É aqui que nossos widgets globais serão colocados, se necessário // Além do exemplo, você também pode adicionar widgets aqui // Especificamente para cada módulo, você pode ter uma pasta 'widgets' // para cada módulo. - widgets - app_bar.dart - drawer.dart - my_box.dart - my_tile.dart - responsive_layout.dart
Com tudo organizado, vamos começar a codificar 👨💻
Em seu arquivo principal main.dart, exclua o conteúdo inicial padrão e insira este código inicial.
void main() { runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, title: 'Flutter Demo', theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), useMaterial3: true, ), home: const ResponsiveLayout( mobileScreen: MobileScaffold(), tabletScreen: TabletScaffold(), desktopScreen: DesktopScaffold(), ), ); }
Em um main.dart padrão, não se surpreenda com essas classes e com os erros decorrentes de sua ausência; nós as criaremos uma a uma. Primeiro, vamos entender nosso ResponsiveLayout()
, que é responsável por verificar o dispositivo que estamos usando e renderizar o layout correto para cada opção. No nosso caso, essas opções são aplicativo – tablet – desktop, e vamos nos aprofundar nelas em breve.
ResponsiveLayout
Nosso RespondiveLayout é o componente que usaremos em nosso main.dart; ele implementa um LayoutBuilder. Ele tem as propriedades de que precisamos para distinguir o tipo de dispositivo que estamos usando com base no tamanho fornecido pelas restrições. Podemos conseguir isso usando os atributos fornecidos por nossas restrições:
– maxWidth
– minWidth
– minHeight
– maxHeight
Criando nosso ResponsiveLayout
Na pasta global widgets, crie um arquivo chamado responsive_layout.dart. Nele, implementaremos o seguinte código:
import 'package:flutter/material.dart'; class ResponsiveLayout extends StatelessWidget { const ResponsiveLayout({ super.key, required this.mobileScreen, required this.tabletScreen, required this.desktopScreen, }); final Widget mobileScreen, tabletScreen, desktopScreen; @override Widget build(BuildContext context) { return LayoutBuilder( builder: (context, constraints) { if (constraints.maxWidth < 600) { return mobileScreen; } else if (constraints.minWidth < 1100) { return tabletScreen; } else { return desktopScreen; } }, ); } }
Aqui podemos ver a implementação do LayoutBuilder
, o verdadeiro responsável por distinguir nossos layouts, e ele funciona de forma reativa. Ou seja, à medida que você aumenta ou diminui sua página, ele trará automaticamente a página correta definida pelos valores especificados. Você pode ver que comparamos nossas constraints.maxWidth
para verificar a largura do dispositivo e atribuir sua respectiva tela. Assim, podemos definir que uma largura menor que 600 é um celular, menor que 1.100 é um tablet e, além disso, definimos como um desktop.
Primeiro, vamos adicionar os valores em seu arquivo core/theme/colors.dart:
import 'package:flutter/material.dart'; abstract class AppColors { static final defaultBackground = Colors.grey[300]; }
Agora, podemos prosseguir com a programação de cada uma de nossas páginas.
Criar página para celular
Em nosso módulo móvel, no arquivo mobile_scaffold.dart, vamos inserir o seguinte código:
import 'package:flutter/material.dart'; import 'package:responsive_ui_web_app/app/core/utils/theme/colors.dart'; import 'package:responsive_ui_web_app/app/widgets/app_bar.dart'; import 'package:responsive_ui_web_app/app/widgets/drawer.dart'; import 'package:responsive_ui_web_app/app/widgets/my_box.dart'; import 'package:responsive_ui_web_app/app/widgets/my_tile.dart'; class MobileScaffold extends StatefulWidget { const MobileScaffold({super.key}); @override State<MobileScaffold> createState() => _MobileScaffoldState(); } class _MobileScaffoldState extends State<MobileScaffold> { @override Widget build(BuildContext context) { return Scaffold( appBar: const MyAppBar(), backgroundColor: AppColors.defaultBackground, drawer: const MyDrawer(), body: Column( children: [ AspectRatio( aspectRatio: 1.0, child: SizedBox( width: double.infinity, child: GridView.builder( itemCount: 4, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 2, ), itemBuilder: (context, index) => const MyBox(), ), ), ), Expanded( child: ListView.builder( primary: true, shrinkWrap: true, itemCount: 5, itemBuilder: (context, index) => const MyTile(), ), ), ], ), ); } }
Aqui temos uma página simples com uma AppBar
, uma Drawer
, uma Grid
e uma List
.
Não se assuste com os componentes que ainda não foram mencionados; nós os criaremos agora.
Criar a AppBar
Vá para o arquivo widgets/app_bar.dart e preencha-o com este código:
import 'package:flutter/material.dart'; class MyAppBar extends StatelessWidget implements PreferredSizeWidget { const MyAppBar({super.key}); @override Widget build(BuildContext context) { return AppBar( leading: IconButton( onPressed: () => Scaffold.of(context).openDrawer(), icon: const Icon( Icons.menu, color: Colors.white, ), ), backgroundColor: Colors.grey[900], ); } @override Size get preferredSize => const Size.fromHeight(46.0); }
Aqui temos uma AppBar
simples com o ícone de menu para que possamos abrir nossa Drawer.
Criar Drawer
Vá para o arquivo widgets/drawer.dart e preencha-o com este código:
import 'package:flutter/material.dart'; class MyDrawer extends StatelessWidget { const MyDrawer({super.key}); @override Widget build(BuildContext context) { return Drawer( backgroundColor: Colors.grey[300], child: const Column( children: [ DrawerHeader( child: Icon( Icons.favorite, ), ), ListTile( leading: Icon( Icons.home, ), title: Text('D A S H B O A R D'), ), ListTile( leading: Icon( Icons.chat, ), title: Text('M E S S A G E'), ), ListTile( leading: Icon( Icons.settings, ), title: Text('S E T T I N GS'), ), ListTile( leading: Icon( Icons.logout, ), title: Text('L O G O U T'), ), ], ), ); } }
Nosso Drawer
é bastante simples, com um cabeçalho e vários itens de ListTile
que simulam opções com ícones e texto
Criar MyBox e MyTile
Como esses componentes são apenas contêineres coloridos para fins de exemplo, vou examiná-los rapidamente aqui.
Vá para o arquivo widgets/my_box.dart
e widgets/my_tile.dart
e preencha-o com este código:
// MyBox import 'package:flutter/material.dart'; class MyBox extends StatelessWidget { const MyBox({super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Container( color: Colors.blue, ), ); } } // MyTile import 'package:flutter/material.dart'; class MyTile extends StatelessWidget { const MyTile({super.key}); @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Container( color: Colors.green, height: 80.0, ), ); } }
Esses objetos vão além de uma explicação e são praticamente idênticos. Agora que todos os nossos widgets estão prontos, você pode executar seu aplicativo.
Se quiser executar o flutter run em um emulador ou em seu telefone, já que até agora só criamos a tela do celular, as outras telas ficarão em branco.
No entanto, recomendo executar o aplicativo na Web para ver o efeito ao redimensionar a janela do navegador horizontalmente.
Use este comando: flutter run -d chrome
ou use o navegador da Web de sua preferência.
Então, vamos criar as outras duas telas, o que será mais fácil, pois aproveitaremos quase tudo o que fizemos até agora.
Criar página do tablet
Vá para o módulo tablet e, no arquivo tablet_scaffold.dart
, usaremos praticamente o mesmo conteúdo do mobile_scaffold.dart com algumas alterações de proporção. Aqui está o código:
import 'package:flutter/material.dart'; import 'package:responsive_ui_web_app/app/core/utils/theme/colors.dart'; import 'package:responsive_ui_web_app/app/widgets/app_bar.dart'; import 'package:responsive_ui_web_app/app/widgets/drawer.dart'; import 'package:responsive_ui_web_app/app/widgets/my_box.dart'; import 'package:responsive_ui_web_app/app/widgets/my_tile.dart'; class TabletScaffold extends StatefulWidget { const TabletScaffold({super.key}); @override State<TabletScaffold> createState() => _TabletScaffoldState(); } class _TabletScaffoldState extends State<TabletScaffold> { @override Widget build(BuildContext context) { return Scaffold( appBar: const MyAppBar(), drawer: const MyDrawer(), backgroundColor: AppColors.defaultBackground, body: Column( children: [ AspectRatio( aspectRatio: 4.0, child: SizedBox( width: double.infinity, child: GridView.builder( itemCount: 4, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 4, ), itemBuilder: (context, index) => const MyBox(), ), ), ), Expanded( child: ListView.builder( primary: true, shrinkWrap: true, itemCount: 9, itemBuilder: (context, index) => const MyTile(), ), ), ], ), ); } }
Observe que aqui temos apenas duas diferenças: nosso aspectRatio
antes era 2, agora é 4, e nosso crossAxisCount
para nossa grade também mudou de 2 para 4, para acomodar o tamanho maior do tablet.
Bem, como você pode ver, o código é o mesmo; você só precisa ajustar o tamanho, que é diferente para celular, tablet e Web, preenchendo o espaço que ficaria “vazio” se não fosse modificado.
Criar Desktop Page
Aqui faremos algumas alterações no layout, adicionando uma barra lateral extra à direita e mantendo o Drawer
aberto.
Para isso, adicionaremos uma Row
em nosso corpo, mantendo o Drawer
sempre aberto, dividindo a tela horizontalmente em três seções.
import 'package:flutter/material.dart'; import 'package:responsive_ui_web_app/app/core/utils/theme/colors.dart'; import 'package:responsive_ui_web_app/app/widgets/app_bar.dart'; import 'package:responsive_ui_web_app/app/widgets/drawer.dart'; import 'package:responsive_ui_web_app/app/widgets/my_box.dart'; import 'package:responsive_ui_web_app/app/widgets/my_tile.dart'; class DesktopScaffold extends StatefulWidget { const DesktopScaffold({super.key}); @override State<DesktopScaffold> createState() => _DesktopScaffoldState(); } class _DesktopScaffoldState extends State<DesktopScaffold> { @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.defaultBackground, appBar: const MyAppBar(), body: Row( children: [ //open drawer const MyDrawer(), Expanded( flex: 2, child: Column( children: [ AspectRatio( aspectRatio: 4.0, child: SizedBox( width: double.infinity, child: GridView.builder( itemCount: 4, gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: 4, ), itemBuilder: (context, index) => const MyBox(), ), ), ), Expanded( child: ListView.builder( primary: true, shrinkWrap: true, itemCount: 9, itemBuilder: (context, index) => const MyTile(), ), ), ], ), ), Expanded( child: Column( children: [ Expanded( child: Container( color: Colors.pink, ), ), ], )) ], ), ); } }
Observe que adicionamos widgets Expanded
, para que o espaço seja dividido de acordo com o peso (flex) de cada um.
Rodando no navegador
flutter run -d chrome
ou seu navegador preferido, você poderá ver esse resultado:
Algumas observações
Tenha cuidado com as importações, especialmente se você também estiver executando o projeto como um aplicativo. Às vezes, eu precisava limpar o projeto depois de executá-lo no navegador e, em seguida, executá-lo como um aplicativo, e vice-versa. Basta usar o flutter clean e executá-lo novamente.