Criando Steppers Customizáveis no Flutter

Vinicius Oliveira
Flutter Brasil
Published in
3 min readApr 28, 2024

O que são Steppers?

Os Steppers, também conhecidos como “passos” ou “etapas”, são elementos de interface com o usuário que fornecem uma maneira estruturada de apresentar um processo sequencial para os usuários. Eles geralmente consistem em uma série de etapas numeradas, onde cada etapa representa uma parte específica do processo.

No contexto do Flutter, os Steppers são implementados usando widgets específicos que permitem aos desenvolvedores criar interfaces de usuário com múltiplas etapas de forma eficiente e personalizada.

Por que criar Steppers customizáveis?

Enquanto o Flutter oferece um widget Stepper embutido que atende a muitas necessidades básicas, há casos em que os desenvolvedores podem querer personalizar ainda mais a aparência e o comportamento dos Steppers para se adequarem ao design e à funcionalidade do seu aplicativo. Alguns dos motivos pelos quais criar Steppers customizados pode ser benéfico incluem:

  1. Alinhamento com a Identidade Visual do Aplicativo: Ao criar um Stepper customizado, os desenvolvedores podem garantir que ele se alinhe perfeitamente com a identidade visual do seu aplicativo, incorporando cores, fontes e estilos específicos.
  2. Adaptação à Experiência do Usuário: Personalizar Steppers permite aos desenvolvedores ajustar a experiência do usuário de acordo com as necessidades do aplicativo e do público-alvo, fornecendo uma interface mais intuitiva e agradável.
  3. Funcionalidades Específicas: Em alguns casos, os Steppers embutidos podem não oferecer todas as funcionalidades desejadas. Criar Steppers customizados permite aos desenvolvedores adicionar recursos específicos, como animações personalizadas, interações complexas ou comportamentos exclusivos.

Implementação

Antes de começarmos a implementar o Stepper customizado no Flutter, é uma boa definir enums para representar os diferentes estados e tipos de cada etapa do Stepper.

enum StepOptions {
cardapio(title: 'Pedidos'),
shoppingCart(title: 'Carrinho'),
aprovacao(title: 'Aprovação'),
entrega(title: 'Entrega'),
checkIn(title: 'Check-in');

const StepOptions({required this.title});
final String title;
}

Vamos começar criando uma classe para o nosso Stepper. Esta classe encapsulará a lógica e a interface do usuário do Stepper, permitindo-nos personalizar e reutilizar o componente conforme necessário.

class CustomStepperWidget extends StatelessWidget {
final List<StepOptions> steps;
final StepOptions currentStep;
final Function(StepOptions) onStepSelected;

const ShoppingStepperWidget({
super.key,
required this.steps,
this.currentStep = StepOptions.cardapio,
required this.onStepSelected,
});

@override
Widget build(BuildContext context) {
return FittedBox(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: steps.asMap().entries.map((entry) {
final index = entry.key;
return Row(
children: [
GestureDetector(
onTap: () => onStepSelected(steps[index]),
child: AnimatedContainer(
duration: const Duration(milliseconds: 500),
width: 35,
height: 35,
decoration: BoxDecoration(
color: index <= steps.indexOf(currentStep) ? Colors.green : Colors.grey,
shape: BoxShape.circle,
),
alignment: Alignment.center,
child: Text(
(index + 1).toString(),
style: const TextStyle(
fontWeight: FontWeight.bold,
color: Colors.white,
fontSize: 20,
),
),
),
),
Visibility(
visible: (index < steps.length - 1),
child: SizedBox(
width: 40,
child: AnimatedContainer(
duration: const Duration(milliseconds: 1000),
height: 4,
color: index <= steps.indexOf(currentStep) ? Colors.green : Colors.grey,
),
),
),
],
);
}).toList()),
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: steps.map((step) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Text(
step.title,
style: TextStyle(
color: currentStep.index >= step.index ? Colors.green : Colors.grey,
fontSize: 14,
fontWeight: FontWeight.bold,
),
),
);
}).toList(),
),
],
),
],
),
);
}
}

Vamos criar uma tela inicial HomeScreenpara demonstrar o uso do nosso widget CustomStepper. Nesta tela, vamos incluir o CustomStepperWidget e algumas etapas para ilustrar como funciona.

Scaffold(
appBar: AppBar(
title: const Text('Custom Stepper'),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CustomStepperWidget(
steps: const [
StepOptions.cardapio,
StepOptions.shoppingCart,
StepOptions.aprovacao,
StepOptions.entrega,
StepOptions.checkIn,
],
currentStep: currentStep,
onStepSelected: (step) => setState(() => currentStep = step),
),
],
),
Expanded(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Current Step: ${currentStep.title}',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
final nextStepIndex = currentStep.index + 1;
if (nextStepIndex < StepOptions.values.length) {
setState(() {
currentStep = StepOptions.values[nextStepIndex];
});
} else {
setState(() {
currentStep = StepOptions.values.first;
});
}
},
child: const Text('Next Step'),
),
],
),
),
),
],
))

Vídeo demonstrativo do funcionamento.

Link para a comunidade flutter do discord:

Participe da comunidade Flutter no Discord! Junte-se a nós em discussões e colaborações sobre Flutter. Clique aqui para acessar. Além disso, recomendo seguir criadores de conteúdo como Toshi Ossada, Brunno Marques e Edson Souza para mais conteúdo de qualidade.

--

--