Gestire il focus dei campi di input in Flutter con FocusNode e FocusScope
GuidePrincipiante25 min Flutter 3.x

Gestire il focus dei campi di input in Flutter con FocusNode e FocusScope

Quando un utente compila un form, spostare il cursore da un campo all'altro deve essere naturale e fluido. In Flutter il sistema di gestione del focus è basato su FocusNode e FocusScope, strumenti che ci permettono di controllare quale widget riceve l'input della tastiera.

In questo tutorial vedremo come:

  • assegnare un FocusNode a ciascun TextField
  • spostare il focus al campo successivo premendo "Next" sulla tastiera
  • portare automaticamente il focus su un campo all'apertura della schermata
  • rilasciare il focus (chiudere la tastiera) al momento giusto
  • reagire ai cambiamenti di focus per aggiornare la UI

Alla fine avrai un form professionale con una navigazione tra campi impeccabile.

  1. 1

    Creare lo scheletro del form

    Partiamo da uno StatefulWidget che contiene tre campi di testo: nome, email e password. Usiamo uno StatefulWidget perché dovremo gestire il ciclo di vita dei FocusNode, che vanno creati e distrutti manualmente.

    Per ora inseriamo solo i TextField in una Column avvolta da un Padding.

    class FocusForm extends StatefulWidget {
      const FocusForm({super.key});
    
      @override
      State<FocusForm> createState() => _FocusFormState();
    }
    
    class _FocusFormState extends State<FocusForm> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Gestione Focus')),
          body: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: const [
                TextField(decoration: InputDecoration(labelText: 'Nome')),
                SizedBox(height: 12),
                TextField(decoration: InputDecoration(labelText: 'Email')),
                SizedBox(height: 12),
                TextField(decoration: InputDecoration(labelText: 'Password')),
              ],
            ),
          ),
        );
      }
    }

    Risultato atteso

    Una schermata con tre campi di testo impilati verticalmente, ancora senza gestione del focus.

  2. 2

    Creare e distruggere i FocusNode

    Ogni campo che vogliamo controllare programmaticamente ha bisogno di un proprio FocusNode. Li dichiariamo come variabili di stato, li inizializziamo in initState e — punto fondamentale — li rilasciamo in dispose per evitare memory leak.

    Associamo poi ciascun FocusNode al rispettivo TextField tramite la proprietà focusNode.

    class _FocusFormState extends State<FocusForm> {
      final _nomeFocus = FocusNode();
      final _emailFocus = FocusNode();
      final _passwordFocus = FocusNode();
    
      @override
      void dispose() {
        _nomeFocus.dispose();
        _emailFocus.dispose();
        _passwordFocus.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: const Text('Gestione Focus')),
          body: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                TextField(
                  focusNode: _nomeFocus,
                  decoration: const InputDecoration(labelText: 'Nome'),
                ),
                const SizedBox(height: 12),
                TextField(
                  focusNode: _emailFocus,
                  decoration: const InputDecoration(labelText: 'Email'),
                ),
                const SizedBox(height: 12),
                TextField(
                  focusNode: _passwordFocus,
                  decoration: const InputDecoration(labelText: 'Password'),
                ),
              ],
            ),
          ),
        );
      }
    }

    Risultato atteso

    Il form funziona come prima, ma ora ogni campo ha un FocusNode dedicato che possiamo controllare via codice.

  3. 3

    Spostare il focus al campo successivo con il tasto Next

    Impostiamo la proprietà textInputAction per mostrare il pulsante corretto sulla tastiera (next per i campi intermedi, done per l'ultimo). Con il callback onSubmitted spostiamo il focus al campo successivo usando FocusScope.of(context).requestFocus(...).

    Aggiungiamo anche keyboardType e obscureText per rendere i campi più realistici.

    TextField(
      focusNode: _nomeFocus,
      textInputAction: TextInputAction.next,
      decoration: const InputDecoration(labelText: 'Nome'),
      onSubmitted: (_) {
        FocusScope.of(context).requestFocus(_emailFocus);
      },
    ),
    const SizedBox(height: 12),
    TextField(
      focusNode: _emailFocus,
      keyboardType: TextInputType.emailAddress,
      textInputAction: TextInputAction.next,
      decoration: const InputDecoration(labelText: 'Email'),
      onSubmitted: (_) {
        FocusScope.of(context).requestFocus(_passwordFocus);
      },
    ),
    const SizedBox(height: 12),
    TextField(
      focusNode: _passwordFocus,
      obscureText: true,
      textInputAction: TextInputAction.done,
      decoration: const InputDecoration(labelText: 'Password'),
      onSubmitted: (_) {
        _passwordFocus.unfocus();
        // Qui potresti inviare il form
      },
    ),

    Risultato atteso

    Premendo 'Next' sulla tastiera il cursore passa al campo successivo; sull'ultimo campo appare 'Done' e la tastiera si chiude.

  4. 4

    Autofocus all'apertura della schermata

    Spesso vogliamo che il primo campo sia già attivo quando l'utente apre il form. Ci sono due modi.

    Il più semplice è usare la proprietà autofocus: true direttamente sul TextField.

    Se invece vogliamo un controllo più fine (ad esempio dare il focus dopo un ritardo o in risposta a un evento), possiamo richiedere il focus dentro initState, avvolgendo la chiamata in un WidgetsBinding.instance.addPostFrameCallback per essere sicuri che il widget sia già montato.

    @override
    void initState() {
      super.initState();
      WidgetsBinding.instance.addPostFrameCallback((_) {
        _nomeFocus.requestFocus();
      });
    }
    
    // In alternativa, per il caso semplice:
    // TextField(
    //   focusNode: _nomeFocus,
    //   autofocus: true,
    //   ...
    // )

    Risultato atteso

    Aprendo la schermata, il campo 'Nome' è già selezionato e la tastiera compare automaticamente.

  5. 5

    Chiudere la tastiera toccando fuori dai campi

    Un dettaglio di UX molto apprezzato è la chiusura della tastiera quando l'utente tocca un'area vuota. Avvolgiamo il body in un GestureDetector che, al tap, richiede il focus su un nodo vuoto tramite FocusScope.of(context).unfocus().

    Impostiamo behavior: HitTestBehavior.opaque così il tap viene rilevato anche sulle zone trasparenti.

    body: GestureDetector(
      behavior: HitTestBehavior.opaque,
      onTap: () => FocusScope.of(context).unfocus(),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // ... i TextField definiti prima
          ],
        ),
      ),
    ),

    Risultato atteso

    Toccando un punto qualsiasi al di fuori dei campi, la tastiera si chiude e nessun campo risulta più attivo.

  6. 6

    Reagire ai cambiamenti di focus per aggiornare la UI

    I FocusNode espongono la proprietà hasFocus e permettono di aggiungere un listener con addListener. Possiamo sfruttarli per evidenziare visivamente il campo attivo, ad esempio cambiando il colore del label o del bordo.

    Nell'esempio aggiungiamo un listener sul nodo dell'email e ricostruiamo la UI quando cambia lo stato di focus. Ricorda di rimuovere il listener in dispose (o di affidarti al dispose del nodo stesso).

    @override
    void initState() {
      super.initState();
      _emailFocus.addListener(_onEmailFocusChange);
    }
    
    void _onEmailFocusChange() {
      // Ricostruisce la UI ad ogni cambio di focus del campo email
      setState(() {});
    }
    
    @override
    void dispose() {
      _emailFocus.removeListener(_onEmailFocusChange);
      _nomeFocus.dispose();
      _emailFocus.dispose();
      _passwordFocus.dispose();
      super.dispose();
    }
    
    // Nel build, usiamo hasFocus per personalizzare la decorazione:
    TextField(
      focusNode: _emailFocus,
      keyboardType: TextInputType.emailAddress,
      textInputAction: TextInputAction.next,
      decoration: InputDecoration(
        labelText: 'Email',
        prefixIcon: Icon(
          Icons.email,
          color: _emailFocus.hasFocus ? Colors.blue : Colors.grey,
        ),
      ),
      onSubmitted: (_) => FocusScope.of(context).requestFocus(_passwordFocus),
    ),

    Risultato atteso

    Quando il campo email riceve il focus, l'icona diventa blu; quando lo perde torna grigia, offrendo un feedback visivo chiaro.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!