Nell'articolo di oggi vedremo come realizzare una calcolatrice con funzioni elementari. In particolare vedremo come utilizzare una griglia per disporre i tasti e come usare un package da aggiungere alle dipendenze del progetto per valutare le espressioni.
Il progetto base e la tastiera
Creiamo un nuovo progetto e sostituiamo il codice standard nel file main.dart con questo.
import 'package:flutter/material.dart';
import './pages/calculator.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter calculator',
debugShowCheckedModeBanner: false,
home: Calculator(),
);
}
}
Creiamo quindi una sottocartella pages e dentro questa, il file calculator.dart con il seguente codice:
import 'package:flutter/material.dart';
List buttonNames = [
"7", "8", "9", "÷",
"4", "5", "6", "×",
"1", "2", "3", "−",
"0", ".", "C", "+",
"±", "(", ")", "="
];
class Calculator extends StatelessWidget {
const Calculator({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: _buildButtons(),
),
);
}
// Grid of buttons
Widget _buildButtons() {
return Material(
color: const Color(0xFFF2F2F2),
child: GridView.count(
crossAxisCount: 4, // 5x4 grid
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(8),
children: buttonNames.map<Widget>((caption) {
return _button(caption);
}).toList()),
);
}
// Buttons
Widget _button(text) {
return Padding(
padding: const EdgeInsets.all(4),
child: InkWell(
onTap: () {
// event on press
},
child: Container(
alignment: Alignment.center,
child: Text(
text,
style: const TextStyle(
color: Colors.black54,
fontSize: 30.0,
fontWeight: FontWeight.w300,
),
),
),
));
}
}
Il codice descrive una applicazione il cui corpo contiene un unico widget _buildButtons costituito da una GridView su 4 colonne in cui sono disposti i 20 pulsanti (quindi su 5 righe). I pulsanti a loro volta sono generati utilizzando la funzione .map sull'array di stringhe che contiene i simboli (disposti in un ordine particolare in modo che la disposizione in griglia è quella desiderata) e restituendo per ogni simbolo un widget _button che consiste sostanzialmente in un oggetto InkWell a cui assoceremo in seguito un funzione per gestire il click.
L'aspetto finale, dovrebbe essere qualcosa del genere
Gestiamo il tap sui tasti
Prima di associare una funzione ai tasti, per gestire lo stato della nostra calcolatrice, ovvero per poter poi visualizzare l'espressione numerica generata, occorre trasformare il nostro widget StatelessWidget in uno StatefulWidget. Con un refactoring sulla classe Calculator otteniamo questo
...
class Calculator extends StatefulWidget {
const Calculator({Key? key}) : super(key: key);
@override
State<Calculator> createState() => _CalculatorState();
}
class _CalculatorState extends State<Calculator> {
@override
Widget build(BuildContext context) {
...
Aggiungiamo alle variabili una nuova stringa che conterrà l'espressione numerica da valutare:
Ora possiamo scrivere la funzione da associare ad ogni pulsante:
void _buttonPressed(String text) {
setState(() {
if (text == "=") {
// calculate
} else if (text == "C") {
// Backspace
expression = expression.substring(0, expression.length - 1);
if (expression == "") expression = "0";
} else if (text == "±") {
// Plus/Minus
if (expression != "0") {
if (expression.substring(0, 1) != "-") {
expression = "-$expression";
} else {
expression = expression.substring(1);
}
}
} else {
// Default
if (expression == "0" && text != ".") expression = "";
expression += text;
}
});
}
Questa funzione (che aggiorna lo stato dell'applicazione) fa le seguenti operazioni:
- se si preme il tasto = elabora l'espressione (ancora da definire)
- se si preme il tasto C cancella l'ultimo carattere dell'espressione
- se si preme il tasto ± cambia il segno all'espressione
- negli altri casi aggiunge il carattere all'espressione
Per associare la funzione all'evento dei pulsanti onTap dei pulsanti:
...
child: InkWell(
onTap: () {
_buttonPressed(text);
},
child: Container(
...
Visualizziamo l'espressione
Modifichiamo il layout dell'applicazione per inserire un riquadro in cui visualizzare l'espressione da valutare e, vedremo dopo, anche il risultato delle operazioni.
Modifichiamo la funzione build in questo modo:
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(children: <Widget>[
Expanded(
flex: 1,
child: Container(
decoration: BoxDecoration(
border: Border.all(
color: Colors.green,
width: 1,
), //Border.all
),
alignment: Alignment.topLeft,
child: Container(
padding: const EdgeInsets.fromLTRB(14, 8, 14, 0),
child: Text(
expression,
style: const TextStyle(
color: Color.fromRGBO(227, 227, 227, 1),
fontSize: 48,
fontWeight: FontWeight.w400),
),
)),
),
Expanded(flex: 3, child: _buildButtons()),
]),
));
}
Abbiamo introdotto un layout a colonne, composto da due elementi, quello superiore che visualizzerà l'espressione numerica e quello inferiore con la tastiera. I due elementi hanno un rapporto di proporzione di 1 a 3 (proprietà flex del widget Expanded).
Valutiamo l'espressione e gestiamo il risultato
Per valutare l'espressione numerica possiamo utilizzare il package math_expressions. Aggiungiamolo alle dipendenze del progetto modificando il file pubspec.yaml. In particolare, sotto la riga relativa a cupertino_icons aggiungiamo il riferimento necessario, facendo attenzione a rispettare la stessa indentatura:
cupertino_icons: ^1.0.2
math_expressions: ^2.3.1
fatto questo, possiamo aggiungere la riga
import 'package:math_expressions/math_expressions.dart';
nel file calculator.dart.
La funzione che valuta il risultato dell'espressione è questa:
String _evaluateEquation(String formula) {
try {
Expression exp = (Parser()).parse(formula);
double res = double.parse(
exp.evaluate(EvaluationType.REAL, ContextModel()).toString());
return double.parse(res.toString()) == int.parse(res.toStringAsFixed(0))
? res.toStringAsFixed(0)
: res.toStringAsFixed(4);
} catch (e) {
return "Errore";
}
}
che, passata una espressione numerica, prova a valutarla come numero reale. Se l'esito è positivo, aggiunge eventuali cifre decimali, altrimenti restituisce un errore.
Per completare, utilizziamo questa nuova funzione nella _buttonPressed già definita, passandogli la variabile expression quando si preme il tasto =.
...
setState(() {
if (text == "=") {
expression = _evaluateEquation(expression);
} else if (text == "C") {
...
Come per gli articoli precedenti potete trovare questo codice tra le mie repo GitHub: https://github.com/luigimicco/flutter_calculator
Commenti
Posta un commento