Aplicacions amb Flutter, Dart i Flame

Tutorial Flutter Flame Projectes   Recursos CITCEA
Exemples Dart Dades pràctiques     Inici

Giny

Els ginys són elements gràfics que ens permeten fer l'estructura bàsica del programa principal o d'una classe. Poden ser de dos tipus, ginys sense estat (StatelessWidget) i ginys amb estat (StatefulWidget).

Els ginys de qualsevol tipus poden ser sensibles a les accions de l'usuari si els tanquem dins d'un detector d'accions.

Quan creem una aplicació buida, el programa principal main.dart conté un giny sense estat.

import 'package:flutter/material.dart';
void main() {
  runApp(const MainApp());
}
class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: Scaffold(
        body: Center(
          child: Text('Hello World!'),
        ),
      ),
    );
  }
}

A les classes podem tenir els dos tipus de ginys: amb estat o sense. Els ginys sense estat sémpren quan el seu contingut no varia; en canvi, els ginys amb estat estan pensats per quan el seu contingut pot variar durant el funcionament de l'aplicació.

Ginys sense estat

Quan creem una classe amb un giny sense estat, tindrem:

import 'package:flutter/material.dart';
class MyWidget extends StatelessWidget {
  const MyWidget({super.key});
  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}

En aquest cas, el noms MyWidget és genèric i s'haurà de personalitzar per a cada giny. Les parts d'un giny sense estat són:

import 'package:flutter/material.dart';  // Importació dels fitxers necessaris
class MyWidget extends StatelessWidget {  // Declaració de la classe, que hereta de StatelessWidget
  const MyWidget({  // Constructor de la classe
    super.key  // Identificador intern del giny dins de l'arbre de ginys
  });

  @override  // Indica que anem a sobreescriure el mètode que ve a continuació
  Widget build(  // Mètode ja existent a la classe StatelessWidget, que sobreescrivim
      BuildContext context  // Posició del giny a l'arbre de ginys
    ) {
    return const Placeholder();  // Retorn del mètode, en aquest cas un giny sense contingut
  }
}

Ginys amb estat

I si creem una classe amb un giny amb estat, tindrem:

import 'package:flutter/material.dart';
class MyWidget extends StatefulWidget {
  const MyWidget({super.key});
  @override
  State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}

Les parts d'un giny amb estat són:

import 'package:flutter/material.dart';  // Importació dels fitxers necessaris
class MyWidget extends StatefulWidget {  // Declaració de la classe, que hereta de StatefulWidget
  const MyWidget({  // Constructor de la classe
    super.key  // Identificador intern del giny dins de l'arbre de ginys
  });

  @override  // Indica que anem a sobreescriure el mètode que ve a continuació
  State<MyWidget> createState() => _MyWidgetState();  // Mètode ja existent a la classe StatefulWidget,
                                                      //   que sobreescrivim
                                                      // Aquest mètode és la connexió entre el giny
                                                      //   i la seva classe State
                                                      // Crea un estat i el retorna
}

class _MyWidgetState extends State<MyWidget> {  // Declaració de la classe d'estat, que hereta de State
  @override  // Indica que anem a sobreescriure el mètode que ve a continuació
  Widget build(  // Mètode ja existent a la classe StatefulWidget, que sobreescrivim
    BuildContext context  // Posició del giny a l'arbre de ginys
  ) {
    return const Placeholder();  // Retorn del mètode, en aquest cas un giny sense contingut
  }
}

En aquest cas, els noms MyWidget i _MyWidgetState són genèrics i s'hauran de personalitzar per a cada giny. Imaginem que volem crear un selector, que ens permet seleccionar entre diverses opcions; podríem fer:

import 'package:flutter/material.dart';
class Seleccio extends StatefulWidget {
  const Seleccio({super.key});

  @override
  State<Seleccio> createState() => _SeleccioState();
}

class _SeleccioState extends State<Seleccio> {
  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        ...
      ],
    );
  }
}

L'hem anomenat Seleccio y hem hagut de canviar MyWidget per Seleccio a tot arreu, sis canvis en total. Atenció: Si es va amb compte, es pot fer el canvi una sola vegada i que es canviï automàticament als sis llocs. Quan es crea el giny, les paraules MyWidget apareixen seleccionades simultàniament als sis llocs; si llavors escrivim el nom desitjat, ens ho canviarà de cop a tot arreu.

Variables pròpies del giny

En un giny, les variables que no es modifiquen durant el funcionament les podem definir dins o fora del giny, però les que es modifiquen o se'ls assigna un valor que no és constant s'han de definir dins el giny. Per exemple, aquesta definició és vàlida:

...
class _PantPrincipalState extends State<PantPrincipal> {
  @override
  Widget build(BuildContext context) {
    List<String> salutacions = ["Bon dia", "Bon vespre", "Bona nit"];
    int ind = Random().nextInt(salutacions.length);
    return Padding(
...

Aquesta també ho és:

...
class _PantPrincipalState extends State<PantPrincipal> {
  List<String> salutacions = ["Bon dia", "Bon vespre", "Bona nit"];
  @override
  Widget build(BuildContext context) {
    int ind = Random().nextInt(salutacions.length);
    return Padding(
...

Però aquesta no ho és:

...
class _PantPrincipalState extends State<PantPrincipal> {
  List<String> salutacions = ["Bon dia", "Bon vespre", "Bona nit"];
  int ind = Random().nextInt(salutacions.length);
  @override
  Widget build(BuildContext context) {
    return Padding(
...

perquè la llista salutacions i la seva llargada no estan disponibles abans d'entrar a la definició del giny.

Ginys reutilitzables

Quan en una aplicació necessitem posar diversos elements que són formalment idèntics, convé crear un giny reutilitzable que es pugui repetir, personalitzat, en diversos llocs. Aquests ginys tindran unes variables que actuaran com a paràmetes (les que el permeten personalitzar) i probablement algunes funcions (corresponents a les possibles accions sobre l'element.

Els trossos de programa que hi ha en aquest apartat s'han agafat d'aquest apartat de l'exemple de càlcul de la resistència per a un led, que pot ser útil per entendre com fer servir els ginys reutilitzables.

L'element reutilitzable el crearem en un fitxer i consistirà en un giny amb estat.

Cada instància d'aquest elements tindrà algun identificador fix (per exemple, un títol) diferent. Caldrà, doncs, crear una variable per a l'identificador i fer que actuï com a paràmetre. Aquesta variable l'hem de declarar com a final a l'inici de la classe i posar-la com a paràmetre obligatori a la definició de la classe. El this indica que és una variable de la pròpia classe. Per exemple:

class EntraNum extends StatefulWidget {
  final String titol;
  const EntraNum({super.key, required this.titol});

  @override
  State<EntraNum> createState() => _EntraNumState();
}
...

És probable que el giny reutilitzable tingui també un o més valors que es modifiquen. Les variables corresponents a aquests valors s'han de declarar al giny que el crida; per exemple, a la pantalla principal.

...
class _PantPralState extends State<PantPral> {
  double caigudaTens = 1.6;
  @override
  Widget build(BuildContext context) {
...

En l'element genèric, cal afegir com a paràmetres el valor que cal mostrar i les funcions que permeten modificar (per exemple, incrementar i decrementar) el valor de la variable.

class EntraNum extends StatefulWidget {
  final String titol;
  final String valor;
  final Function() incrementar;
  final Function() decrementar;
  const EntraNum({
    super.key,
    required this.titol,
    required this.valor,
    required this.incrementar,
    required this.decrementar,
  });

  @override
  State<EntraNum> createState() => _EntraNumState();
}

class _EntraNumState extends State<EntraNum> {
  @override
  Widget build(BuildContext context) {
    return Container(
...
          children: [
            Text(widget.titol),
            Text(widget.valor),
...
                  child: IconButton(
                    onPressed: () {
                      widget.decrementar();
                    },
                    icon: Icon(Icons.remove),
                  ),
                ),
...

I a la pantalla principal cal definir les funcions.

...
class _PantPralState extends State<PantPral> {
  double caigudaTens = 1.6;  // V
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        SelectorTens(),
        Padding(
          padding: const EdgeInsets.only(left: 20, right: 20),
          child: Row(
            children: [
              Expanded(child: EntraNum(titol: "Caiguda de tensió", 
                valor: caigudaTens.toStringAsFixed(1).replaceAll('.', ','),
                incrementar: (){setState(() {caigudaTens += 0.1;});},
                decrementar: (){setState(() {caigudaTens -= 0.1;});},
                )),
...

 

 

 

 

 

 

 

 

 

 

Llicència de Creative Commons
Aquesta obra d'Oriol Boix està llicenciada sota una llicència no importada Reconeixement-NoComercial-SenseObraDerivada 3.0.