Bikin tampilan layout responsif yang simpel namun bagus💻
📱 Flutter Responsive Layout: Bikin UI Fleksibel yang Keren!
Halo coder squad! 👋
Kalau biasanya kita bikin UI Flutter seadanya—Column → Row → selesai—kali ini kita bakal bikin layout responsif yang kece dan siap tampil di berbagai ukuran layar. Bukan cuma “bisa tampil”, tapi juga adaptif dan enak dipandang. 🔥
🔑 Fitur Keren di Layout Responsif Ini
-
Column & Row Kombinasi → Bikin susunan widget rapi baik horizontal maupun vertikal.
-
Flexible & Expanded → Supaya komponen otomatis menyesuaikan ruang, gak ada yang ketiban.
-
LayoutBuilder → Jadi UI bisa menyesuaikan breakpoint—beda style antara HP, tablet, atau web.
-
Responsive Card Style → Setiap box punya padding dan margin yang pas, tetap proporsional di semua layar.
Codenya: // main.dart
// Full working Responsive Real-Estate Dashboard (all widgets included).// Paste this entire file into main.dart and run in zapp.run.import 'package:flutter/material.dart';void main() => runApp(const RealEstateApp());class RealEstateApp extends StatelessWidget {const RealEstateApp({super.key});@overrideWidget build(BuildContext context) {return MaterialApp(debugShowCheckedModeBanner: false,title: 'Real Estate Dashboard',theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal, brightness: Brightness.dark),useMaterial3: true,),home: const RealEstateDashboard(),);}}class RealEstateDashboard extends StatefulWidget {const RealEstateDashboard({super.key});@overrideState<RealEstateDashboard> createState() => _RealEstateDashboardState();}class _RealEstateDashboardState extends State<RealEstateDashboard> {int _selectedIndex = 0;bool _compactMode = false;// Daftar halaman untuk BottomNavigationBarfinal List<Widget> _pages = [const HomePage(),const ListingsPage(),const SettingsPage(),const ProfilePage(),];void _onNavTap(int idx) {setState(() => _selectedIndex = idx);}void _openActionSheet() {showModalBottomSheet(context: context,builder: (_) => SafeArea(child: Column(mainAxisSize: MainAxisSize.min, children: [ListTile(leading: const Icon(Icons.add), title: const Text('Tambah listing'), onTap: () => Navigator.pop(context)),ListTile(leading: const Icon(Icons.calendar_today), title: const Text('Jadwal kunjungan'), onTap: () => Navigator.pop(context)),ListTile(leading: const Icon(Icons.close), title: const Text('Batal'), onTap: () => Navigator.pop(context)),]),),);}@overrideWidget build(BuildContext context) {return Scaffold(extendBodyBehindAppBar: true,appBar: AppBar(elevation: 0,backgroundColor: Colors.transparent,title: const Text('Real Estate Dashboard'),actions: [IconButton(tooltip: 'Toggle compact mode',onPressed: () => setState(() => _compactMode = !_compactMode),icon: AnimatedSwitcher(duration: const Duration(milliseconds: 300),child: _compactMode? const Icon(Icons.mobile_friendly, key: ValueKey('mobile')): const Icon(Icons.desktop_mac, key: ValueKey('desktop')),),)],),body: Container(decoration: const BoxDecoration(gradient: LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [Color(0xFF041726), Color(0xFF123243)]),),child: SafeArea(child: _pages[_selectedIndex],),),bottomNavigationBar: NavigationBar(selectedIndex: _selectedIndex,onDestinationSelected: _onNavTap,destinations: const [NavigationDestination(icon: Icon(Icons.home), label: 'Home'),NavigationDestination(icon: Icon(Icons.list), label: 'Listings'),NavigationDestination(icon: Icon(Icons.settings), label: 'Settings'),NavigationDestination(icon: Icon(Icons.person), label: 'Profile'),],),floatingActionButton: _selectedIndex == 0 ? FloatingActionButton.extended(onPressed: _openActionSheet,label: const Text('Tambah Listing'),icon: const Icon(Icons.add_home),) : null,);}}// ------------------- Home Page -------------------class HomePage extends StatelessWidget {const HomePage({super.key});@overrideWidget build(BuildContext context) {return LayoutBuilder(builder: (context, constraints) {final w = constraints.maxWidth;if (w >= 1100) return _buildDesktopLayout(context, false);if (w >= 700) return _buildTabletLayout(context, false);return _buildMobileLayout(context, false);});}}// ------------------- Layouts -------------------Widget _buildDesktopLayout(BuildContext context, bool compact) {return Padding(padding: const EdgeInsets.all(24.0),child: Row(children: [Flexible(flex: 3,child: ConstrainedBox(constraints: const BoxConstraints(minWidth: 0),child: Column(crossAxisAlignment: CrossAxisAlignment.stretch,children: const [_WelcomeCardRealEstate(big: true),SizedBox(height: 16),_StatsRowRealEstate(),SizedBox(height: 16),Expanded(child: _RecentInquiriesList()),],),),),const SizedBox(width: 20),Flexible(flex: 5,child: ConstrainedBox(constraints: const BoxConstraints(minWidth: 0),child: Column(children: [const _MarketBanner(),const SizedBox(height: 16),Expanded(child: _PropertiesGrid(shrinkWrap: false)),],),),),const SizedBox(width: 20),Flexible(flex: 2,child: ConstrainedBox(constraints: const BoxConstraints(minWidth: 0),child: Column(children: const [_AgentCard(),SizedBox(height: 16),_QuickPropertyActions(),],),),),],),);}Widget _buildTabletLayout(BuildContext context, bool compact) {return Padding(padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),child: Column(children: [const _WelcomeCardRealEstate(big: false),const SizedBox(height: 12),const _MarketBanner(),const SizedBox(height: 12),const _StatsRowRealEstate(),const SizedBox(height: 12),Expanded(child: Row(children: [Flexible(flex: 3, child: _PropertiesGrid(shrinkWrap: false)),const SizedBox(width: 12),const Flexible(flex: 2, child: _QuickPropertyActions()),],),),],),);}Widget _buildMobileLayout(BuildContext context, bool compact) {return SingleChildScrollView(padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),child: Column(crossAxisAlignment: CrossAxisAlignment.stretch,children: const [_WelcomeCardRealEstate(big: false),SizedBox(height: 12),_MarketBanner(),SizedBox(height: 12),_StatsRowRealEstate(),SizedBox(height: 12),_QuickPropertyActions(),SizedBox(height: 12),_PropertiesGrid(shrinkWrap: true),SizedBox(height: 12),_RecentInquiriesList(),],),);}// ------------------- Widgets (real-estate theme) -------------------class _WelcomeCardRealEstate extends StatelessWidget {final bool big;const _WelcomeCardRealEstate({this.big = false, super.key});void _onAdd(BuildContext context) {showDialog(context: context,builder: (_) => AlertDialog(title: const Text('Tambah Listing'),content: const Text('Form tambah listing akan muncul di sini (mock).'),actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('Tutup'))],),);}@overrideWidget build(BuildContext context) {return Container(padding: const EdgeInsets.all(16),decoration: BoxDecoration(borderRadius: BorderRadius.circular(16),color: Colors.white.withOpacity(0.02),boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.35), blurRadius: 12, offset: const Offset(0, 6))],),child: Row(children: [CircleAvatar(radius: big ? 32 : 26, backgroundColor: Colors.tealAccent.shade700, child: const Icon(Icons.home_work, color: Colors.white)),const SizedBox(width: 12),Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: const [Text('Halo, Agen RIZQI', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white)),SizedBox(height: 6),Text('Ringkasan pasar dan listing kamu hari ini.', style: TextStyle(color: Colors.white70)),],),),ElevatedButton.icon(onPressed: () => _onAdd(context),icon: const Icon(Icons.add_business),label: const Text('Tambah'),style: ElevatedButton.styleFrom(minimumSize: const Size(64, 36), padding: const EdgeInsets.symmetric(horizontal: 12)),)],),);}}class _MarketBanner extends StatelessWidget {const _MarketBanner({super.key});void _onView(BuildContext context) {Navigator.of(context).push(MaterialPageRoute(builder: (_) => const MarketInsightPage()));}void _onShare(BuildContext context) {showDialog(context: context, builder: (_) => AlertDialog(title: const Text('Bagikan Insight'), content: const Text('Bagikan ringkasan pasar ke kolega.'), actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))]));}@overrideWidget build(BuildContext context) {return InkWell(borderRadius: BorderRadius.circular(16),onTap: () => _onView(context),child: Container(padding: const EdgeInsets.all(18),decoration: BoxDecoration(borderRadius: BorderRadius.circular(16),gradient: const LinearGradient(colors: [Color(0xFF0EA5A3), Color(0xFF1E3A8A)]),boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.25), blurRadius: 10, offset: const Offset(0, 6))],),child: Row(children: [Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [const Text('Market Insight', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),const SizedBox(height: 8),const Text('Permintaan naik 12% — area Jakarta Selatan menunjukkan kenaikan harga.', style: TextStyle(color: Colors.white70)),const SizedBox(height: 12),Row(children: [ElevatedButton(onPressed: () => _onView(context), child: const Text('Lihat detail')),const SizedBox(width: 8),OutlinedButton(onPressed: () => _onShare(context), child: const Text('Bagikan')),])]),),const SizedBox(width: 12),Container(width: 120, height: 70, decoration: BoxDecoration(color: Colors.white.withOpacity(0.12), borderRadius: BorderRadius.circular(12)), child: const Icon(Icons.pie_chart, size: 36, color: Colors.white70)),],),),);}}class _StatsRowRealEstate extends StatelessWidget {const _StatsRowRealEstate({super.key});@overrideWidget build(BuildContext context) {return Row(children: const [Expanded(child: _StatTileRealEstate(title: 'Listing Terpublish', value: '24', accent: Colors.teal)),SizedBox(width: 12),Expanded(child: _StatTileRealEstate(title: 'Avg Price', value: 'Rp 2.1M', accent: Colors.amber)),SizedBox(width: 12),Expanded(child: _StatTileRealEstate(title: 'Permintaan', value: '48', accent: Colors.indigo)),]);}}class _StatTileRealEstate extends StatelessWidget {final String title;final String value;final Color accent;const _StatTileRealEstate({required this.title, required this.value, required this.accent, super.key});@overrideWidget build(BuildContext context) {return Container(padding: const EdgeInsets.all(12),decoration: BoxDecoration(color: Colors.white.withOpacity(0.02), borderRadius: BorderRadius.circular(12)),child: Row(children: [CircleAvatar(backgroundColor: accent.withOpacity(0.15), child: Icon(Icons.location_on, color: accent)),const SizedBox(width: 12),Flexible(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text(title, style: const TextStyle(color: Colors.white70)),const SizedBox(height: 6),Text(value, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),]),)]),);}}// ------------------- Properties grid & model -------------------class PropertyModel {final int id;final String title;final String location;final String price;final String description;const PropertyModel({required this.id, required this.title, required this.location, required this.price, required this.description});}class _PropertiesGrid extends StatelessWidget {final bool shrinkWrap;const _PropertiesGrid({this.shrinkWrap = true, super.key});@overrideWidget build(BuildContext context) {final props = List.generate(6,(i) => PropertyModel(id: i + 1,title: 'Rumah ${i + 1}',location: i % 2 == 0 ? 'Jakarta Selatan' : 'Bandung',price: (800 + i * 150).toString() + ' jt',description: 'Rumah nyaman dengan akses ke fasilitas umum — 3 kamar, 2 kamar mandi.',),);return LayoutBuilder(builder: (context, constraints) {final w = constraints.maxWidth;int cols = 1;if (w > 1000) cols = 3;else if (w > 600) cols = 2;return GridView.builder(physics: shrinkWrap ? const BouncingScrollPhysics() : const AlwaysScrollableScrollPhysics(),padding: const EdgeInsets.only(top: 0),gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: cols,crossAxisSpacing: 12,mainAxisSpacing: 12,childAspectRatio: 1.05,),itemCount: props.length,itemBuilder: (context, idx) => GestureDetector(onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => PropertyDetailPage(prop: props[idx]))),child: _PropertyCard(prop: props[idx]),),shrinkWrap: shrinkWrap,);});}}class _PropertyCard extends StatelessWidget {final PropertyModel prop;const _PropertyCard({required this.prop, super.key});@overrideWidget build(BuildContext context) {return AnimatedContainer(duration: const Duration(milliseconds: 300),decoration: BoxDecoration(borderRadius: BorderRadius.circular(14),color: Colors.white.withOpacity(0.02),border: Border.all(color: Colors.white.withOpacity(0.03)),boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.25), blurRadius: 8, offset: const Offset(0, 6))],),child: Padding(padding: const EdgeInsets.all(12.0),child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Row(children: [Container(width: 48, height: 48, decoration: BoxDecoration(borderRadius: BorderRadius.circular(8), color: Colors.teal), child: const Icon(Icons.home, color: Colors.white)),const SizedBox(width: 10),Expanded(child: Text(prop.title, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold))),]),const SizedBox(height: 8),Text(prop.location, style: const TextStyle(color: Colors.white70)),const SizedBox(height: 8),Expanded(child: Text(prop.description, style: TextStyle(color: Colors.white.withOpacity(0.85)))),const SizedBox(height: 8),Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [Text(prop.price, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),Row(children: [TextButton(onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => PropertyDetailPage(prop: prop))), child: const Text('Detail')),IconButton(onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => ScheduleVisitPage(prop: prop))), icon: const Icon(Icons.calendar_today, color: Colors.white70)),])])]),),);}}// ------------------- Right column widgets -------------------class _AgentCard extends StatelessWidget {const _AgentCard({super.key});void _onEditProfile(BuildContext context) {Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ProfileEditPage()));}@overrideWidget build(BuildContext context) {return Container(padding: const EdgeInsets.all(12),decoration: BoxDecoration(color: Colors.white.withOpacity(0.02), borderRadius: BorderRadius.circular(14)),child: Row(children: [const CircleAvatar(radius: 28, backgroundColor: Colors.teal, child: Icon(Icons.person, color: Colors.white)),const SizedBox(width: 10),Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: const [Text('RIZQI', style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),SizedBox(height: 6),Text('Agen Properti • Jakarta', style: TextStyle(color: Colors.white70)),])),IconButton(onPressed: () => _onEditProfile(context), icon: const Icon(Icons.edit, color: Colors.white70))]),);}}class _QuickPropertyActions extends StatelessWidget {const _QuickPropertyActions({super.key});@overrideWidget build(BuildContext context) {final actions = [_ActionItem(icon: Icons.add_business, label: 'Tambah Listing', page: const AddListingPage()),_ActionItem(icon: Icons.calendar_month, label: 'Jadwal Kunjungan', page: const SchedulePage()),_ActionItem(icon: Icons.payment, label: 'Pembayaran', page: const PaymentsPage()),_ActionItem(icon: Icons.settings, label: 'Pengaturan', page: const SettingsPage()),];return Container(padding: const EdgeInsets.all(12),decoration: BoxDecoration(color: Colors.white.withOpacity(0.01), borderRadius: BorderRadius.circular(14)),child: Wrap(spacing: 8,runSpacing: 8,children: actions.map((a) => ActionChip(label: Text(a.label),avatar: Icon(a.icon, size: 18),onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => a.page)),backgroundColor: Colors.white.withOpacity(0.03),labelStyle: const TextStyle(color: Colors.white),)).toList(),),);}}class _ActionItem {final IconData icon;final String label;final Widget page;const _ActionItem({required this.icon, required this.label, required this.page});}// ------------------- Recent inquiries -------------------class _RecentInquiriesList extends StatelessWidget {const _RecentInquiriesList({super.key});void _onMore(BuildContext context, int idx) {showModalBottomSheet(context: context,builder: (_) => SafeArea(child: Column(mainAxisSize: MainAxisSize.min, children: [ListTile(title: Text('Detail Inquiry ${idx + 1}'), onTap: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => InquiryDetailPage(index: idx + 1)))),ListTile(title: const Text('Tandai selesai'), onTap: () => Navigator.pop(context)),ListTile(title: const Text('Batal'), onTap: () => Navigator.pop(context)),]),),);}@overrideWidget build(BuildContext context) {final list = List.generate(4, (i) => 'Inquiry ${i + 1}');return ListView.separated(itemCount: list.length,separatorBuilder: (_, __) => const SizedBox(height: 8),itemBuilder: (context, idx) => Container(padding: const EdgeInsets.all(12),decoration: BoxDecoration(color: Colors.white.withOpacity(0.01), borderRadius: BorderRadius.circular(12)),child: ListTile(leading: CircleAvatar(child: Text('${idx + 1}')),title: Text('${list[idx]} - ${idx % 2 == 0 ? 'Rumah' : 'Apartemen'}', style: const TextStyle(color: Colors.white)),subtitle: const Text('Calon pembeli menghubungi untuk jadwal kunjungan', style: TextStyle(color: Colors.white70)),trailing: IconButton(onPressed: () => _onMore(context, idx), icon: const Icon(Icons.more_horiz, color: Colors.white70)),),),shrinkWrap: true,);}}// ------------------- Pages & details -------------------class PropertyDetailPage extends StatelessWidget {final PropertyModel prop;const PropertyDetailPage({required this.prop, super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text(prop.title)),body: Padding(padding: const EdgeInsets.all(16.0),child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Container(height: 180, decoration: BoxDecoration(borderRadius: BorderRadius.circular(12), color: Colors.grey[800]), child: const Center(child: Icon(Icons.photo, size: 56, color: Colors.white70))),const SizedBox(height: 12),Text(prop.location, style: const TextStyle(color: Colors.white70)),const SizedBox(height: 6),Text(prop.price, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 20)),const SizedBox(height: 12),Text(prop.description, style: const TextStyle(color: Colors.white70)),const Spacer(),Row(children: [Expanded(child: ElevatedButton(onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => ScheduleVisitPage(prop: prop))), child: const Text('Jadwalkan Kunjungan'))),const SizedBox(width: 8),OutlinedButton(onPressed: () {/* share placeholder */}, child: const Text('Bagikan'))])]),),);}}class ScheduleVisitPage extends StatelessWidget {final PropertyModel? prop;const ScheduleVisitPage({this.prop, super.key});@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Jadwalkan Kunjungan')),body: Padding(padding: const EdgeInsets.all(16.0),child: Column(children: [Text(prop != null ? 'Menjadwalkan kunjungan ke ${prop!.title}' : 'Pilih listing untuk jadwal kunjungan', style: const TextStyle(color: Colors.white70)),const SizedBox(height: 12),ElevatedButton(onPressed: () { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Kunjungan dijadwalkan (mock).'))); }, child: const Text('Konfirmasi'))]),),);}}class MarketInsightPage extends StatelessWidget {const MarketInsightPage({super.key});@overrideWidget build(BuildContext context) => Scaffold(appBar: AppBar(title: const Text('Market Insight')), body: const Center(child: Padding(padding: EdgeInsets.all(24.0), child: Text('Grafik & metrik pasar properti akan ditampilkan di sini.'))));}class ListingsPage extends StatelessWidget {const ListingsPage({super.key});@overrideWidget build(BuildContext context) => Scaffold(appBar: AppBar(title: const Text('All Listings')),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [const Text('Daftar semua listing (placeholder)'),const SizedBox(height: 20),ElevatedButton(onPressed: () {// Membuat instance PropertyModel terlebih dahulufinal specialProperty = PropertyModel(id: 999,title: 'Listing Spesial',location: 'Bali',price: 'Rp 3.5M',description: 'Villa mewah dengan pemandangan pantai dan kolam renang pribadi');Navigator.of(context).push(MaterialPageRoute(builder: (_) => PropertyDetailPage(prop: specialProperty)));},child: const Text('Lihat Detail Properti Spesial'),),],),),);}class AddListingPage extends StatelessWidget {const AddListingPage({super.key});@overrideWidget build(BuildContext context) => Scaffold(appBar: AppBar(title: const Text('Tambah Listing')), body: const Center(child: Text('Form tambah listing (placeholder)')));}class PaymentsPage extends StatelessWidget {const PaymentsPage({super.key});@overrideWidget build(BuildContext context) => Scaffold(appBar: AppBar(title: const Text('Pembayaran')), body: const Center(child: Text('Riwayat pembayaran')));}class SchedulePage extends StatelessWidget {const SchedulePage({super.key});@overrideWidget build(BuildContext context) => Scaffold(appBar: AppBar(title: const Text('Jadwal')), body: const Center(child: Text('Kalender kunjungan & janji temu')));}class InquiryDetailPage extends StatelessWidget {final int index;const InquiryDetailPage({required this.index, super.key});@overrideWidget build(BuildContext context) => Scaffold(appBar: AppBar(title: Text('Inquiry $index')), body: Padding(padding: const EdgeInsets.all(16.0), child: Text('Detail inquiry $index (mock)')));}// ------------------- ENHANCED Settings & Profile -------------------class SettingsPage extends StatefulWidget {const SettingsPage({super.key});@overrideState<SettingsPage> createState() => _SettingsPageState();}class _SettingsPageState extends State<SettingsPage> {bool _notifications = true;bool _publicProfile = false;String _timezone = 'WIB (UTC+7)';@overrideWidget build(BuildContext context) {return Scaffold(appBar: AppBar(title: const Text('Pengaturan')),body: ListView(padding: const EdgeInsets.all(16), children: [const Text('Akun', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white)),const SizedBox(height: 8),Card(color: Colors.white.withOpacity(0.02),child: ListTile(leading: const Icon(Icons.person),title: const Text('Akun Saya'),subtitle: const Text('Atur informasi akun dan preferensi publik'),trailing: TextButton(onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ProfileEditPage())), child: const Text('Edit')),),),const SizedBox(height: 16),const Text('Notifikasi', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white)),const SizedBox(height: 8),Card(color: Colors.white.withOpacity(0.02),child: Column(children: [SwitchListTile.adaptive(value: _notifications,onChanged: (v) => setState(() => _notifications = v),title: const Text('Notifikasi pesan / inquiry'),subtitle: const Text('Terima notifikasi setiap ada inquiry atau pesan baru'),),SwitchListTile.adaptive(value: _publicProfile,onChanged: (v) => setState(() => _publicProfile = v),title: const Text('Profil Publik'),subtitle: const Text('Tampilkan nama & listing publik di profil agen'),),]),),const SizedBox(height: 16),const Text('Zona Waktu', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white)),const SizedBox(height: 8),Card(color: Colors.white.withOpacity(0.02),child: ListTile(leading: const Icon(Icons.access_time),title: const Text('Zona Waktu'),subtitle: Text(_timezone),trailing: PopupMenuButton<String>(onSelected: (v) => setState(() => _timezone = v),itemBuilder: (_) => [const PopupMenuItem(value: 'WIB (UTC+7)', child: Text('WIB (UTC+7)')),const PopupMenuItem(value: 'WITA (UTC+8)', child: Text('WITA (UTC+8)')),const PopupMenuItem(value: 'WIT (UTC+9)', child: Text('WIT (UTC+9)')),],),),),const SizedBox(height: 24),ElevatedButton.icon(onPressed: () {showDialog(context: context, builder: (_) => AlertDialog(title: const Text('Reset Data?'), content: const Text('Reset data mock akan mengembalikan pengaturan ke default.'), actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('Batal')), TextButton(onPressed: () { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Data mock direset'))); }, child: const Text('Reset'))]));},icon: const Icon(Icons.refresh),label: const Text('Reset Data Mock'),),const SizedBox(height: 12),OutlinedButton.icon(onPressed: () {showDialog(context: context, builder: (_) => AlertDialog(title: const Text('Keluar'), content: const Text('Ini adalah aksi mock. Keluar sekarang?'), actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('Batal')), TextButton(onPressed: () => Navigator.pop(context), child: const Text('OK'))]));},icon: const Icon(Icons.logout),label: const Text('Keluar'),)]),);}}class ProfilePage extends StatelessWidget {const ProfilePage({super.key});@overrideWidget build(BuildContext context) {final recent = List.generate(5, (i) => 'Aktivitas ${i + 1}');return Scaffold(appBar: AppBar(title: const Text('Profile')),body: ListView(padding: const EdgeInsets.all(16), children: [Container(padding: const EdgeInsets.all(14),decoration: BoxDecoration(color: Colors.white.withOpacity(0.02), borderRadius: BorderRadius.circular(12)),child: Row(children: [const CircleAvatar(radius: 36, backgroundColor: Colors.teal, child: Icon(Icons.person, color: Colors.white, size: 36)),const SizedBox(width: 12),Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [const Text('RIZQI', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: Colors.white)),const SizedBox(height: 6),const Text('Agen Properti • Jakarta', style: TextStyle(color: Colors.white70)),const SizedBox(height: 8),Row(children: [ElevatedButton(onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (_) => const ProfileEditPage())), child: const Text('Edit Profil')),const SizedBox(width: 8),OutlinedButton(onPressed: () {/*share*/}, child: const Text('Bagikan')),])]),)]),),const SizedBox(height: 16),Wrap(spacing: 12, children: [_smallStatCard('Total Listing', '24'),_smallStatCard('Kunjungan', '1.2k'),_smallStatCard('Rating', '4.8'),]),const SizedBox(height: 16),const Text('Aktivitas Terbaru', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white)),const SizedBox(height: 8),...recent.map((r) => ListTile(leading: const Icon(Icons.circle, size: 12), title: Text(r, style: const TextStyle(color: Colors.white70)), subtitle: const Text('Ringkasan singkat aktivitas', style: TextStyle(color: Colors.white38)))),]),);}Widget _smallStatCard(String title, String value) {return Container(width: 120,padding: const EdgeInsets.all(12),decoration: BoxDecoration(color: Colors.white.withOpacity(0.01), borderRadius: BorderRadius.circular(10)),child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text(title, style: const TextStyle(color: Colors.white70)), const SizedBox(height: 8), Text(value, style: const TextStyle(fontWeight: FontWeight.bold, color: Colors.white))]),);}}class ProfileEditPage extends StatelessWidget {const ProfileEditPage({super.key});@overrideWidget build(BuildContext context) {final nameCtrl = TextEditingController(text: 'RIZQI');final phoneCtrl = TextEditingController(text: '+62 812-3456-7890');return Scaffold(appBar: AppBar(title: const Text('Edit Profil')),body: Padding(padding: const EdgeInsets.all(16.0),child: Column(children: [TextField(controller: nameCtrl, decoration: const InputDecoration(labelText: 'Nama')),const SizedBox(height: 8),TextField(controller: phoneCtrl, decoration: const InputDecoration(labelText: 'Telepon')),const SizedBox(height: 12),ElevatedButton(onPressed: () { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Profil disimpan'))); }, child: const Text('Simpan'))]),),);}}
🎨 Tampilan UI
Desainnya dibuat sederhana tapi modern:
-
Latar belakang putih bersih biar fokus ke konten.
-
Card dengan rounded corner dan bayangan lembut.
-
Pada layar lebar, konten ditata berdampingan (Row), sementara di layar sempit otomatis jadi bertumpuk (Column).
-
Warna primer cerah untuk header supaya tampil beda.
💻 Cara Kerja Singkat
-
LayoutBuilder cek ukuran layar → kalau
maxWidth > 600, kita tampilkan Row; kalau lebih kecil, kita fallback ke Column. -
Flexible dipakai biar box menyesuaikan lebar—gak ada yang terlalu sempit atau meluber.
-
Padding & Spacing konsisten supaya antar-elemen tetap rapi.
-
Dengan struktur ini, UI terasa natural di HP, tablet, bahkan desktop.
🚀 Kenapa Harus Cobain Layout Responsif Ini?
-
Belajar Konsep Dasar Responsive UI → Ini pondasi sebelum kamu masuk ke desain kompleks.
-
Siap Jadi Template → Tinggal ganti warna atau isi card, langsung cocok buat dashboard, katalog produk, atau portfolio.
-
User Friendly → Gak ada pengalaman buruk karena layout pecah di layar kecil.
-
Best Practice Flutter → Kamu belajar pakai Flexible, Expanded, dan LayoutBuilder—alat wajib seorang Flutter dev.
🎯 Penutup
Itu dia pembahasan layout responsif ala Flutter yang simpel tapi powerful. Cocok banget buat latihan atau jadi basis proyek beneran. Dengan pendekatan ini, aplikasi kamu bakal tampil rapi di mana pun—HP jadul, tablet mid-range, sampai monitor gede.
So… siap bikin UI Flutter yang bukan cuma jalan, tapi juga bisa dibanggain? 💪
Gaskeun, coder squad! 🔥
MADE BY: RIZQI RESDHIANA XI RPL 2
Linknya: https://zm1ko06fjm1kp.zapp.page/#/
Comments
Post a Comment