Menganimasikan Aplikasi Dengan Flutter
Diterbitkan: 2022-03-10Aplikasi untuk platform apa pun dipuji karena intuitif, tampan, dan memberikan umpan balik yang menyenangkan untuk interaksi pengguna. Animasi adalah salah satu cara untuk melakukan hal itu.
Flutter, kerangka kerja lintas platform, telah matang dalam dua tahun terakhir untuk menyertakan dukungan web dan desktop. Ini telah mengumpulkan reputasi bahwa aplikasi yang dikembangkan dengannya mulus dan terlihat bagus. Dengan dukungan animasi yang kaya, cara penulisan UI yang deklaratif, “Hot Reload,” dan fitur lainnya, sekarang menjadi kerangka kerja lintas platform yang lengkap.
Jika Anda memulai dengan Flutter dan ingin mempelajari cara menambahkan animasi yang tidak biasa, maka Anda berada di tempat yang tepat: kita akan menjelajahi bidang animasi dan widget gerak, cara implisit untuk menambahkan animasi.
Flutter didasarkan pada konsep widget. Setiap komponen visual aplikasi adalah widget — anggap saja sebagai tampilan di Android. Flutter menyediakan dukungan animasi menggunakan kelas Animasi, objek "AnimationController" untuk manajemen, dan "Tween" untuk menginterpolasi rentang data. Ketiga komponen ini bekerja sama untuk memberikan animasi yang halus. Karena ini memerlukan pembuatan dan pengelolaan animasi secara manual, ini dikenal sebagai cara animasi yang eksplisit.
Sekarang izinkan saya memperkenalkan Anda pada widget animasi dan gerak. Flutter menyediakan banyak widget yang secara inheren mendukung animasi. Tidak perlu membuat objek animasi atau pengontrol apa pun, karena semua animasi ditangani oleh kategori widget ini. Cukup pilih widget yang sesuai untuk animasi yang diperlukan dan teruskan nilai properti widget untuk dianimasikan. Teknik ini merupakan cara animasi yang implisit.
Bagan di atas secara kasar menetapkan hierarki animasi di Flutter, bagaimana animasi eksplisit dan implisit didukung.
Beberapa widget animasi yang dibahas dalam artikel ini adalah:
-
AnimatedOpacity
-
AnimatedCrossFade
-
AnimatedAlign
-
AnimatedPadding
-
AnimatedSize
-
AnimatedPositioned
.
Flutter tidak hanya menyediakan widget animasi yang telah ditentukan sebelumnya tetapi juga widget generik yang disebut AnimatedWidget
, yang dapat digunakan untuk membuat widget animasi implisit kustom. Seperti yang terlihat dari namanya, widget ini termasuk dalam kategori widget animasi dan gerak, sehingga mereka memiliki beberapa properti umum yang memungkinkan kita membuat animasi lebih halus dan terlihat lebih baik.
Izinkan saya menjelaskan properti umum ini sekarang, karena mereka akan digunakan nanti dalam semua contoh.
-
duration
Durasi untuk menganimasikan parameter. -
reverseDuration
Durasi animasi terbalik. -
curve
Kurva yang akan diterapkan saat menganimasikan parameter. Nilai interpolasi dapat diambil dari distribusi linier atau, jika dan ketika ditentukan, dapat diambil dari kurva.
Mari kita mulai perjalanan dengan membuat aplikasi sederhana yang kita sebut "Dikutip". Ini akan menampilkan kutipan acak setiap kali aplikasi dimulai. Dua hal yang perlu diperhatikan: pertama, semua kutipan ini akan di-hardcode dalam aplikasi; dan kedua, tidak ada data pengguna yang akan disimpan.
Catatan : Semua file untuk contoh ini dapat ditemukan di GitHub.
Mulai
Flutter harus dipasang dan Anda perlu membiasakan diri dengan aliran dasar sebelum melanjutkan. Tempat yang baik untuk memulai adalah, “Menggunakan Flutter Google Untuk Pengembangan Seluler yang Benar-Benar Lintas-Platform”.
Buat proyek Flutter baru di Android Studio.
Ini akan membuka wizard proyek baru, di mana Anda dapat mengonfigurasi dasar-dasar proyek.
Di layar pemilihan jenis proyek, ada berbagai jenis proyek Flutter, masing-masing melayani skenario tertentu. Untuk tutorial ini, pilih Aplikasi Flutter dan tekan Berikutnya .
Anda sekarang perlu memasukkan beberapa informasi khusus proyek: nama dan jalur proyek, domain perusahaan, dan sebagainya. Lihat gambar di bawah ini.
Tambahkan nama proyek, jalur Flutter SDK, lokasi proyek, dan deskripsi proyek opsional. Tekan Berikutnya .
Setiap aplikasi (baik itu Android atau iOS) memerlukan nama paket yang unik. Biasanya, Anda menggunakan kebalikan dari domain situs web Anda; misalnya com.google atau com.yahoo. Tekan Selesai untuk menghasilkan aplikasi Flutter yang berfungsi.
Setelah proyek dibuat, Anda akan melihat layar yang ditunjukkan di atas. Buka file main.dart (disorot di tangkapan layar). Ini adalah file aplikasi utama. Proyek sampel selesai dengan sendirinya, dan dapat dijalankan langsung di emulator atau perangkat fisik tanpa modifikasi apa pun.
Ganti konten file main.dart dengan potongan kode berikut:
import 'package:animated_widgets/FirstPage.dart'; import 'package:flutter/material.dart'; void main() => runApp(MyApp()); class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( title: 'Animated Widgets', debugShowCheckedModeBanner: false, theme: ThemeData( primarySwatch: Colors.blue, accentColor: Colors.redAccent, ), home: FirstPage(), ); } }
Kode ini membersihkan file main.dart hanya dengan menambahkan informasi sederhana yang relevan untuk membuat aplikasi baru. Kelas MyApp mengembalikan objek: widget MaterialApp
, yang menyediakan struktur dasar untuk membuat aplikasi yang sesuai dengan Desain Material. Untuk membuat kode lebih terstruktur, buat dua file dart baru di dalam folder lib : FirstPage.dart dan Quotes.dart .
FirstPage.dart akan berisi semua kode yang bertanggung jawab untuk semua elemen visual (widget) yang diperlukan untuk aplikasi Dikutip kami. Semua animasi ditangani dalam file ini.
Catatan : Nanti di artikel, semua cuplikan kode untuk setiap widget animasi ditambahkan ke file ini sebagai anak dari widget Scaffold. Untuk informasi lebih lanjut, Contoh di GitHub ini dapat bermanfaat.
Mulailah dengan menambahkan kode berikut ke FirstPage.dart . Ini adalah kode parsial di mana hal-hal lain akan ditambahkan nanti.
import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } }
import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } }
import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } }
import 'dart:math'; import 'package:animated_widgets/Quotes.dart'; import 'package:flutter/material.dart'; class FirstPage extends StatefulWidget { @override State createState() { return FirstPageState(); } } class FirstPageState extends State with TickerProviderStateMixin { bool showNextButton = false; bool showNameLabel = false; bool alignTop = false; bool increaseLeftPadding = false; bool showGreetings = false; bool showQuoteCard = false; String name = ''; double screenWidth; double screenHeight; String quote; @override void initState() { super.initState(); Random random = new Random(); int quoteIndex = random.nextInt(Quotes.quotesArray.length); quote = Quotes.quotesArray[quoteIndex]; } @override Widget build(BuildContext context) { screenWidth = MediaQuery.of(context).size.width; screenHeight = MediaQuery.of(context).size.height; return Scaffold( appBar: _getAppBar(), body: Stack( children: [ // All other children will be added here. // In this article, all the children widgets are contained // in their own separate methods. // Just method calls should be added here for the respective child. ], ), ); } }
File Quotes.dart berisi daftar semua kutipan hardcoded. Satu hal yang perlu diperhatikan di sini adalah bahwa daftar adalah objek statis. Ini berarti dapat digunakan di tempat lain tanpa membuat objek baru dari kelas Quotes. Ini dipilih berdasarkan desain, karena daftar di atas hanya berfungsi sebagai utilitas.
Tambahkan kode berikut ke file ini:
class Quotes { static const quotesArray = [ "Good, better, best. Never let it rest. 'Til your good is better and your better is best", "It does not matter how slowly you go as long as you do not stop.", "Only I can change my life. No one can do it for me." ]; }
Kerangka proyek sekarang sudah siap, jadi mari kita selesaikan Dikutip sedikit lagi.
AnimatedOpacity
Untuk memberikan sentuhan pribadi pada aplikasi, akan menyenangkan mengetahui nama pengguna, jadi mari kita memintanya dan menunjukkan tombol berikutnya. Sampai pengguna memasukkan nama mereka, tombol ini disembunyikan, dan akan muncul dengan anggun saat nama diberikan. Kami membutuhkan semacam animasi visibilitas untuk tombol, tetapi apakah ada widget untuk itu? Ya ada.
Masukkan AnimatedOpacity
. Widget ini dibangun di atas widget Opacity dengan menambahkan dukungan animasi implisit. bagaimana kami menggunakannya? Ingat skenario kita: kita perlu menampilkan tombol berikutnya dengan visibilitas animasi. Kami membungkus widget tombol di dalam widget AnimatedOpacity
, memasukkan beberapa nilai yang tepat dan menambahkan kondisi untuk memicu animasi — dan Flutter dapat menangani sisanya.
_getAnimatedOpacityButton() { return AnimatedOpacity( duration: Duration(seconds: 1), reverseDuration: Duration(seconds: 1), curve: Curves.easeInOut, opacity: showNextButton ? 1 : 0, child: _getButton(), ); }
Widget AnimatedOpacity
memiliki dua properti wajib:
-
opacity
Nilai 1 berarti benar-benar terlihat; 0 (nol) artinya tersembunyi. Saat menjiwai, Flutter menginterpolasi nilai di antara dua ekstrem ini. Anda dapat melihat bagaimana suatu kondisi ditempatkan untuk mengubah visibilitas, sehingga memicu animasi. -
child
Widget anak yang visibilitasnya akan dianimasikan.
Anda sekarang harus memahami betapa sederhananya menambahkan animasi visibilitas dengan widget implisit. Dan semua widget tersebut mengikuti panduan yang sama dan mudah digunakan. Mari kita lanjutkan ke yang berikutnya.
AnimatedCrossFade
Kami memiliki nama pengguna, tetapi widget masih menunggu input. Pada langkah sebelumnya, saat pengguna memasukkan nama mereka, kami menampilkan tombol berikutnya. Sekarang, ketika pengguna menekan tombol, saya ingin berhenti menerima input dan menampilkan nama yang dimasukkan. Ada banyak cara untuk melakukannya, tentu saja, tetapi mungkin kita dapat menyembunyikan widget input dan menampilkan widget teks yang tidak dapat diedit. Mari kita coba menggunakan widget AnimatedCrossFade
.
Widget ini membutuhkan dua anak, karena widget saling bersilangan di antara mereka berdasarkan beberapa kondisi. Satu hal penting yang perlu diingat saat menggunakan widget ini adalah bahwa kedua anak harus memiliki lebar yang sama. Jika ketinggiannya berbeda, maka widget yang lebih tinggi akan terpotong dari bawah. Dalam skenario ini, dua widget akan digunakan sebagai anak: input dan label.
_getAnimatedCrossfade() { return AnimatedCrossFade( duration: Duration(seconds: 1), alignment: Alignment.center, reverseDuration: Duration(seconds: 1), firstChild: _getNameInputWidget(), firstCurve: Curves.easeInOut, secondChild: _getNameLabelWidget(), secondCurve: Curves.easeInOut, crossFadeState: showNameLabel ? CrossFadeState.showSecond : CrossFadeState.showFirst, ); }
Widget ini memerlukan serangkaian parameter wajib yang berbeda:
-
crossFadeState
Keadaan ini menentukan anak mana yang harus ditunjukkan. -
firstChild
Menentukan anak pertama untuk widget ini. -
secondChild
Menentukan anak kedua.
AnimatedAlign
Pada titik ini, label nama diposisikan di tengah layar. Ini akan terlihat jauh lebih baik di bagian atas, karena kita membutuhkan bagian tengah layar untuk menunjukkan tanda kutip. Sederhananya, perataan widget label nama harus diubah dari tengah ke atas. Dan bukankah lebih baik untuk menganimasikan perubahan keselarasan ini bersama dengan animasi cross-fade sebelumnya? Ayo lakukan.
Seperti biasa, beberapa teknik dapat digunakan untuk mencapai ini. Karena widget label nama sudah rata tengah, menganimasikan perataannya akan jauh lebih sederhana daripada memanipulasi nilai atas dan kiri widget. Widget AnimatedAlign
sangat cocok untuk pekerjaan ini.
Untuk memulai animasi ini, diperlukan pemicu. Satu-satunya tujuan widget ini adalah untuk menganimasikan perubahan perataan, sehingga hanya memiliki beberapa properti: menambahkan anak, mengatur perataannya, memicu perubahan perataan, dan hanya itu.
_getAnimatedAlignWidget() { return AnimatedAlign( duration: Duration(seconds: 1), curve: Curves.easeInOut, alignment: alignTop ? Alignment.topLeft : Alignment.center, child: _getAnimatedCrossfade(), ); }
Ini hanya memiliki dua properti wajib:
- anak:
Anak yang keselarasannya akan diubah. - penyelarasan:
Nilai keselarasan yang diperlukan.
Widget ini sangat sederhana namun hasilnya elegan. Selain itu, kami melihat betapa mudahnya kami menggunakan dua widget animasi yang berbeda untuk membuat animasi yang lebih kompleks. Inilah keindahan widget animasi.
AnimatedPadding
Sekarang kami memiliki nama pengguna di bagian atas, dianimasikan dengan lancar tanpa banyak usaha, menggunakan berbagai jenis widget animasi. Mari tambahkan salam, “Hai,” sebelum namanya. Menambahkan widget teks dengan nilai "Hai," di bagian atas akan membuatnya tumpang tindih dengan widget teks ucapan, terlihat seperti gambar di bawah ini.
Bagaimana jika widget teks nama memiliki beberapa bantalan di sebelah kiri? Meningkatkan padding kiri pasti akan berhasil, tapi tunggu: bisakah kita menambah padding dengan beberapa animasi? Ya, dan itulah yang dilakukan AnimatedPadding
. Untuk membuat semua ini terlihat lebih baik, biarkan widget teks salam memudar dan padding widget teks nama meningkat secara bersamaan.
_getAnimatedPaddingWidget() { return AnimatedPadding( duration: Duration(seconds: 1), curve: Curves.fastOutSlowIn, padding: increaseLeftPadding ? EdgeInsets.only(left: 28.0) : EdgeInsets.only(left: 0), child: _getAnimatedCrossfade(), ); }
Karena animasi di atas seharusnya terjadi hanya setelah penyelarasan animasi sebelumnya selesai, kita perlu menunda pemicuan animasi ini. Melenceng dari topik secara singkat, ini adalah saat yang tepat untuk membicarakan mekanisme populer untuk menambah penundaan. Flutter menyediakan beberapa teknik seperti itu, tetapi konstruktor Future.delayed adalah salah satu pendekatan yang lebih sederhana, lebih bersih, dan lebih mudah dibaca. Misalnya, untuk mengeksekusi sepotong kode setelah 1 detik:
Future.delayed(Duration(seconds: 1), (){ sum = a + b; // This sum will be calculated after 1 second. print(sum); });
Karena durasi tunda sudah diketahui (dihitung dari durasi animasi sebelumnya), animasi dapat dipicu setelah interval ini.
// Showing “Hi” after 1 second - greetings visibility trigger. _showGreetings() { Future.delayed(Duration(seconds: 1), () { setState(() { showGreetings = true; }); }); } // Increasing the padding for name label widget after 1 second - increase padding trigger. _increaseLeftPadding() { Future.delayed(Duration(seconds: 1), () { setState(() { increaseLeftPadding = true; }); }); }
Widget ini hanya memiliki dua properti wajib:
-
child
Anak di dalam widget ini, yang akan diterapkan padding. -
padding
Jumlah ruang untuk ditambahkan.
AnimatedSize
Saat ini, aplikasi apa pun yang memiliki beberapa jenis animasi akan menyertakan memperbesar atau memperkecil komponen visual untuk menarik perhatian pengguna (biasa disebut animasi penskalaan). Mengapa tidak menggunakan teknik yang sama di sini? Kami dapat menunjukkan kepada pengguna kutipan motivasi yang diperbesar dari tengah layar. Izinkan saya memperkenalkan Anda ke widget AnimatedSize
, yang mengaktifkan efek zoom-in dan zoom-out, yang dikontrol dengan mengubah ukuran anaknya.
Widget ini sedikit berbeda dari yang lain dalam hal parameter yang diperlukan. Kami membutuhkan apa yang disebut Flutter sebagai "Ticker." Flutter memiliki metode untuk memberi tahu objek kapan pun peristiwa bingkai baru dipicu. Itu dapat dianggap sebagai sesuatu yang mengirimkan sinyal yang mengatakan, “Lakukan sekarang! … Lakukan sekarang! … Lakukan sekarang! …”
Widget AnimatedSize
memerlukan properti — vsync — yang menerima penyedia ticker. Cara termudah untuk mendapatkan penyedia ticker adalah dengan menambahkan Mixin ke kelas. Ada dua implementasi penyedia ticker dasar: SingleTickerProviderStateMixin
, yang menyediakan satu ticker; dan TickerProviderStateMixin
, yang menyediakan beberapa.
Implementasi default dari Ticker digunakan untuk menandai frame dari sebuah animasi. Dalam hal ini, yang terakhir digunakan. Lebih lanjut tentang mixin.
// Helper method to create quotes card widget. _getQuoteCardWidget() { return Card( color: Colors.green, elevation: 8.0, child: _getAnimatedSizeWidget(), ); } // Helper method to create animated size widget and set its properties. _getAnimatedSizeWidget() { return AnimatedSize( duration: Duration(seconds: 1), curve: Curves.easeInOut, vsync: this, child: _getQuoteContainer(), ); } // Helper method to create the quotes container widget with different sizes. _getQuoteContainer() { return Container( height: showQuoteCard ? 100 : 0, width: showQuoteCard ? screenWidth - 32 : 0, child: Center( child: Padding( padding: EdgeInsets.symmetric(horizontal: 16), child: Text(quote, style: TextStyle(color: Colors.white, fontWeight: FontWeight.w400, fontSize: 14),), ), ), ); } // Trigger used to show the quote card widget. _showQuote() { Future.delayed(Duration(seconds: 2), () { setState(() { showQuoteCard = true; }); }); }
Properti wajib untuk widget ini:
-
vsync
Penyedia ticker yang diperlukan untuk mengoordinasikan animasi dan perubahan bingkai.< -
child
Anak yang ukurannya berubah akan dianimasikan.
Zoom in dan zoom out animasi sekarang mudah dijinakkan.
AnimatedPositioned
Besar! Kutipan memperbesar dari tengah untuk menarik perhatian pengguna. Bagaimana jika itu meluncur dari bawah saat memperbesar? Mari kita coba. Gerakan ini melibatkan bermain dengan posisi widget kutipan dan menganimasikan perubahan pada properti posisi. AnimatedPositioned
adalah kandidat yang sempurna.
Widget ini secara otomatis mentransisikan posisi anak selama durasi tertentu setiap kali posisi yang ditentukan berubah. Satu hal yang perlu diperhatikan: ini hanya berfungsi jika widget induknya adalah "Stack." Widget ini cukup sederhana dan mudah digunakan. Ayo lihat.
// Helper method to create the animated positioned widget. // With position changes based on “showQuoteCard” flag. _getAnimatedPositionWidget() { return AnimatedPositioned( duration: Duration(seconds: 1), curve: Curves.easeInOut, child: _getQuoteCardWidget(), top: showQuoteCard ? screenHeight/2 - 100 : screenHeight, left: !showQuoteCard ? screenWidth/2 : 12, ); }
Widget ini hanya memiliki satu properti wajib:
-
child
Widget yang posisinya akan diubah.
Jika ukuran anak tidak diharapkan berubah seiring dengan posisinya, alternatif yang lebih performatif untuk widget ini adalah SlideTransition
.
Berikut animasi lengkap kami:
Kesimpulan
Animasi merupakan bagian integral dari pengalaman pengguna. Aplikasi statis atau aplikasi dengan animasi tersendat tidak hanya menurunkan retensi pengguna tetapi juga reputasi pengembang untuk memberikan hasil.
Saat ini, sebagian besar aplikasi populer memiliki semacam animasi halus untuk menyenangkan pengguna. Umpan balik animasi untuk permintaan pengguna juga dapat melibatkan mereka untuk menjelajahi lebih banyak. Flutter menawarkan banyak fitur untuk pengembangan lintas platform, termasuk dukungan yang kaya untuk animasi yang halus dan responsif.
Flutter memiliki dukungan plug-in hebat yang memungkinkan kita menggunakan animasi dari pengembang lain. Sekarang telah matang ke versi 1.9, dengan begitu banyak cinta dari komunitas, Flutter pasti akan menjadi lebih baik di masa depan. Menurut saya sekarang adalah waktu yang tepat untuk belajar Flutter!
Sumber Daya Lebih Lanjut
- “Widget Animasi Dan Gerak,” Flutter Docs
- “Pengantar Animasi,” Flutter Docs
- Codelab Flutter
Catatan Redaksi : Terima kasih yang sebesar-besarnya kepada Ahmad Awais atas bantuannya dalam mereview artikel ini.