Mengenal Widget Flutter #2: Container, Stack & Positioned

Mengenal Widget Flutter #2: Container, Stack & Positioned

Pendahuluan

Perkenalan lebih lanjut dengan widget Flutter yang banyak digunakan secara umum untuk membuat dan mengatur layout pada aplikasi mobile yang dibangun dengan Flutter. Container, Stack & Positioned akan menjadi materi pembahasan pada kesempatan kali ini untuk melengkapi materi sebelumnya yang membahas widget Row-Column.

Adapun alur dari materi ini adalah dengan membahas ketiga widget tersebut secara individu untuk memperlihatkan kegunaannya masing-masing, kemudian diakhir materi kita akan mengerjakan sebuah case sederhana dengan menyatukan ketiga widget tersebut dalam satu frame.

Baca Juga: Mengenal Widget Flutter #1: Scaffold & Row-Column

Beragam Fungsi Container

Secara bahasa, Container jika diterjemahkan ke dalam bahasa indonesia maka hasilnya adalah wadah. Bagaikan sebuah wadah, container ini memiliki banyak fungsi untuk menampung objek lainnya atau sebagai dirinya sendiri. Beberapa step ke depan, kita akan berinteraksi dengan Container dengan menggunakan beragam attribute yang dimilikinya dan menggunakannya untuk menyelesaikan beberapa case.

Namun sebelum itu, kita mulai dengan Flutter fresh install.

flutter create dw_container

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('Container'),
        ),
        body: Container(
          margin: EdgeInsets.all(10),
          color: Colors.pinkAccent,
        ),
      ),
    );
  }
}

Penjelasan: Fokusnya adalah membahas Container, jadi pada code di atas, kita membuat sebuah objek menggunakan Container dengan mengatur warnanya serta memberikan margin. EdgeInsets.all() artinya memberikan margin sebesar value yang ada di dalamnya pada semua sisi (left, top, right, bottom). Sifat dasar Container secara default akan memenuhi semua ruang yang diberikan.

Sedikit meng-explore lebih jauh tentang apa saja yang bisa dilakukan dengan Container, dengan file yang sama lakukan 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('Container'),
        ),
        body: Container(
          margin: EdgeInsets.all(10),
          padding: EdgeInsets.all(15),
          color: Colors.pinkAccent,
          child: Container(
            decoration: BoxDecoration(
              color: Colors.lightGreen,
              gradient: LinearGradient(
                begin: Alignment.topCenter,
                end: Alignment.bottomCenter,
                colors: [Colors.lightGreen, Colors.limeAccent],
              ),
              borderRadius: BorderRadius.circular(30),
              boxShadow: <BoxShadow>[
                BoxShadow(blurRadius: 10),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

Penjelasan: Container di dalam Container, jadi selain menjadi parent, juga bisa berperan sebagai child widget. Case di atas kita membuat sebuah kotak besar yang berwarna pink dan di dalamnya terdapat kotak lainnya dengan warna gradient. Cara lain mewarnai Container adalah dengan menggunakan parent pada BoxDecoration. Adapun borderRadius berperan untuk mengatur lengkungan sudut dari kotak yang kita buat. Intinya adalah decoration ini memiliki banyak attribute untuk mengatur style dari container sesuai dengan keinginan penggunanya.

Stack & Positioned

Meski tak sepaket tapi tak apalah kita jadikan berpasangan, jadi Stack itu memungkinkan widget yang ada di dalamnya (red: children) untuk di-set posisinya. Sehingga dengan bantuan Positioned maka kita dapat mengarahkan dimana posisi widget tersebut hendak ditempatkan.

Jadi schema yang diinginkan untuk mengenal cara kerja dari Stack dan Positioned kali adalah kita akan membuat sebuah kotak menggunakan container dan di sudut kanan bawah kotak tersebut terdapat kotak kecil yang berisikan text "Stack & Positioned".

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('Container'),
        ),
        body: Stack(
          children: <Widget>[
            Container(
              color: Colors.orangeAccent,
              height: 250,
            ),
            Positioned(
              top: 195,
              left: 155,
              child: Container(
                height: 50,
                width: 250,
                color: Colors.black38,
                child: Center(
                  child: Text(
                    "Stack & Positioned",
                    style: TextStyle(
                      color: Colors.white,
                      fontWeight: FontWeight.bold,
                      fontSize: 20,
                    ),
                  ),
                ),
              ),
            )
          ],
        ),
      ),
    );
  }
}

Penjelasan: Stack itu hampir sama dengan Column dalam hal kesanggupan menampung widget, yakni bisa menampung lebih dari 1 widget sebagai children-nya. Hanya saja yang membedakan adalah widget yang ada di dalam Stack saling tumpang tindih sehingga dibutuhkan dekorasi lanjutan untuk menempatkan posisinya mau dibawa kemana. Sedangkan bagian yang penting dari Positioned adalah attribute top, left, bottom, right yang berfungsi untuk mengatur jarak dari sudut sekitarnya.

Kesimpulan dari duet Stack & Positioned adalah kemampuan untuk mengatur tatak letak secara custom, berbeda halnya dengan Column-Row yang menempatkan widget secara berurutan baik secara vertical dan horizontal.

Membuat UI Catatan Keuangan Pribadi

Setelah belajar fungsi dari masing-masing widget di atas, yakni: Container, Stack & Positioned, maka pada part kali ini kita akan menyelesaikan sebuah case sederhana sebagaimana screenshoot yang dilampirkan diawal materi, yaitu membuat UI atau tampilan aplikasi catatan keuangan pribadi.

Jika dilihat kembali gambar yang ada di pendahuluan, ada 3 buah object yang berbeda, pertama adalah sebuah container dengan warna pink disertai borderRadius pada posisi bawah dibuat sedikit melengkung. Kedua adalah container baru dengan warna putih dan diletakkan diatas container sebelumnya dengan memanfaatkan positioned. Bagian terakhir adalah Card yang dituliskan sebanyak 3 kali (note: Karena kita belum belajar listView dan materi ini masih sekedar UI) sebagai history pengeluaran dan pemasukan.

Sebaiknya install Flutter yang baru, kemudian 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(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Container(
          //MENGGUNAKAN COLUMN KARENA ADA DUA OBJEK YANG INGIN DIPISAHKAN
          //PERTAMA ADALAH HEADER BESERTA SUMMARYNYA DAN YANG KEDUA ADALAH HISTORY
          child: Column(
            children: <Widget>[
              //KITA GUNAKAN STACK UNTUK MENGATUR POSITION HEADER SERTA CONTAINER UNTUK SUMMARY
              Stack(
                children: <Widget>[backgroundHeader(), summaryCash()],
              ),
                
              //BAGIAN INI AKAN MENAMPILKAN HISTORY PENGELUARAN
              cardDetail('Makan Siang', 'Beli Makan Di Warteg', '10.000', 'out'),
              cardDetail('Bonus', 'Dapat Bonus Proyek', '500.000', 'in'),
              cardDetail('Beli Baju', 'Baju Kemeja Kantor', '250.000', 'out'),
            ],
          ),
        ),
      ),
    );
  }
}

Widget cardDetail(title, description, price, type) {
  //CARD UNTUK HISTORY PENGELUARAN
}

Widget summaryCash() {
  //CONTAINER UNTUK SUMMARY PENGELUARAN DAN PEMASUKAN
}

Widget backgroundHeader() {
  //CONTAINER UNTUK HEADER 
}

Note: Bagian Header, Summary dan Card yang meng-handle history dipisahkan ke dalam masing-masing fungsi agar lebih mudah untuk mengelolanya sehingga tidak menumpuk.

Langkah pertama adalah kita kerjakan bagian header terlebih dahulu, modifikasi fungsinya menjadi

Widget backgroundHeader() {
  //KITA BUAT CONTAINER DENGAN TINGGI SEBESAR 300, DAN LEBAR SEJAUH YANG BISA DIJANGKAU
  //BOXDECORATIONNYA KITA SET WARNANYA PINKACCENT DAN PADA BAGIAN BAWAH KIRI-KANAN DIBUAT LENGKUNGAN
  return Container(
    height: 300,
    width: double.infinity,
    decoration: BoxDecoration(
      color: Colors.pinkAccent,
      borderRadius: BorderRadius.only(
        bottomLeft: Radius.circular(30),
        bottomRight: Radius.circular(30),
      ),
    ),
    //ADAPUN CHILD DARI CONTAINER DIATAS KITA ATUR POSISINYA MENGGUNAKAN PADDING
    child: Padding(
      //PADDINGNYA DI-SET HANYA UNTUK TOP DAN LEFT
      padding: const EdgeInsets.only(top: 60, left: 20),
      //KARENA KITA AKAN MENAMPILKAN LEBIH DARI 1 OBJEK YANG BERUSUSUN KEBAWAH
      //MAKA KITA GUNAKAN COLUMN
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          //BAGIAN INI NORMAL, HANYA MENAMPILKAN TEXT DENGAN STYLE MASING-MASING
          Text(
            "Anugrah Sandi",
            style: TextStyle(
                fontSize: 25, color: Colors.white, fontWeight: FontWeight.bold),
          ),
          Text(
            "085343966997",
            style: TextStyle(
              fontSize: 15,
              color: Colors.white,
            ),
          ),
        ],
      ),
    ),
  );
}

Jika di-run maka code di atas akan membentuk sebuah header berlatar pink dengan 2 buah teks di dalamnya. Selanjutnya adalah menempatkan summary di atas header tersebut, modifikasi fungsi summaryCash() menjadi

Widget summaryCash() {
  //CONTAINER KEDUA INI BERWARNA PUTIH, KITA SET POSITIONEDNYA DENGAN MENENTUKAN VALUE DARI TOP DAN LEFT AGAR BERADA DITENGAH, DISESUAIKAN SAJA
  return Positioned(
    top: 180,
    left: 20,
    //CONTAINER KEDUA INI KITA BUAT LEBIH KECIL DENGAN MENENTUKAN WIDTH DAN HEIGHNYA TERBATAS
    child: Container(
      width: 370,
      height: 140,
      //SAMA HALNYA DENGAN CONTAINER SEBELUMNYA, WARNANYA DI-SET DAN BORDERRADIUSNYA KALI INI BERBEDA KITA SET KE-4 SISINYA AGAR MELENGKUNG
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(30),
      ),
      //CHILD DARI CONTAINER INI DI-SET PADDINGNYA AGAR TERDAPAT JARAK DARI ATAS
      child: Padding(
        padding: const EdgeInsets.only(top: 30.0),
        //KARENA ADA DUA BAGIAN YANG BERBARIS DARI KIRI KE KANAN MAKA KITA GUNAKAN ROW()
        child: Row(
          //MAIN ALIGMENTNYA DI-SET SPACEAROUND AGAR KEDUA OBJEKNYA ADA JARAK YANG SESUAI
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            //MASING-MASING OBJECT MENGGUNAKAN COLUMN LAGI ADA DUA BUAH TEKS YANG INGIN DITAMPILKAN SECARA VERTICAL
            Column(
              children: <Widget>[
                Text("Penghasilan"),
                Divider(),
                Text(
                  "Rp 500.000",
                  style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
                ),
              ],
            ),
            Column(
              children: <Widget>[
                Text("Pengeluaran"),
                Divider(),
                Text(
                  "Rp 260.000",
                  style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
                ),
              ],
            ),
          ],
        ),
      ),
    ),
  );
}

Bagian terakhir adalah membuat Card history-nya, modifikasi fungsi cardDetail() menjadi

//KARENA INI SIFATNYA REUSABLE, MAKA KITA MEMINTA 3 DATA: TITLE, DESC, DAN PRICE
Widget cardDetail(title, description, price, type) {
  //BUAT CARD
  return Card(
    //DENGAN MARGIN YANG DISESUAIKAN
    margin: EdgeInsets.only(top: 15, left: 15, right: 15),
    //DENGAN KETEBALAN AGAR MEMBENTUK SHADOW SENILAI 8
    elevation: 8,
    //CHILD DARI CARD MENGGUNAKAN LISTTILE AGAR LEBIH MUDAH MENGATUR AREANYA
    //KARENA SECARA DEFAULT LISTTILE TELAH TERBAGI MENJADI 3 BAGIAN
    //POSISI KIRI (LEADING), TENGAH (TITLE), BAWAH TENGAH (SUBTITLE) DAN KANAN(TRAILING)
    //SEHINGGA KITA HANYA TINGGAL MEMASUKKAN TEKS YANG SESUAI
    child: ListTile(
      leading: Icon(
        type == 'out' ? Icons.subdirectory_arrow_left:Icons.subdirectory_arrow_right,
        color: type == 'out' ? Colors.redAccent:Colors.lightGreen,
      ),
      title: Text(
        title,
        style: TextStyle(fontWeight: FontWeight.bold),
      ),
      subtitle: Text(description),
      trailing: Text(
        "Rp "+price,
        style: TextStyle(color: type == 'out' ? Colors.redAccent:Colors.lightGreen),
      ),
    ),
  );
}

Jika di-save dan di-run maka hasil yang akan diperoleh akan sesuai dengan gambar yang telah dilampirkan sebelumnya dan hanya bermodalkan Container, Stack & Positioned maka kita sudah bisa membuat sebuah UI yang sederhana tapi menarik untuk digunakan.

Baca Juga: Aplikasi E-Commerce Laravel 6 #10: Multiple Authentication

Kesimpulan

Memahami Flutter adalah tentang memahami bagaimana cara menggunakan widget yang dimilikinya, karena layout yang dihasilkannya adalah kombinasi dari satu widget dengan widget lainnya. Tapi kan attribute-nya banyak? Ingat dalam dunia pemrograman tidak semua hal yang disediakan oleh bahasa pemrograman atau framework itu sering digunakan, maka pahami cara kerja attribute yang umum digunakan.

Category:
Share:

Comments