Mengenal Widget Flutter #6: Grid View, ClipRReact & ClipPath, Hero Animation

Mengenal Widget Flutter #6: Grid View, ClipRReact & ClipPath, Hero Animation

Membangun sebuah aplikasi menggunakan Flutter adalah tentang proses bagaimana memahami widget-nya bekerja, maka pada seri kali ini kita akan membahas lebih lanjut tentang widget tersebut yang telah memasuki materi bagaimana membuat tampilan Grid View di Flutter, mengatur sudut lengkungan sebuah objek dengan ClipRReact & ClipPath, membuat animasi dengan Hero animation.

Setiap widget akan dibahas secara individu dan diakhir materi atau pada materi selanjutnya, ketiganya akan digunakan secara bersamaan dengan menyelesaikan sebuah case sederhana yakni membuat Instagram UI dengan Flutter. Tentu saja materi yang telah lampau akan turut serta untuk menunjang penyelesaian case tersebut.

Baca Juga: Mengenal Widget Flutter #5: Membuat Form Login

Membuat Grid Dengan Grid View

Merasa familiar dengan kata Grid? Benar, jika anda datang atau pernah mencoba web development, maka kita tentu saja sangat terbantu dengan grid yang ditawarkan oleh Bootstrap sehingga pengguna bisa dengan mudah membagi ruang yang ada sesuai keinginan dengan besaran yang telah ditentukan.

Flutter juga memiliki widget tersebut yang secara otomatis akan membagi bidang yang ada sesuai ketentuan, tidak hanya itu saja, karena Gird View sudah dilengkapi dengan fungsi scrollable. Jadi kalau ada yang bertanya, kan kita bisa membuat grid dengan bermodalkan Row-Column saja? Benar, tapi semuanya perlu dilakukan secara manual, sedangkan ketika menggunakan GridView maka semuanya akan otomatis.

Langkah pertama, install Flutter terlebih dahulu dengan command

flutter create dw_grid_view

Pada case pertama ini kita akan membuat sebuah tampilan berupa kotak pilihan kategori yang biasanya ada pada acara kuis. Kurang lebih tampilannya akan terlihat seperti gambar di bawah ini

menggunakan grid view flutter

Buka file lib/main.dart dan modifikasi menjadi

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Grid View'),
        ),
        body: Container(
          margin: const EdgeInsets.all(10.0), //SET MARGINNYA DARI BERBAGAI SISI
          //KEMUDIAN CHILDNYA KITA GUNAKAN GRID VIEW
          child: GridView(
            //ATTRIBUTE INI UNTUK MENGATUR PROPERTI DARI GRID VIEW
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              //TOTAL GRID DALAM 1 BIDANG, MISAL 3 MAKA AKAN BERISI 3 ELEMENT KEKANAN DAN ELEMEN KE-4 AKAN DISIMPAN DIBAWAHNYA SECARA OTOMATIS
              crossAxisCount: 3,
              mainAxisSpacing: 3.0, //MENGATUR JARAK ANTARA OBJEK ATAS DAN BAWAH
              crossAxisSpacing: 3, //MENGATUR JARAK ANTARA OBJEK KIRI DAN KANAN
              childAspectRatio: 1.0, //ASPEK RASIONYA KITA SET BANDING 1 SAJA
            ),
            children: <Widget>[
              //LIST CHILDREN YANG AKAN DITAMPILKAN DI DALAM GRID
            ],
          ),
        ),
      ),
    );
  }
}

Sebagai permulaan, kita akan mengatur children-nya dengan membuat kotak secara statis (red: memasukkanya satu-persatu) terlebih dahulu, dimana akan ada 6 buah kotak yang berisi masing-masing kategori di dalamnya kotaknya. Tambahkan code berikut di dalam children dari Grid View

Container(
  color: Colors.pink,
  height: 200,
  child: Center(
    child: Text(
      'Flutter',
      style: TextStyle(
        color: Colors.white,
        fontWeight: FontWeight.bold,
      ),
    ),
  ),
),
Container(
  color: Colors.blue,
  height: 200,
  child: Center(
    child: Text(
      'Laravel',
      style: TextStyle(
        color: Colors.white,
        fontWeight: FontWeight.bold,
      ),
    ),
  ),
),
Container(
  color: Colors.amber,
  height: 200,
  child: Center(
    child: Text(
      'Vue.js',
      style: TextStyle(
        color: Colors.white,
        fontWeight: FontWeight.bold,
      ),
    ),
  ),
),
Container(
  color: Colors.yellow,
  height: 200,
  child: Center(
    child: Text(
      'Javascript',
      style: TextStyle(
        color: Colors.black,
        fontWeight: FontWeight.bold,
      ),
    ),
  ),
),
Container(
  color: Colors.red,
  height: 200,
  child: Center(
    child: Text(
      'PHP',
      style: TextStyle(
        color: Colors.white,
        fontWeight: FontWeight.bold,
      ),
    ),
  ),
),
Container(
  color: Colors.black,
  height: 200,
  child: Center(
    child: Text(
      'Java',
      style: TextStyle(
        color: Colors.white,
        fontWeight: FontWeight.bold,
      ),
    ),
  ),
),

Note: Code di atas hanya 6 buah Container yang di-set warna dan tingginya serta di dalamnya terdapat teks untuk masing-masing kategori. Jika bingung tentang code di atas, silahkan baca Serial mengenal widget Flutter yang membahas Container.

Ketika dijalankan, maka hasil yang akan diperoleh kurang lebih seperti gambar di atas.

Explore lebih lanjut, bagaimana jika ingin menggunakan looping? Jadi kotak yang ada di dalamnya tidak perlu dituliskan satu persatu? Sebenarnya proses looping bisa diterapkan dengan menggunakan pendekatan sebelumnya, tapi untuk memperkaya pengetahuan kita, maka kita akan menggunakan GridView.builder(), modifikasi code sebelumnya menjadi.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  //KITA BERANDAI PUNYA DATA YANG DIDAPATKAN DARI BACKEND DENGAN FORMAT SEPERTI BERIKUT
  //SEBUAH ARRAY YANG DI DALAMNYA TERDAPAT OBJECT DATA
  final List<Map<String, dynamic>> _kategori = [
    {'cat': 'Flutter', 'color': Colors.pink},
    {'cat': 'Laravel', 'color': Colors.blue},
    {'cat': 'Vue.js', 'color': Colors.amber},
    {'cat': 'Javascript', 'color': Colors.yellow},
    {'cat': 'PHP', 'color': Colors.red},
    {'cat': 'Java', 'color': Colors.black},
  ];

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Grid View'),
        ),
        body: Container(
          margin: const EdgeInsets.all(10.0),
          //GUNAKAN GRIDVIEW BUILDER
          child: GridView.builder(
            itemCount: _kategori.length, //SET BERAPA TOTAL DATANYA DENGAN MENGHITUNG DARI LIST DATA ARRAY DIATAS
            //SAMA SEPERTI SEBELUMNYA, SET PROPERTINYA UNTUK MENGATUR TAMPILAN YG DIINGINKAN
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3,
                mainAxisSpacing: 3,
                crossAxisSpacing: 3,
                childAspectRatio: 1),
            //YANG MENARIK ADALAH BAGIAN BUILDER INI
            //PARAMTER KEDUA ADA INDEX DARI HASIL LOOPINGNYA AKAN SELALU BERUBAH
            itemBuilder: (context, index) {
              //TINGGAL KITA TERAPKAN KEDALAM SEBUAH CONTAINER 
              return Container(
                color: _kategori[index]['color'], //DENGAN WARNA YANG DIAMBIL DARI ARRAY SEBELUMNYA BERDASARKAN INDEX SAAT INI
                height: 200, //TINGGI KOTAKNYA SAMA
                child: Center(
                  child: Text(
                    _kategori[index]['cat'], //ADAPUN UNTUK TEKSNYA KITA JUGA AMBIL DARI ARRAY SEBELUMNYA BERDASARKAN VALUE DARI INDEX SAAT INI, KEMUDIAN AKSES CAT
                    style: TextStyle(
                      //KARENA WARNA PUTIH PADA TEKS TIDAK COCOK DENGAN KOTAK KUNING
                      //MAKA KITA CEK JIKA BUKAN JAVASCRIPT MAKA WARNA TEKSNYA PUTIH, SELAIN ITU HITAM
                      color: _kategori[index]['cat'] != 'Javascript'
                          ? Colors.white
                          : Colors.black,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

Adapun hasilnya akan sama persis dengan screenshoot sebelumnya, hanya menggunakan pendekatan yang bebeda untuk menyelesaikan permasalahan serupa.

Mengatur Lengkungan Object Dengan ClipRReact & ClipPath

Pendekatan lain dalam mengatur lengkungan tiap sudut dari sebuah objek adalah dengan menggunakan ClipRReact. Tapi kan Container juga bisa melakukannya? Benar, Container kaya akan attribute yang bisa membantu kita dalam menyelesaikan berbagai case, tapi Flutter menyediakan ClipRReact yang sifatnya to the point dalam mengatur borderRadius sehingga perlu dijadikan salah pilihan jika kita membutuhkannya hanya untuk sekedar mengatur radius.

Skenarionya adalah ke-6 kotak kategori yang sudah dibuat sebelumnya akan kita ubah menjadi lingkaran, maka buka file main.dart dan modifikasi bagian wrap Container yang ada di dalam GridView menjadi

//CODE SEBELUMNYA
itemBuilder: (context, index) {
  //BUNGKUS CONTAINER DENGAN CLIPRREACT
  return ClipRRect(
    borderRadius: BorderRadius.circular(100), //KEMUDIAN SET RADIUSNYA SETENGAH DARI UKURAN KOTAK AGAR MENDAPATKAN LINGKARAN SEMPURNA
    child: Container(
      color: _kategori[index]['color'],
      height: 200,
      child: Center(
        child: Text(
          _kategori[index]['cat'],
          style: TextStyle(
            color: _kategori[index]['cat'] != 'Javascript'
                ? Colors.white
                : Colors.black,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    ),
  );
},

Hasilnya akan terlihat seperti gambar berikut

menggunakan cliprreact flutter

Hal serupa juga bisa dilakukan pada sebuah gambar, bungkus widget Image dengan ClipRReact dan set radius-nya sesuai keinginan.

Opsi lainnya dari keluarga widget Clip adalah ClipPath dimana widget ini memiliki kemampuan untuk membuat curva sesuai keinginan sehingga kita bisa meng-custom lengkungan dari sebuah objek. Mari kita buat bentuk kotak sebelumnya menjadi sedikit melengkung curam pada sisi bawahnya. Modifikasi pada bagian itemBuilder menjadi.

itemBuilder: (context, index) {
  //ClipRReact KITA GANTI JADI ClipPath KARENA AKAN MENGGUNAKAN PENDEKATAN YANG BERBEDA
  return ClipPath(
    //CLIPPERNYA UNTUK MENDESAIN LENGKUNGAN YANG DIINGINKAN KITA BUAT CUSTOM CLASS
    //YANG NANTINYA AKAN DIBUAT SETELAH CODE INI
    clipper: ClipperContainer(),
    //YANG LAINNYA TETAP NORMAL SEPERTI SEBELUMNYA
    child: Container(
      color: _kategori[index]['color'],
      height: 200,
      child: Center(
        child: Text(
          _kategori[index]['cat'],
          style: TextStyle(
            color: _kategori[index]['cat'] != 'Javascript'
                ? Colors.white
                : Colors.black,
            fontWeight: FontWeight.bold,
          ),
        ),
      ),
    ),
  );
},

Kemudian di luar class MyApp, tambahkan class baru tersebut untuk menentukan titik poinnya.

//JADI CLASS INI MENG-EXTEND CUSTOMCLIPPER YANG SEBENARNYA BISA LANGSUNG DI EMBED PADA CODE DI ATAS
//AGAR LEBIH READBALE MAKA KITA PISAHKAN
class ClipperContainer extends CustomClipper<Path> {
  @override
  
  //ATTRIBUTE INI YANG AKAN MENJADI TEMPAT KITA MENDESAIN SETIAP SUDUTNYA
  Path getClip(Size size) {
    var path = Path(); //INISIASI PATHNYA TERLEBIH DAHULU
    path.lineTo(0, size.height); //GARIS PERTAMA PADA SUMBU X (WIDTH) KITA SET 0 KARENA AKAN DI TARIK PADA SISI PALING KIRI, DAN SUMBU Y (HEIGHT) KITA GUNAKAN SELURUH TINGGI ADA YANG ADA DARI KOTAK TERSEBUT
    
    //BAGIAN INI ADA 4 TITIK, X1 DAN X2, Y1 DAN Y2
    //SUMBU X1 KITA MULAI DARI SUDUT PALING KIRI
    //SUMBU Y1 KITA SET / 2 DARI LEBAR YANG ADA SEHINGGA AKAN MEMBENTUK TITIK LENGKUNG SESUAI LEBAR / 2
    //SEDANGKAN X2 DAN Y2 KITA GUNAKAN SIZE MAX NYA AGAR MEMBENTUK TITIK TEMU DI UJUNG KOTAK
    path.quadraticBezierTo(0, size.width / 2, size.height, size.width);
    path.lineTo(size.width, 0);
    path.close();
    return path;
  }

  @override
  bool shouldReclip(CustomClipper<Path> oldClipper) => true;
}

Note: Jika bingung terkait value apa sebaiknya yang dimasukkan, silahkan ubah value sesuai keinginan dan lihat perubahan setiap sudut yang dihasilkan. Karena intinya adalah saya ingin mengenalkan bahwa jika kamu ingin menggunakan potongan yang sesuai selera maka widget ClipPath akan menjadi widget yang tepat karena kita bisa menyesuaikannya.

Adapun hasil dari code di atas akan terlihat seperti gambar berikut

menggunakan clippath flutter

Membuat Animasi Transisi Dengan Widget Hero

Secara default, perpindahan page akan memuat content dari bawah ke atas sebagai transisinya dan perpindahan tersebut sudah sangat menarik. Akan tetapi kita juga memiliki opsi untuk membuat bentuk transisi lainnya dan widget pahlawan tersebut bernama Hero.

Kita akan sedikit me-refactor code sebelumnya mejadi seperti berikut ini sebelum melanjutkan untuk membuat second page-nya.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: FirstPage());
  }
}

class FirstPage extends StatelessWidget {
  final List<Map<String, dynamic>> _kategori = [
    {'cat': 'Flutter', 'color': Colors.pink},
    {'cat': 'Laravel', 'color': Colors.blue},
    {'cat': 'Vue.js', 'color': Colors.amber},
    {'cat': 'Javascript', 'color': Colors.yellow},
    {'cat': 'PHP', 'color': Colors.red},
    {'cat': 'Java', 'color': Colors.black},
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Grid View'),
      ),
      body: Container(
        margin: const EdgeInsets.all(10.0),
        child: GridView.builder(
          itemCount: _kategori.length,
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: 3,
              mainAxisSpacing: 3,
              crossAxisSpacing: 3,
              childAspectRatio: 1),
          itemBuilder: (context, index) {
            return ClipPath(
              clipper: ClipperContainer(),
              child: Container(
                color: _kategori[index]['color'],
                height: 200,
                child: Center(
                  child: Text(
                    _kategori[index]['cat'],
                    style: TextStyle(
                      color: _kategori[index]['cat'] != 'Javascript'
                          ? Colors.white
                          : Colors.black,
                      fontWeight: FontWeight.bold,
                    ),
                  ),
                ),
              ),
            );
          },
        ),
      ),
    );
  }
}

Note: Yang berbeda dari code di atas hanyalah widget MaterialApp dan Scaffold kita pisahkan class-nya agar back button tersedia secara otomatis ketika berpindah page.

Selanjutnya di dalam file yang sama, tambahkan class baru bernama SecondPage

class SecondPage extends StatelessWidget {
  //DEFINISIKAN VARIABLE 
  final Color _color;
  final String _title;

  //MINTA DATA TERSEBUT DARI PAGE SEBELUMNYA KETIKA PAGE INI DI-LOAD
  SecondPage(this._color, this._title);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Hero Animation'),
      ),
      body: Container(
        //WRAP CLIPPATH DENGAN WIDGET HERO, KEMUDIAN SET TAG-NYA SESUAI DENGAN VALUE YANG ADA DI VARIABLE _TITLE
        child: Hero(
          tag: _title,
          child: ClipPath(
            clipper: ClipperContainer(),
            child: Container(
              color: _color,
              height: 450,
              child: Center(
                child: Text(
                  _title,
                  style: TextStyle(
                      color:
                          _title != 'Javascript' ? Colors.white : Colors.black,
                      fontWeight: FontWeight.bold,
                      fontSize: 30),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

Terus kegunaan hero dan tag-nya untuk apa? Tenang, akan kita bahas setelah ini. Tapi sebelumnya, wrap ClipPath yang ada di dalam class FirstPage dengan widget Hero & GestureDetector untuk mendeteksi kegiatan user, dimana pada widget ini kita akan menggunakan attribute onTap nya untuk berpindah halaman.

//CODE SEBELUMNYA
//CODE DIBAWAH ADALAH ITEM BUILDER DARI GRID VIEW PADA FIRSTPAGE
itemBuilder: (context, index) {
  return GestureDetector(
    onTap: () {
    //KITA GUNAKAN ONTAP UNTUK BERPINDAH HALAMAN DEGAN NAVIGATOR
      Navigator.of(context).push(
        MaterialPageRoute(
          //DIMANA TUJUANNYA ADALAH SECONDAPAGE DAN MENGIRIMKAN DATA YANG DIMINTA
          builder: (context) => SecondPage(
            _kategori[index]['color'],
            _kategori[index]['cat'],
          ),
        ),
      );
    },
    //WRAP CLIPPATH DENGAN HER DAN SET TAG-NYA SAMA DENGAN DI-SECONDPAGE, DALAM HAL INI TITLE ATAU NAMA KATEGORI
    child: Hero(
      tag: _kategori[index]['cat'],
      child: ClipPath(
        clipper: ClipperContainer(),
        child: Container(
          color: _kategori[index]['color'],
          height: 200,
          child: Center(
            child: Text(
              _kategori[index]['cat'],
              style: TextStyle(
                color: _kategori[index]['cat'] != 'Javascript'
                    ? Colors.white
                    : Colors.black,
                fontWeight: FontWeight.bold,
              ),
            ),
          ),
        ),
      ),
    ),
  );
},

Dari sini bisa kita simpulkan bahwa fungsi dari attribute tag dari widget Hero adalah bersifat unik untuk menandakan bahwa tujuan dari animasinya pada halaman selanjutnya adalah widget Hero yang memiliki tag dengan value yang sama.

Silahkan hot re-load dan lakukan perpindahan page, maka animasinya akan sedikit berbeda dari sebelumnya dimana object Container seakan memiliki efek membesar. Biasanya pada real device akan lebih smooth ketimbang emulator.

Baca Juga: Mengenal Widget Flutter #3: Image, Flexible & Spacer

Kesimpulan

UI Instagram-nya mana? Yah kecewa! Tenang akan kita bahas pada artikel berikut karena artikel ini sudah sangat panjang dimana posisinya sudah lebih dari 2000 kata, sehingga akan sangat membosankan membacanya lebih banyak lagi. Jadi berikutnya kita akan mengulas semua widget yang sudah dipelajari lagi dengan sebuah case yang sudah disebutkan diawal.

Adapun pada materi ini kita telah belajar banyak hal guna menunjang kita dalam membuat aplikasi mobile menggunakan Flutter.

Category:
Share:

Comments