
Counter App
En main.dart
import 'package:flutter/material.dart';
import 'package:hello_world_app/presentation/screens/counter/counter_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: CounterScreen()
);
}
}
A nuestro Screen Home estamos indicando que va a ser CounterScreen(), asi que vamos a crearo
/lib/presentation/screens/counter/counter_screen.dart
import 'package:flutter/material.dart';
class CounterScreen extends StatelessWidget {
const CounterScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('10', style:TextStyle( fontSize: 160, fontWeight: FontWeight.w100 )),
Text('Clicks', style:TextStyle( fontSize: 25))
],
),
),
floatingActionButton: FloatingActionButton(onPressed: () {
},
child: const Icon( Icons.plus_one ),
),
);
}
}
Material Design 3
Actualmente estamos teniendo los estilos de Material 2, vamos a modificarlo para tener material 3, ello es tan facil como agregar:
theme: ThemeData(
useMaterial3: true
),
Lo agregamos dentro de nuestro Widget Build en main.dart, quedando de la siguiente manera:
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.blue
),
home: const CounterScreen()
);
}
Tambié podemos agregarle una paleta de colores con:
colorSchemeSeed: Colors.blue
Cambiar el estado de la aplicación
Si quiero cambiar el estado no puedo hacerlo directamente en el StatelessWidget, asi que lo vamos a cambiar a un Stateful Widget
Nuestro código quedaría asi:
import 'package:flutter/material.dart';
class CounterScreen extends StatefulWidget {
const CounterScreen({super.key});
@override
State<CounterScreen> createState() => _CounterScreenState();
}
class _CounterScreenState extends State<CounterScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Counter Screen'),
),
body: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('10',
style: TextStyle(fontSize: 160, fontWeight: FontWeight.w100)),
Text('Clicks', style: TextStyle(fontSize: 25))
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.plus_one),
),
);
}
}
Ahora en _CounterScreenState podemos agregar nuestra variable
int clickCounter = 0;
la cual va a variar cuando pulsemos el boton más, eso lo hacemos agregandole la pariable al onPressed
onPressed: () {
clickCounter++;
},
Quedando nuestro _counterScreenState de la siguiente manera:
class _CounterScreenState extends State<CounterScreen> {
int clickCounter = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Counter Screen'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('$clickCounter',
style: const TextStyle(
fontSize: 160, fontWeight: FontWeight.w100)),
const Text('Clicks', style: TextStyle(fontSize: 25))
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
clickCounter++;
},
child: const Icon(Icons.plus_one),
),
);
}
}
Acá hay un problema, es que el state si esta aumentando y eso lo podemos ver si hacemos un hot reload, el problema es que tenemos que definir una función para que actualice la pantalla.
Lo solucionamos agregando un setState:
setState(() {
clickCounter++;
});
Ahora ya podemos ver en tiempo real como va sumando los números.
Cuando está el número 1, lo correcto sería que esté en singular, para ello podemos usar el operador ternario:
Text( (clickCounter == 1) ? 'Click' : 'Clicks',
style: const TextStyle(fontSize: 25))
Vamos a querer agregar más botones en la pare interior, para ello a nuestro floatingActionButton vamos agregarle un Column con un arreglo de children, vamos a poder agregar varios botones, alinearlos, darle espaciado y diferentes acciones
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
FloatingActionButton(
shape: const StadiumBorder(),
onPressed: () {
setState(() {
clickCounter++;
});
},
child: const Icon(Icons.plus_one),
),
const SizedBox(height: 10),
FloatingActionButton(
onPressed: () {
setState(() {
clickCounter--;
});
},
child: const Icon(Icons.exposure_minus_1_outlined),
),
],
)
Widgets Personalizados
En este momento nos afrontamos a que si queremos hacer algun cambio, como cambiar el redondeado, forma, etc, se hace boton por boton, pero hacer eso no es correcto, lo ideal es que herede alguna funciones, para ello vamos a tener que crear nuestros Widgets personalizados.
En este ejemplo vamos a crear un custom Widget llamado CustomButton
class CustomButton extends StatelessWidget {
final IconData icon;
final VoidCallback? onPressed;
const CustomButton({
super.key,
required this.icon,
this.onPressed,
});
@override
Widget build(BuildContext context) {
return FloatingActionButton(
shape: const StadiumBorder(),
onPressed: onPressed,
child: Icon(icon),
);
}
}
Esto lo podemos llamar y dar la propiedades respectivas:
children: [
CustomButton(
icon: Icons.refresh_outlined,
onPressed: () {
clickCounter = 0;
setState(() {});
},
),
const SizedBox(height: 10),
CustomButton(
icon: Icons.plus_one,
onPressed: () {
clickCounter++;
setState(() {});
},
),
const SizedBox(height: 10),
CustomButton(
icon: Icons.exposure_minus_1_outlined,
onPressed: () {
if (clickCounter == 0) return;
clickCounter--;
setState(() {});
},
),
],