Membuat Layout Responsif



1. Membuat Layout Responsif

Pengertian

Layout responsif adalah desain tata letak yang dapat menyesuaikan tampilannya secara otomatis berdasarkan ukuran layar perangkat (misalnya, smartphone, tablet, desktop).

Tujuan

  • Memberikan pengalaman pengguna terbaik di berbagai perangkat.

  • Menghindari tampilan yang rusak atau tidak proporsional.

Prinsip Dasar

  • Gunakan ukuran relatif (persentase, flex) daripada ukuran absolut (pixel tetap).

  • Gunakan grid atau system flexbox untuk mengatur komponen.

  • Gunakan media queries atau breakpoints untuk menyesuaikan tampilan pada ukuran layar berbeda.

  • Sembunyikan atau tampilkan elemen tertentu sesuai ukuran layar.


2. Navigasi

Pengertian

Navigasi adalah cara pengguna berpindah antar halaman atau layar di dalam aplikasi.

Jenis Navigasi Umum

  • Navigasi Stack (tumpukan): Navigasi halaman baru di atas halaman lama, biasanya dengan tombol "back" untuk kembali.

  • Navigasi Tab: Pengguna memilih tab untuk berpindah antar layar.

  • Drawer/Menu Samping: Menu tersembunyi yang muncul dari samping layar.

Konsep Penting

  • Route: Halaman atau layar dalam aplikasi.

  • Navigator: Sistem pengelola route dan riwayat navigasi.

  • Pastikan navigasi intuitif dan mudah digunakan.


3. Stateful Widget

Pengertian

Stateful widget adalah komponen yang bisa berubah atau memperbarui tampilannya ketika data internal atau statusnya berubah.

Perbedaan dengan Stateless Widget

  • Stateless widget: Tidak memiliki perubahan internal; tampilannya statis.

  • Stateful widget: Memiliki state (data/status) yang bisa berubah dan memicu rebuild tampilan.

Contoh Penggunaan

  • Form input yang merespon perubahan teks.

  • Tombol yang berubah warna saat ditekan.

  • Widget dengan data yang diambil secara dinamis.

Manfaat

  • Memberikan interaktivitas pada aplikasi.

  • Memungkinkan UI menyesuaikan diri dengan perubahan data.


4. Dynamic Theme

Pengertian

Dynamic theme adalah kemampuan aplikasi untuk mengubah tema (warna, font, gaya) secara dinamis, biasanya berdasarkan preferensi pengguna atau kondisi tertentu (misalnya mode terang/gelap).

Manfaat

  • Memberikan pengalaman personalisasi.

  • Mendukung mode gelap yang lebih nyaman di malam hari.

  • Meningkatkan aksesibilitas dan estetika aplikasi.

Cara Kerja

  • Tema aplikasi didefinisikan dalam satu set properti (warna utama, latar belakang, teks).

  • State atau konfigurasi tema dapat diubah secara runtime.

  • UI otomatis memperbarui tampilannya sesuai tema baru.


Code Zapp Run

import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatefulWidget {
  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool _isDarkMode = false;

  void _toggleTheme() {
    setState(() {
      _isDarkMode = !_isDarkMode;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Pola Hidup Sehat untuk Wanita',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        brightness: Brightness.light,
        primaryColor: Color.fromARGB(255, 240, 49, 230),
        colorScheme: ColorScheme.light(
          primary: Color.fromARGB(255, 237, 40, 227)!,
          secondary: Colors.teal,
        ),
        scaffoldBackgroundColor: Colors.grey[50],
        appBarTheme: AppBarTheme(
          backgroundColor: Color.fromARGB(255, 240, 45, 231),
          elevation: 4,
          titleTextStyle: TextStyle(
            fontSize: 22,
            fontWeight: FontWeight.bold,
            color: Colors.white,
          ),
          iconTheme: IconThemeData(color: Colors.white),
        ),
        cardTheme: CardTheme(
          elevation: 6,
          margin: EdgeInsets.symmetric(vertical: 8, horizontal: 12),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(16),
          ),
        ),
        textTheme: TextTheme(
          headline6: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
          subtitle1: TextStyle(fontSize: 18, fontWeight: FontWeight.w600),
          bodyText1: TextStyle(fontSize: 16),
          bodyText2: TextStyle(fontSize: 14),
        ),
      ),
      darkTheme: ThemeData(
        brightness: Brightness.dark,
        primaryColor: Color.fromARGB(255, 240, 24, 190),
        colorScheme: ColorScheme.dark(
          primary: Color.fromARGB(255, 220, 18, 220)!,
          secondary: Colors.tealAccent,
        ),
        scaffoldBackgroundColor: Colors.grey[900],
        appBarTheme: AppBarTheme(
          backgroundColor: Color.fromARGB(255, 227, 19, 189),
          elevation: 4,
          titleTextStyle: TextStyle(
            fontSize: 22,
            fontWeight: FontWeight.bold,
            color: Colors.white,
          ),
          iconTheme: IconThemeData(color: Colors.white),
        ),
        cardTheme: CardTheme(
          elevation: 6,
          margin: EdgeInsets.symmetric(vertical: 8, horizontal: 12),
          shape: RoundedRectangleBorder(
            borderRadius: BorderRadius.circular(16),
          ),
        ),
        textTheme: TextTheme(
          headline6: TextStyle(fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white),
          subtitle1: TextStyle(fontSize: 18, fontWeight: FontWeight.w600, color: Colors.white70),
          bodyText1: TextStyle(fontSize: 16, color: Colors.white70),
          bodyText2: TextStyle(fontSize: 14, color: Colors.white60),
        ),
      ),
      themeMode: _isDarkMode ? ThemeMode.dark : ThemeMode.light,
      home: HomePage(
        isDarkMode: _isDarkMode,
        onThemeToggle: _toggleTheme,
      ),
    );
  }
}

class HomePage extends StatelessWidget {
  final bool isDarkMode;
  final VoidCallback onThemeToggle;

  HomePage({ required this.isDarkMode, required this.onThemeToggle });

  final List<_ContentItem> items = [
    _ContentItem(
      title: 'Makanan Sehat: Sayur',
      imagePath: 'assets/sayuran.jpg',
      cara: 'Konsumsi sayur segar minimal 1‑2 jenis saat satu kali makan. Variasi warna (hijau, merah, kuning) agar nutrisi lengkap.',
      penjelasan: 'Sayur kaya serat, vitamin, mineral, membantu pencernaan, mencegah sembelit, dan mendukung sistem imun. Memasak ringan agar vitamin tidak hilang. '
    ),
    _ContentItem(
      title: 'Makanan Sehat: Buah',
      imagePath: 'assets/buah.jpg',
      cara: 'Makan buah utuh sebagai camilan atau setelah makan. Pilih buah lokal yang matang. Hindari ditambah gula.',
      penjelasan: 'Buah mengandung vitamin dan antioksidan, membantu detoks tubuh, menjaga kulit dan kesehatan mata. Serat buah baik untuk perut.'
    ),
    _ContentItem(
      title: 'Olahraga',
      imagePath: 'assets/olahraga.jpg',
      cara: 'Lakukan olahraga ringan‑sedang seperti jalan cepat 30 menit, yoga atau pilates 3‑4 kali seminggu.',
      penjelasan: 'Olahraga meningkatkan sirkulasi darah, menjaga berat badan, meningkatkan mood, memperkuat tulang dan otot.'
    ),
    _ContentItem(
      title: 'Urutan Skincare',
      imagePath: 'assets/skincare.jpg',
      cara: 'Bersihkan wajah → toner → serum → pelembab → sunscreen di pagi hari. Malam hari bisa ditambah malam treatment seperti eksfoliasi atau masker.',
      penjelasan: 'Perawatan kulit sebaiknya dimulai dari yang ringan ke berat. Sunscreen penting agar kulit tidak kusam & terlindungi dari sinar UV.'
    ),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Pola Hidup Sehat untuk Wanita'),
        actions: [
          IconButton(
            icon: Icon(isDarkMode ? Icons.wb_sunny : Icons.nightlight_round),
            onPressed: onThemeToggle,
            tooltip: isDarkMode ? 'Mode Terang' : 'Mode Gelap',
          ),
        ],
      ),
      body: LayoutBuilder(
        builder: (ctx, constraints) {
          double width = constraints.maxWidth;
          int crossAxisCount;
          if (width >= 1200) {
            crossAxisCount = 3;
          } else if (width >= 800) {
            crossAxisCount = 2;
          } else {
            crossAxisCount = 1;
          }

          return GridView.builder(
            padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16),
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
              crossAxisCount: crossAxisCount,
              crossAxisSpacing: 16,
              mainAxisSpacing: 16,
              childAspectRatio: 4/3, // lebar : tinggi
            ),
            itemCount: items.length,
            itemBuilder: (context, index) {
              final item = items[index];
              return ContentCard(item: item);
            },
          );
        }
      ),
    );
  }
}

class ContentCard extends StatelessWidget {
  final _ContentItem item;

  ContentCard({ required this.item });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        // navigasi ke halaman detail
        Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => DetailPage(item: item),
          ),
        );
      },
      child: Card(
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(16),
        ),
        elevation: 6,
        clipBehavior: Clip.hardEdge,
        child: Stack(
          children: [
            // Gambar sebagai latar
            Positioned.fill(
              child: Image.asset(
                item.imagePath,
                fit: BoxFit.cover,
              ),
            ),
            // overlay gelap untuk teks agar terbaca
            Positioned.fill(
              child: Container(
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    colors: [Colors.black.withOpacity(0.5), Colors.transparent],
                    begin: Alignment.bottomCenter,
                    end: Alignment.topCenter,
                  ),
                ),
              ),
            ),
            // Judul di bagian bawah
            Positioned(
              left: 12,
              right: 12,
              bottom: 12,
              child: Text(
                item.title,
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                  shadows: [
                    Shadow(
                      blurRadius: 4,
                      color: Colors.black45,
                      offset: Offset(0, 2),
                    )
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class DetailPage extends StatelessWidget {
  final _ContentItem item;

  DetailPage({ required this.item });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(item.title),
      ),
      body: SingleChildScrollView(
        child: Padding(
          padding: EdgeInsets.all(16),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              // Gambar besar
              ClipRRect(
                borderRadius: BorderRadius.circular(16),
                child: Image.asset(
                  item.imagePath,
                  fit: BoxFit.cover,
                ),
              ),
              SizedBox(height: 16),
              Text(
                'Cara Caranya:',
                style: Theme.of(context).textTheme.headline6,
              ),
              SizedBox(height: 8),
              Text(
                item.cara,
                style: Theme.of(context).textTheme.bodyText1,
              ),
              SizedBox(height: 16),
              Text(
                'Penjelasan:',
                style: Theme.of(context).textTheme.headline6,
              ),
              SizedBox(height: 8),
              Text(
                item.penjelasan,
                style: Theme.of(context).textTheme.bodyText1,
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _ContentItem {
  final String title;
  final String imagePath;
  final String cara;
  final String penjelasan;

  _ContentItem({
    required this.title,
    required this.imagePath,
    required this.cara,
    required this.penjelasan,
  });
}


Gambarannya













Comments

Popular posts from this blog

Artikel terkait Pengembangan Gim

Memahami UI Flutter

project planning ui/ux