Cara Menggunakan Hero Animation di Flutter

Pada kesempatan ini kita akan membahas cara menggunakan Hero Animation di Flutter. Hero Animasi merupakan sebuah element transisi yang bergerak melayang saat berpindah dari satu layar ke layar lainnya. Contoh tampilan hero seperti gambar dibawah ini

Cara Menggunakan Hero Animation di Flutter
Contoh Hero Animation di Flutter

Tutorial ini sebagai lanjutan dari navigasi routing, jadi apabila anda belum mengetahui dasar-dasar routing silahkan baca terlebih dahulu pembahasan routing pada flutter disini

Pengenalan Hero Animation

Hero Animation bisa dikatakan merupakan salah satu animasi paling mudah dan sederhana di flutter. Caranya yaitu hanya dengan menggunakan class Hero pada kedua item (awal dan akhir) dan dihubungkan dengan properti tag yang memilik nilai yang sama. Contoh sederhana penggunaan Hero widget seperti dibawah ini:

Hero(
  tag: "ContohTag",
  child: Icon(
    Icons.airport_shuttle,
    size: 100.0,
    ),
),

Kedua pasangan masing element harus memiliki value tag yang sama dan bersifat unik. Sebagai contoh kita akan membuat transisi icon mobil dimana posisi awal mobil berada di pojok kiri atas dan akan bergerak ketengah saat berpindah ke layar selanjutnya. Contoh code lengkapnya seperti ini

main.dart

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: HomePage(),
  ));
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Belajar Routing'),
      ),
      body: InkWell(
        onTap: () {
          Navigator.push(
            context,
            PageRouteBuilder(
              transitionDuration: Duration(seconds: 1),
              pageBuilder: (_, __, ___) => AboutPage(),
            ),
          );
        },
        child: Hero(
          tag: "ContohTag",
          child: Icon(
            Icons.airport_shuttle,
            size: 100.0,
          ),
        ),
      ),
    );
  }
}

class AboutPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Halaman Kedua'),
      ),
      body: Center(
        child: InkWell(
          onTap: () {
            Navigator.pop(context);
          },
          child: Hero(
            tag: "ContohTag",
            child: Icon(
              Icons.airport_shuttle,
              size: 100.0,
            ),
          ),
        ),
      ),
    );
  }
}

Perhatikan tag pada Hero widget dimana nilainya sama-sama menggunakan string dengan value “ContohTag“. Hasilnya seperti gambar dibawah ini

contoh sederhana hero animasi flutter

Merubah Widget Hero saat Transisi

Kita juga dapat merubah tampilan widget saat proses transisi dengan menggunakan properti flightShuttleBuilder.

Hero(
  tag: "ContohTag",
  child: Icon(
    Icons.airport_shuttle,
    size: 100.0,
  ),
  flightShuttleBuilder:
      (flightContext, animation, direction, fromContext, toContext) {
      return Icon( Icons.local_airport, size: 100.0, );
  },
),

Dengan properti flightShuttleBuilder kita juga dapat merubah widget push dan pop dengan cara membedakan widget berdasarkan Hero direction

if (direction == HeroFlightDirection.push) {
  return Icon(
    Icons.local_airport,
    size: 100.0,
  );
} else if (direction == HeroFlightDirection.pop) {
  return Icon(
    Icons.local_airport,
    size: 60.0,
  );
}
contoh merubah widget hero flutter

Kode lengkapnya dibawah ini :

main.dart

import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    home: HomePage(),
  ));
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Belajar Hero'),
      ),
      body: InkWell(
        onTap: () {
          Navigator.push(
            context,
            PageRouteBuilder(
              transitionDuration: Duration(seconds: 1),
              pageBuilder: (_, __, ___) => AboutPage(),
            ),
          );
        },
        child: Hero(
          tag: "ContohTag",
          child: Icon(
            Icons.airport_shuttle,
            size: 100.0,
          ),
          flightShuttleBuilder:
              (flightContext, animation, direction, fromContext, toContext) {
            if (direction == HeroFlightDirection.push) {
              return Icon(
                Icons.local_airport,
                size: 100.0,
              );
            } else if (direction == HeroFlightDirection.pop) {
              return Icon(
                Icons.local_airport,
                size: 60.0,
              );
            }
          },
        ),
      ),
    );
  }
}

class AboutPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Halaman Kedua'),
      ),
      body: Center(
        child: InkWell(
          onTap: () {
            Navigator.pop(context);
          },
          child: Hero(
            tag: "ContohTag",
            child: Icon(
              Icons.home,
              size: 100.0,
            ),
          ),
        ),
      ),
    );
  }
}

Menambahkan placeholder

Untuk menambahkan placeholder di Hero, kita dapat menggunakan properti placeholderBuilder

Hero(
  tag: "ContohTag",
  child: Icon(
    Icons.airport_shuttle,
    size: 100.0,
  ),
  placeholderBuilder: (context, widget) {
    return Container(
      height: 110.0,
      width: 110.0,
      child: CircularProgressIndicator(),
    );
  },
),

Hero + Transition Effect + Generate Route

Menggunakan Hero Animasi dengan GenerateRoute juga sama seperti cara-cara diatas. Berikut contoh penggunaan Hero animasi yang digabungkan dengan Fade Transition Effect menggunakan generate route

Struktur File

├── lib
│   ├── main.dart
│   ├── pixabay-data.dart
│   ├── routes.dart
│   └── screen.dart

main.dart

import 'package:flutter/material.dart';
import 'package:belajar_flutter/routes.dart';

void main() {
  runApp(MaterialApp(
    onGenerateRoute: RouteGenerator.generateRoute,
  ));
}

routes.dart

import 'package:flutter/material.dart';
import 'package:belajar_flutter/screen.dart';

class RouteGenerator {
  static Route<dynamic> generateRoute(RouteSettings settings) {
    final args = settings.arguments;
    switch (settings.name) {
      case '/':
        return MaterialPageRoute(builder: (_) => HomePage());
      case '/detail':
        return PageRouteBuilder(
          pageBuilder: (_, __, ___) => DetailPage(args),
          transitionDuration: Duration(milliseconds: 700),
          transitionsBuilder: (_, animation, secondAnimation, child) {
            return FadeTransition(opacity: animation, child: child);
          },
        );
      default:
        return _errorRoute();
    }
  }

  static Route<dynamic> _errorRoute() {
    return MaterialPageRoute(builder: (_) {
      return Scaffold(
        appBar: AppBar(title: Text("Error")),
        body: Center(child: Text('Error page')),
      );
    });
  }
}

screen.dart

import 'package:flutter/material.dart';
import 'package:belajar_flutter/pixabay-data.dart';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  void initState() {
    ImageData.init();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Belajar Animasi Hero'),
      ),
      body: ListView.separated(
          itemCount: ImageData.pixabay.length,
          itemBuilder: (_, index) => GestureDetector(
                onTap: () {
                  Navigator.pushNamed(context, '/detail',
                      arguments: ImageData.pixabay[index]);
                },
                child: ListTile(
                  leading: Hero(
                    tag: ImageData.pixabay[index].id,
                    child: Image.network(ImageData.pixabay[index].imageLarge),
                    transitionOnUserGestures: true,
                  ),
                  title: Text(ImageData.pixabay[index].title),
                  subtitle: Text(ImageData.pixabay[index].author),
                ),
              ),
          separatorBuilder: (context, position) {
            return Divider();
          }),
    );
  }
}

class DetailPage extends StatelessWidget {
  final pixabay;
  DetailPage(this.pixabay);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Detail Gambar'),
      ),
      body: Center(
        child: Hero(
          tag: pixabay.id,
          child: Image.network(pixabay.imageLarge),
        ),
      ),
    );
  }
}

pixabay-data.dart

class ImageData {
  int id;
  String title;
  String author;
  String imageLarge;

  ImageData({this.id, this.title, this.author, this.imageLarge});

  static List<ImageData> pixabay;

  ImageData.init() {
    pixabay = List();
    pixabay.add(
      ImageData(
          id: 1,
          title: 'Tupai',
          author: 'Capri23auto',
          imageLarge:
              'https://pixabay.com/get/53e5d1464c51a414f6da8c7dda793676173bd7e15a596c48702672d39548c459b9_1280.jpg'),
    );
    pixabay.add(
      ImageData(
          id: 2,
          title: 'Anggur',
          author: 'NickyPe',
          imageLarge:
              'https://pixabay.com/get/53e5d0424950a914f6da8c7dda793676173bd7e15a596c48702672d39548c459b9_1280.jpg'),
    );
    pixabay.add(
      ImageData(
          id: 3,
          title: 'Pemandangan',
          author: 'Sonyuser',
          imageLarge:
              'https://pixabay.com/get/53e5d6474956ad14f6da8c7dda793676173bd7e15a596c48702672d39548c459b9_1280.jpg'),
    );
    pixabay.add(
      ImageData(
          id: 4,
          title: 'Kucing Kuning',
          author: 'Alexas_Fotos',
          imageLarge:
              'https://pixabay.com/get/53e5d0434a51af14f6da8c7dda793676173bd7e15a596c48702672d39548c459b9_1280.jpg'),
    );
    pixabay.add(
      ImageData(
          id: 5,
          title: 'Burung',
          author: 'Derweg',
          imageLarge:
              'https://pixabay.com/get/53e5d1454c53ac14f6da8c7dda793676173bd7e15a596c48702672d39548c459b9_1280.jpg'),
    );
  }
}

Tampilan kode diatas akan menjadi seperti gambar dibawah ini

contoh hero animation di flutter

Penutupan

Menggunakan Hero Animation di Flutter cukup mudah namun tentu pembahasan diatas hanya dasar saja. Untuk penjelasan transition effect kita akan coba bahas dikesempatan lain.

Selamat Mencoba 🙂

Referensi :
https://api.flutter.dev/flutter/widgets/Hero-class.html

Default image
Omadi Jaya
Fullstack developer, Software Engineer @ Depok, Indonesia

Leave a Reply