Untuk teman-teman yang akan baru memulai belajar Flutter silahkan masuk ke artikel ini dulu ya teman-teman ➡ Aplikasi Pertamaku “Halo Semuaaa…“. Jika sudah yuk lanjut baca artikel ini…
Jangan lupa baca artikel sebelumnya ya teman-teman ➡ Penggunaan Widget TabBar
Pada artikel ini kita akan membuat button yang memicu proses download, menampilkan proses download dan lalu memberi akses ke aset yang telah selesai di download. Hal tersebut sangat membantu untuk menunjukan ke pengguna kemajuan dari proses download yang dilakukan dan proses tersebut akan ditampilkan secara visual pada button itu sendiri berdasarkan status download aplikasi. Berikut langkah-langkahnya:
1. Mendefinisikan Widget Stateful Baru
Widget button yang kita buat membutuhkan perubahan visual dari waktu ke waktu selama proses download terjadi. Oleh karena itu, kita butuh mengimplementasikan custom widget stateful pada button tersebut.
Definisikan widget stateful baru yang diberi nama DownloadButton
.
@immutable class DownloadButton extends StatefulWidget { const DownloadButton({ Key? key, }) : super(key: key); @override _DownloadButtonState createState() => _DownloadButtonState(); } class _DownloadButtonState extends State<DownloadButton> { @override Widget build(BuildContext context) { // TODO: return SizedBox(); } }
2. Mendefinisikan Button Menampilkan Perkiraan Status
Presentasi yang ditampilkan secara visual oleh button akan berdasarkan pada status download yang sedang berjalan. Dalam hal ini, kita akan mendefinisikan perkiraan status download, lalu memperbaharui DownloadButton
yang selanjutnya diubah ke DownloadStatus
dan menetukan tampilan Duration
berapa lama status download tersebut bergerak dari mulai sampai selesai.
enum DownloadStatus { notDownloaded, fetchingDownload, downloading, downloaded, } @immutable class DownloadButton extends StatefulWidget { const DownloadButton({ Key? key, required this.status, this.transitionDuration = const Duration(milliseconds: 500), }) : super(key: key); final DownloadStatus status; final Duration transitionDuration; @override _DownloadButtonState createState() => _DownloadButtonState(); }
3. Menampilkan Button Shape
Selanjutnya kita akan membuat visual download button berubah bentuk ke status download. Ketika status button notDownloaded
dan downloaded
, Button akan berbentuk persegi panjang berwarna abu-abu. Kemudian ketika button statusnya fetchingDownload
dan downloading
, maka button akan berbentuk lingkaran transparan.
Berdasarkan DownloadStatus
terkini, buat sebuah AnimatedContainer
dengan ShapeDecoration
yang menampilkan persegi panjang bulat atau lingkaran.
Kita harus pertimbangkan saat mendefinisikan pohon widget shape’s di dalam method lokal _buildXXXX()
sehingga method build()
tetap dalam bentuk sederhana dan mudah untuk dimodifikasi. Selain itu konfigurasi pohon widget shape agar menerima widget child, sehingga dapat kita gunakan untuk menampilkan teks pada langkah selanjutnya.
class _DownloadButtonState extends State<DownloadButton> { bool get _isDownloading => widget.status == DownloadStatus.downloading; bool get _isFetching => widget.status == DownloadStatus.fetchingDownload; bool get _isDownloaded => widget.status == DownloadStatus.downloaded; @override Widget build(BuildContext context) { return _buildButtonShape( child: SizedBox(), ); } Widget _buildButtonShape({ required Widget child, }) { return AnimatedContainer( duration: widget.transitionDuration, curve: Curves.ease, width: double.infinity, decoration: _isDownloading || _isFetching ? ShapeDecoration( shape: const CircleBorder(), color: Colors.white.withOpacity(0.0), ) : const ShapeDecoration( shape: StadiumBorder(), color: CupertinoColors.lightBackgroundGray, ), child: child, ); } }
Dari cupllikan kode di atas mungkin teman-teman bertanya-bertanya mengapa kita membutuhkan widget ShapeDecoration
pada lingkaran transparan, itu dikarenakan lingkaran transparan tersebut tidak terlihat. Kemudian dapat kita lihat, AnimatedContainer
di mulai dengan persegi panjang bersudut lengkung. Saat DownloadStatus
berubah menjadi fetchingDownload
, AnimatedContainer
perlu dianimasikan dari persegi panjang bersudut lengkung ke lingkaran penuh dan satu-satunya cara untuk menerapkan animasi tersebut adalah dengan mendefinisikan bentuk awal yaitu persegi panjang bersudut lengkung kemudian bentuk akhirnya lingkaran penuh. Namun pada saat bentuk akhirnya kita buat lingkaran penuhnya menjadi transparan sedikit pudar, supaya terlihat bagus.
4. Menampilkan Button Text
Pada langkah ini DownloadButton
menampilkan GET
selama tahap notDownloaded
dan menampilkan OPEN
selama tahap downloaded
sehingga tidak ada teks diantaranya.
Cuplikan kode di bawah ini memperlihatkan penambahan widget untuk menampilkan teks selama setiap tahap download dan memberikan efek buram pada teks di antara tahap tersebut. Tambahkan pohon widget teks sebagai child dari pohon widget shape.
class _DownloadButtonState extends State<DownloadButton> { @override Widget build(BuildContext context) { return _buildButtonShape( child: _buildText(), ); } Widget _buildText() { final text = _isDownloaded ? 'OPEN' : 'GET'; final opacity = _isDownloading || _isFetching ? 0.0 : 1.0; return Padding( padding: const EdgeInsets.symmetric(vertical: 6), child: AnimatedOpacity( duration: widget.transitionDuration, opacity: opacity, curve: Curves.ease, child: Text( text, textAlign: TextAlign.center, style: Theme.of(context).textTheme.button?.copyWith( fontWeight: FontWeight.bold, color: CupertinoColors.activeBlue, ), ), ), ); } }
5. Menampilkan Animasi Spinner Ketika Proses Download
Pada langkah ini, selama tahap fetchingDownload
DownloadButton
akan menampilkan radial spinner. Spinner ini mulai masuk dari tahap notDownloaded
dan menghilang pada tahap fetchingDownload
.
class _DownloadButtonState extends State<DownloadButton> { @override Widget build(BuildContext context) { return Stack( children: [ _buildButtonShape( child: _buildText(), ), _buildDownloadingProgress(), ], ); } Widget _buildDownloadingProgress() { return Positioned.fill( child: AnimatedOpacity( duration: widget.transitionDuration, opacity: _isDownloading || _isFetching ? 1.0 : 0.0, curve: Curves.ease, child: _buildProgressIndicator(), ), ); } Widget _buildProgressIndicator() { return AspectRatio( aspectRatio: 1.0, child: CircularProgressIndicator( backgroundColor: Colors.white.withOpacity(0.0), valueColor: AlwaysStoppedAnimation( CupertinoColors.lightBackgroundGray ), strokeWidth: 2.0, value: null, ), ); } }
6. Menampilkan Progress dan Stop Button Saat Downloading
Setelah tahap fetchingDownload
adalah tahap downloading
. Selama tahap downloading
, DownloadButton
mengganti radial progress spinner dengan growing radial progress bar. DownloadButton
juga menampilkan ikon stop button sehingga pengguna dapat membatalkan proses download yang sedang berjalan.
@immutable class DownloadButton extends StatefulWidget { const DownloadButton({ Key? key, this.progress = 0.0, }) : super(key: key); final double progress; @override _DownloadButtonState createState() => _DownloadButtonState(); } class _DownloadButtonState extends State<DownloadButton> { @override Widget build(BuildContext context) { return Stack( children: [ _buildButtonShape( child: _buildText(), ), _buildDownloadingProgress(), ], ); } Widget _buildDownloadingProgress() { return Positioned.fill( child: AnimatedOpacity( duration: widget.transitionDuration, opacity: _isDownloading || _isFetching ? 1.0 : 0.0, curve: Curves.ease, child: Stack( alignment: Alignment.center, children: [ _buildProgressIndicator(), if (_isDownloading) const Icon( Icons.stop, size: 14.0, color: CupertinoColors.activeBlue, ), ], ), ), ); } Widget _buildProgressIndicator() { return AspectRatio( aspectRatio: 1.0, child: TweenAnimationBuilder<double>( tween: Tween(begin: 0.0, end: widget.progress), duration: const Duration(milliseconds: 200), builder: (BuildContext context, double progress, Widget? child) { return CircularProgressIndicator( backgroundColor: _isDownloading ? CupertinoColors.lightBackgroundGray : Colors.white.withOpacity(0.0), valueColor: AlwaysStoppedAnimation(_isFetching ? CupertinoColors.lightBackgroundGray : CupertinoColors.activeBlue), strokeWidth: 2.0, value: _isFetching ? null : progress, ); }, ), ); } }
7. Menambahkan Button tap Untuk Melakukan Callbacks
Detail terakhir yang dibutuhkan DownloadButton
adalah button behavior. Tombol tersebut harus melakukan berbagai hal saat pengguna mengetuknya. Oleh karena itu, akan kita tambahkan properti widget callback untuk memulai proses download, membatalkan download, dan membuka download.
Terakhir, gabungkan pohon widget DownloadButton
yang ada dengan widget GestureDetector
, dan teruskan event ketukan tersebut ke properti callback yang sesuai.
@immutable class DownloadButton extends StatefulWidget { const DownloadButton({ Key? key, required this.onDownload, required this.onCancel, required this.onOpen, }) : super(key: key); final VoidCallback onDownload; final VoidCallback onCancel; final VoidCallback onOpen; @override _DownloadButtonState createState() => _DownloadButtonState(); } class _DownloadButtonState extends State<DownloadButton> { void _onPressed() { switch (widget.status) { case DownloadStatus.notDownloaded: widget.onDownload(); break; case DownloadStatus.fetchingDownload: // do nothing. break; case DownloadStatus.downloading: widget.onCancel(); break; case DownloadStatus.downloaded: widget.onOpen(); break; } } @override Widget build(BuildContext context) { return GestureDetector( onTap: _onPressed, child: Stack( children: [ _buildButtonShape( child: _buildText(), ), _buildDownloadingProgress(), ], ), ); } }
Selamat! Teman-teman sudah memiliki tombol yang akan mengubah tampilannya tergantung pada tahap mana tombol tersebut berada: not downloaded, fetching download, downloading, dan downloaded. Sekarang, pengguna dapat mengetuk untuk memulai proses download, ketuk untuk membatalkan proses download yang sedang berlangsung, dan ketuk untuk membuka hasil download yang sudah selesai.
Berikut cuplikan kode dan simulasinya, jika teman-teman menggunakan VSCode jalankan projectnya dengan menekan F5, klik hot reload (⚡) atau klik tombol ▶, berikut tampilannya:
Jika ada pertanyaan silahkan komen dan jika artikel ini dirasa bermanfaat, jangan lupa like dan sharenya ya teman-teman. ??????? Sampai bertemu di artikel selanjutnya.
Terima Kasih, Assalamu’alaykum… Salam KODINGINDONESIA
Referensi : https://flutter.dev//