Onboarding animato in Flutter con PageView e SmoothPageIndicator
GuidePrincipiante35 min Flutter 3.x

Onboarding animato in Flutter con PageView e SmoothPageIndicator

Onboarding animato in Flutter

La schermata di onboarding è spesso il primo contatto dell'utente con la tua app: presenta le funzionalità principali attraverso alcune pagine scorrevoli.

In questo tutorial costruiremo un onboarding completo usando il widget PageView, il pacchetto smooth_page_indicator per gli indicatori animati e shared_preferences per mostrare l'onboarding solo al primo avvio.

Alla fine avrai una base riutilizzabile e professionale da integrare in qualsiasi progetto.

  1. 1

    Aggiungere le dipendenze

    Aggiungiamo al progetto i pacchetti necessari: smooth_page_indicator per gli indicatori animati e shared_preferences per ricordare se l'onboarding è già stato completato.

    Esegui il comando da terminale oppure aggiungi manualmente le righe al file pubspec.yaml.

    flutter pub add smooth_page_indicator shared_preferences

    Risultato atteso

    Le dipendenze compaiono nel pubspec.yaml e vengono scaricate senza errori.

  2. 2

    Creare il modello dei contenuti

    Definiamo una semplice classe OnboardingPageData che rappresenta il contenuto di ogni pagina: titolo, descrizione e icona.

    Creiamo poi una lista con le pagine da mostrare.

    class OnboardingPageData {
      final String title;
      final String description;
      final IconData icon;
    
      const OnboardingPageData({
        required this.title,
        required this.description,
        required this.icon,
      });
    }
    
    const onboardingPages = <OnboardingPageData>[
      OnboardingPageData(
        title: 'Benvenuto',
        description: 'Scopri come la nostra app semplifica la tua giornata.',
        icon: Icons.waving_hand,
      ),
      OnboardingPageData(
        title: 'Organizza tutto',
        description: 'Tieni sotto controllo attività e promemoria in un unico posto.',
        icon: Icons.checklist,
      ),
      OnboardingPageData(
        title: 'Inizia subito',
        description: 'Registrati in pochi secondi e comincia a usare l\'app.',
        icon: Icons.rocket_launch,
      ),
    ];

    Risultato atteso

    Hai una lista di tre pagine pronte da visualizzare.

  3. 3

    Costruire la singola pagina

    Creiamo un widget riutilizzabile OnboardingPage che riceve un OnboardingPageData e ne mostra icona, titolo e descrizione centrati verticalmente.

    import 'package:flutter/material.dart';
    
    class OnboardingPage extends StatelessWidget {
      final OnboardingPageData data;
      const OnboardingPage({super.key, required this.data});
    
      @override
      Widget build(BuildContext context) {
        final theme = Theme.of(context);
        return Padding(
          padding: const EdgeInsets.all(32),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(data.icon, size: 120, color: theme.colorScheme.primary),
              const SizedBox(height: 40),
              Text(
                data.title,
                style: theme.textTheme.headlineMedium,
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 16),
              Text(
                data.description,
                style: theme.textTheme.bodyLarge,
                textAlign: TextAlign.center,
              ),
            ],
          ),
        );
      }
    }

    Risultato atteso

    Il widget mostra una pagina ben formattata con icona, titolo e descrizione.

  4. 4

    Assemblare la schermata con PageView e indicatore

    Ora creiamo OnboardingScreen, uno StatefulWidget che usa un PageController e un PageView.builder per scorrere le pagine.

    Aggiungiamo SmoothPageIndicator per mostrare la posizione corrente con un'animazione ed espandiamo un pulsante che cambia testo sull'ultima pagina.

    import 'package:flutter/material.dart';
    import 'package:smooth_page_indicator/smooth_page_indicator.dart';
    
    class OnboardingScreen extends StatefulWidget {
      final VoidCallback onFinish;
      const OnboardingScreen({super.key, required this.onFinish});
    
      @override
      State<OnboardingScreen> createState() => _OnboardingScreenState();
    }
    
    class _OnboardingScreenState extends State<OnboardingScreen> {
      final _controller = PageController();
      int _currentPage = 0;
    
      bool get _isLastPage => _currentPage == onboardingPages.length - 1;
    
      @override
      void dispose() {
        _controller.dispose();
        super.dispose();
      }
    
      void _next() {
        if (_isLastPage) {
          widget.onFinish();
        } else {
          _controller.nextPage(
            duration: const Duration(milliseconds: 350),
            curve: Curves.easeInOut,
          );
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: SafeArea(
            child: Column(
              children: [
                Align(
                  alignment: Alignment.topRight,
                  child: TextButton(
                    onPressed: widget.onFinish,
                    child: const Text('Salta'),
                  ),
                ),
                Expanded(
                  child: PageView.builder(
                    controller: _controller,
                    itemCount: onboardingPages.length,
                    onPageChanged: (i) => setState(() => _currentPage = i),
                    itemBuilder: (_, i) => OnboardingPage(data: onboardingPages[i]),
                  ),
                ),
                SmoothPageIndicator(
                  controller: _controller,
                  count: onboardingPages.length,
                  effect: const ExpandingDotsEffect(
                    dotHeight: 10,
                    dotWidth: 10,
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.all(24),
                  child: SizedBox(
                    width: double.infinity,
                    child: FilledButton(
                      onPressed: _next,
                      child: Text(_isLastPage ? 'Inizia' : 'Avanti'),
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }

    Risultato atteso

    Puoi scorrere le pagine, l'indicatore si anima e il pulsante mostra 'Inizia' sull'ultima pagina.

  5. 5

    Ricordare se l'onboarding è già stato completato

    Usiamo shared_preferences per salvare un flag quando l'utente completa l'onboarding, così da non mostrarlo più agli avvii successivi.

    Creiamo una piccola classe helper con due metodi.

    import 'package:shared_preferences/shared_preferences.dart';
    
    class OnboardingStorage {
      static const _key = 'onboarding_completed';
    
      Future<bool> isCompleted() async {
        final prefs = await SharedPreferences.getInstance();
        return prefs.getBool(_key) ?? false;
      }
    
      Future<void> setCompleted() async {
        final prefs = await SharedPreferences.getInstance();
        await prefs.setBool(_key, true);
      }
    }

    Risultato atteso

    Hai un helper che legge e scrive lo stato di completamento dell'onboarding.

  6. 6

    Decidere quale schermata mostrare all'avvio

    Nel main leggiamo il flag e decidiamo se mostrare l'OnboardingScreen o direttamente la HomeScreen.

    Usiamo un FutureBuilder per attendere la lettura da shared_preferences e mostriamo un indicatore di caricamento nel frattempo.

    import 'package:flutter/material.dart';
    
    void main() => runApp(const MyApp());
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Onboarding Demo',
          theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
          home: const AppEntry(),
        );
      }
    }
    
    class AppEntry extends StatefulWidget {
      const AppEntry({super.key});
    
      @override
      State<AppEntry> createState() => _AppEntryState();
    }
    
    class _AppEntryState extends State<AppEntry> {
      final _storage = OnboardingStorage();
      late Future<bool> _completedFuture;
    
      @override
      void initState() {
        super.initState();
        _completedFuture = _storage.isCompleted();
      }
    
      void _finishOnboarding() async {
        await _storage.setCompleted();
        if (!mounted) return;
        setState(() => _completedFuture = Future.value(true));
      }
    
      @override
      Widget build(BuildContext context) {
        return FutureBuilder<bool>(
          future: _completedFuture,
          builder: (context, snapshot) {
            if (!snapshot.hasData) {
              return const Scaffold(
                body: Center(child: CircularProgressIndicator()),
              );
            }
            if (snapshot.data == true) {
              return const HomeScreen();
            }
            return OnboardingScreen(onFinish: _finishOnboarding);
          },
        );
      }
    }
    
    class HomeScreen extends StatelessWidget {
      const HomeScreen({super.key});
    
      @override
      Widget build(BuildContext context) {
        return const Scaffold(
          body: Center(child: Text('Benvenuto nella Home!')),
        );
      }
    }

    Risultato atteso

    Al primo avvio vedi l'onboarding; dopo averlo completato l'app apre direttamente la Home ai riavvii successivi.

CondividiXLinkedInFacebookWhatsApp

Commenti (0)

Ancora nessun commento. Inizia tu!