Flutter 9: Desktop (macOS & Windows) — Window Management, Menu, Shortcuts va Moslashuvchan UI
💙 Flutter — Mobile (iOS, Android) & Desktop (macOS, Windows)
Flutter 9: Desktop (macOS & Windows) — Window Management, Menu, Shortcuts va Moslashuvchan UI
Holat: 2026-yil. Flutter endi faqat mobil ramka emas — u macOS, Windows va Linux uchun to'laqonli 60/120 FPS native ishlovchi desktop platformasidir. Desktop ilovalar mobil ilovalardan tubdan farq qiladi: sichqoncha bilan hover, klaviatura tugmalari (Shortcuts), oyna o'lchamlarini o'zgartirish (Window Resizing), ko'p ustunli interfeys (Master-Detail) va tizim menyulari (Menu Bar). Ushbu darsda Flutter bilan desktop ilovalar yaratishning barcha nozikliklarini amaliy kodlar bilan ko'rib chiqamiz.
Mundarija
- Desktop va Mobile o'rtasidagi asosiy farqlar
- Desktop platformalari arxitekturasi va Runner tuzilishi
- Window Management: window_manager bilan oynani boshqarish
- Tizim menyusi: PlatformMenuBar va Context Menu
- Klaviatura Shortcuts va Actions tizimi
- Sichqoncha, Hover effektlar va Kursorni boshqarish
- Desktop Scrollbar va Wheel aylantirish qoidalari
- Moslashuvchan (Adaptive & Responsive) Dizayn: Breakpointlar
- To'liq Desktop & Mobile moslashuvchan kod namunasi
- Build va Distribution: macOS Sandbox va Windows Installer
- Xulosa va Maslahatlar
- Rasmiy Manbalar
1. Desktop va Mobile o'rtasidagi asosiy farqlar {#farqlar}
Mobil ilovani shunchaki desktop ekraniga cho'zib qo'yish — bu eng katta xato (anti-pattern) hisoblanadi.
┌─────────────────────────────────┐ ┌────────────────────────────────────────────────────────┐
│ MOBILE INTERFACE │ │ DESKTOP INTERFACE │
├─────────────────────────────────┤ ├────────────────────────────────────────────────────────┤
│ • Kichik sensorli ekran (Touch) │ │ • Katta displey, sichqoncha (Mouse/Trackpad) + Klaviat.│
│ • Bitta oyna, to'liq ekran │ │ • Erkin o'lchamli oyna (Resizable, Min/Max/Restore) │
│ • BottomNavigationBar (pastda) │ │ • NavigationRail / Sidebar (chapda) + Split View │
│ • Orqaga surish (Swipe back) │ │ • Klaviatura tugmalari (Cmd/Ctrl+Z, Esc, Enter) │
│ • Yengil dialoglar (ActionSheet)│ │ • Sichqonchaning o'ng tugmasi (Context Menu) + Hover │
│ • Virtual klaviatura │ │ • Native Menu Bar (macOS Apple menyusi, Win titlebar) │
└─────────────────────────────────┘ └────────────────────────────────────────────────────────┘
| Xususiyat | Mobil (iOS / Android) | Desktop (macOS / Windows) |
|---|---|---|
| Kiritish qurilmasi | Barmoq teginishi (Touch, Gestures) | Kursor, g'ildirak (Wheel), klaviatura |
| Hover holati | Mavjud emas | Juda muhim (tugmalar, havolalar, ro'yxatlar) |
| Navigatsiya | BottomNavigationBar yoki Drawer | NavigationRail yoki doimiy Sidebar |
| Oyna o'lchami | Ruxsat etilgan qat'iy ekran | Foydalanuvchi erkin o'zgartiradi |
| Ko'rinish zichligi | Katta teginish maydonlari (min 44-48px) | Yilqiq, zich ma'lumotlar (VisualDensity.compact) |
| Scrollbar | Sukut bo'yicha yashirin, surilganda chiqadi | Har doim ko'rinib turishi maqsadga muvofiq |
2. Desktop platformalari arxitekturasi va Runner tuzilishi {#arxitektura}
Flutter Desktop ilovasi qanday ishga tushadi?
- macOS: Ilova
macos/papkasidagi Cocoa (Objective-C/Swift)NSApplicationkonteyneri ichida ishlaydi. Metal / Impeller grafik dvigateli orqali bevosita GPU ga render qilinadi. - Windows: Ilova
windows/papkasidagi Win32 (C++) ilovasi ichida ishlaydi. DirectX 11/12 (yoki Angle/Skia) orqali render qilinadi.
Loyihada desktopni faollashtirish:
flutter config --enable-macos-desktop
flutter config --enable-windows-desktop
flutter create --platforms=macos,windows,ios,android .
3. Window Management: window_manager bilan oynani boshqarish {#window-manager}
Desktop ilovada oyna boshlang'ich o'lchami, uning markazda ochilishi, sarlavhasi (title) va minimal/maksimal cheklovlarni boshqarish uchun window_manager paketi ishlatiladi:
# pubspec.yaml
dependencies:
flutter:
sdk: flutter
window_manager: ^0.4.3
main.dart da oynani ishga tushirish:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:window_manager/window_manager.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Faqat Desktop platformalarda oynani sozlaymiz
if (Platform.isMacOS || Platform.isWindows || Platform.isLinux) {
await windowManager.ensureInitialized();
const windowOptions = WindowOptions(
size: Size(1100, 750),
minimumSize: Size(650, 500),
center: true,
backgroundColor: Colors.transparent,
skipTaskbar: false,
title: 'DevStudio Desktop',
titleBarStyle: TitleBarStyle.normal, // yoki TitleBarStyle.hidden macOS sarlavhasi uchun
);
await windowManager.waitUntilReadyToShow(windowOptions, () async {
await windowManager.show();
await windowManager.focus();
});
}
runApp(const MultiplatformApp());
}
4. Tizim menyusi: PlatformMenuBar va Context Menu {#menu-bar}
macOS da ekranning eng yuqorisida (Apple logotipi yonida) tizim menyusi mavjud. Flutter buni PlatformMenuBar orqali native macOS menyusiga ulaydi:
class MultiplatformApp extends StatelessWidget {
const MultiplatformApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.indigo,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: PlatformMenuBar(
menus: [
PlatformMenu(
label: 'DevStudio',
menus: [
PlatformMenuItem(
label: 'Dastur haqida...',
onSelected: () => debugPrint('Haqida bosildi'),
),
const PlatformMenuItemGroup(
members: [
PlatformProvidedMenuItem(type: PlatformProvidedMenuItemType.quit),
],
),
],
),
PlatformMenu(
label: 'Fayl',
menus: [
PlatformMenuItem(
label: 'Yangi loyiha',
shortcut: const SingleActivator(LogicalKeyboardKey.keyN, meta: true),
onSelected: () => debugPrint('Yangi loyiha ochildi'),
),
PlatformMenuItem(
label: 'Saqlash',
shortcut: const SingleActivator(LogicalKeyboardKey.keyS, meta: true),
onSelected: () => debugPrint('Saqlandi'),
),
],
),
],
child: const HomeScreen(),
),
);
}
}
Sichqonchaning o'ng tugmasi (Context Menu):
Ixtiyoriy element ustiga o'ng tugma bosilganda kontekst menyu chiqarish:
GestureDetector(
onSecondaryTapUp: (details) {
showMenu(
context: context,
position: RelativeRect.fromLTRB(
details.globalPosition.dx,
details.globalPosition.dy,
details.globalPosition.dx + 1,
details.globalPosition.dy + 1,
),
items: const [
PopupMenuItem(value: 'copy', child: Text('Nusxa olish')),
PopupMenuItem(value: 'edit', child: Text('Tahrirlash')),
PopupMenuDivider(),
PopupMenuItem(value: 'delete', child: Text('O\'chirish', style: TextStyle(color: Colors.red))),
],
);
},
child: const Card(child: ListTile(title: Text('O\'ng tugmani bosing'))),
)
5. Klaviatura Shortcuts va Actions tizimi {#shortcuts}
Desktop foydalanuvchisi sichqonchaga teginmasdan klaviatura yordamida ishlashni afzal ko'radi (masalan: Cmd+K yoki Ctrl+K bilan qidiruv ochish, Esc bilan yopish).
Flutter'da bu Shortcuts + Actions + Intent modeli orqali ishlaydi:
// 1. Intent e'lon qilamiz
class SearchIntent extends Intent {
const SearchIntent();
}
class RefreshIntent extends Intent {
const RefreshIntent();
}
// 2. Widget ichida bog'laymiz
Widget buildKeyboardShortcutsWrapper({required Widget child, required BuildContext context}) {
return Shortcuts(
shortcuts: <ShortcutActivator, Intent>{
// Cmd+K (macOS) yoki Ctrl+K (Windows)
LogicalKeySet(
Platform.isMacOS ? LogicalKeyboardKey.meta : LogicalKeyboardKey.control,
LogicalKeyboardKey.keyK,
): const SearchIntent(),
// Cmd+R yoki Ctrl+R
LogicalKeySet(
Platform.isMacOS ? LogicalKeyboardKey.meta : LogicalKeyboardKey.control,
LogicalKeyboardKey.keyR,
): const RefreshIntent(),
},
child: Actions(
actions: <Type, Action<Intent>>{
SearchIntent: CallbackAction<SearchIntent>(
onInvoke: (intent) => openSearchDialog(context),
),
RefreshIntent: CallbackAction<RefreshIntent>(
onInvoke: (intent) => refreshData(),
),
},
child: Focus(
autofocus: true,
child: child,
),
),
);
}
6. Sichqoncha, Hover effektlar va Kursorni boshqarish {#mouse-hover}
Desktopda element ustiga kursor olib kelinganda foydalanuvchiga fikr-mulohaza (feedback) berish zarur:
- Kursor shakli o'zgarishi (
SystemMouseCursors.click) - Fon rangi yoki soya o'zgarishi (Hover highlight)
class DesktopInteractiveCard extends StatefulWidget {
final String title;
final VoidCallback onTap;
const DesktopInteractiveCard({super.key, required this.title, required this.onTap});
@override
State<DesktopInteractiveCard> createState() => _DesktopInteractiveCardState();
}
class _DesktopInteractiveCardState extends State<DesktopInteractiveCard> {
bool _isHovered = false;
@override
Widget build(BuildContext context) {
return MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) => setState(() => _isHovered = true),
onExit: (_) => setState(() => _isHovered = false),
child: GestureDetector(
onTap: widget.onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
curve: Curves.easeOutCubic,
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: _isHovered ? Colors.indigo.shade50 : Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: _isHovered ? Colors.indigo : Colors.grey.shade300,
width: _isHovered ? 1.5 : 1,
),
boxShadow: _isHovered
? [BoxShadow(color: Colors.indigo.withOpacity(0.12), blurRadius: 16, offset: const Offset(0, 6))]
: [],
),
child: Row(
children: [
Icon(Icons.dashboard_rounded, color: _isHovered ? Colors.indigo : Colors.grey.shade700),
const SizedBox(width: 12),
Text(
widget.title,
style: TextStyle(
fontWeight: _isHovered ? FontWeight.w600 : FontWeight.w500,
color: _isHovered ? Colors.indigo : Colors.black87,
),
),
],
),
),
),
);
}
}
7. Desktop Scrollbar va Wheel aylantirish qoidalari {#scrollbars}
Mobil telefonda scrollbar odatda yashirin bo'ladi. Ammo Windows va macOS da uzun ro'yxatlarda scrollbar doimo ko'rinib turishi yoki hover qilinganda qalinlashishi shart.
Desktop uchun global Scrollbar sozlamasi:
MaterialApp(
theme: ThemeData(
scrollbarTheme: ScrollbarThemeData(
thumbVisibility: WidgetStateProperty.all(true), // Har doim ko'rinsin
thickness: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.hovered)) return 10.0;
return 6.0;
}),
radius: const Radius.circular(8),
thumbColor: WidgetStateProperty.all(Colors.grey.shade400),
),
),
);
Har bir ListView yoki SingleChildScrollView ga ScrollController bog'lash shart, aks holda Flutter ScrollController not attached ogohlantirishini beradi:
final scrollController = ScrollController();
Scrollbar(
controller: scrollController,
thumbVisibility: true,
child: ListView.builder(
controller: scrollController,
itemCount: 100,
itemBuilder: (context, index) => ListTile(title: Text('Element #$index')),
),
);
8. Moslashuvchan (Adaptive & Responsive) Dizayn: Breakpointlar {#adaptive-layout}
Bitta kod bazasidan foydalangan holda ilova mobil telefonda ham, planshetda ham, 4K monitorli Mac va Windows kompyuterda ham mukammal ko'rinishi kerak:
┌────────────────────────────────────────────────────────────────────────┐
│ RESPONSIVE BREAKPOINTS │
├──────────────────┬───────────────────────┬─────────────────────────────┤
│ Ekran kengligi │ Qurilma turi │ Tavsiya etilgan navigatsiya │
├──────────────────┼───────────────────────┼─────────────────────────────┤
│ < 600px │ Smartfon (Mobile) │ BottomNavigationBar │
│ 600px - 1024px │ Planshet / Kichik Win │ NavigationRail (ixcham) │
│ > 1024px │ Desktop / Katta Mac │ Doimiy Sidebar + Split View │
└──────────────────┴───────────────────────┴─────────────────────────────┘
Breakpointlarni aniqlash klassi:
enum ScreenType { mobile, tablet, desktop }
class ResponsiveHelper {
static ScreenType getScreenType(BuildContext context) {
final width = MediaQuery.sizeOf(context).width;
if (width < 600) return ScreenType.mobile;
if (width < 1024) return ScreenType.tablet;
return ScreenType.desktop;
}
static bool isMobile(BuildContext context) => getScreenType(context) == ScreenType.mobile;
static bool isDesktop(BuildContext context) => getScreenType(context) == ScreenType.desktop;
}
9. To'liq Desktop & Mobile moslashuvchan kod namunasi {#full-code}
Quyidagi kod — ham iOS/Android'da (Bottom Bar), ham macOS/Windows'da (Yonbosh Sidebar + Master-Detail ko'rinish) moslashib ishlaydigan mukammal shablondir:
import 'package:flutter/material.dart';
class AdaptiveDashboardScreen extends StatefulWidget {
const AdaptiveDashboardScreen({super.key});
@override
State<AdaptiveDashboardScreen> createState() => _AdaptiveDashboardScreenState();
}
class _AdaptiveDashboardScreenState extends State<AdaptiveDashboardScreen> {
int _selectedIndex = 0;
int? _selectedItemId = 1; // Master-detail uchun tanlangan element
final List<String> _navigationDestinations = [
'Bosh sahifa',
'Buyurtmalar',
'Statistika',
'Sozlamalar',
];
@override
Widget build(BuildContext context) {
final screenWidth = MediaQuery.sizeOf(context).width;
final isDesktop = screenWidth >= 1024;
final isTablet = screenWidth >= 600 && screenWidth < 1024;
// MOBIL KO'RINISH (< 600px)
if (screenWidth < 600) {
return Scaffold(
appBar: AppBar(title: Text(_navigationDestinations[_selectedIndex])),
body: _buildContentArea(isMasterDetail: false),
bottomNavigationBar: NavigationBar(
selectedIndex: _selectedIndex,
onDestinationSelected: (idx) => setState(() => _selectedIndex = idx),
destinations: const [
NavigationDestination(icon: Icon(Icons.home_outlined), selectedIcon: Icon(Icons.home), label: 'Asosiy'),
NavigationDestination(icon: Icon(Icons.shopping_bag_outlined), selectedIcon: Icon(Icons.shopping_bag), label: 'Buyurtma'),
NavigationDestination(icon: Icon(Icons.bar_chart_outlined), selectedIcon: Icon(Icons.bar_chart), label: 'Tahlil'),
NavigationDestination(icon: Icon(Icons.settings_outlined), selectedIcon: Icon(Icons.settings), label: 'Sozlama'),
],
),
);
}
// DESKTOP VA TABLET KO'RINISH (>= 600px)
return Scaffold(
body: Row(
children: [
// YONBOSH MENYU (NavigationRail yoki Sidebar)
NavigationRail(
extended: isDesktop, // Desktopda yozuvlari bilan kengayadi
minExtendedWidth: 200,
selectedIndex: _selectedIndex,
onDestinationSelected: (idx) => setState(() => _selectedIndex = idx),
leading: Padding(
padding: const EdgeInsets.symmetric(vertical: 20),
child: Row(
children: [
const Icon(Icons.bolt, color: Colors.indigo, size: 28),
if (isDesktop) ...[
const SizedBox(width: 8),
const Text('DEVSTUDIO', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
],
],
),
),
destinations: const [
NavigationRailDestination(icon: Icon(Icons.home_outlined), selectedIcon: Icon(Icons.home), label: Text('Asosiy')),
NavigationRailDestination(icon: Icon(Icons.shopping_bag_outlined), selectedIcon: Icon(Icons.shopping_bag), label: Text('Buyurtmalar')),
NavigationRailDestination(icon: Icon(Icons.bar_chart_outlined), selectedIcon: Icon(Icons.bar_chart), label: Text('Statistika')),
NavigationRailDestination(icon: Icon(Icons.settings_outlined), selectedIcon: Icon(Icons.settings), label: Text('Sozlamalar')),
],
),
const VerticalDivider(width: 1, thickness: 1),
// ASOSIY TARKIB (MASTER-DETAIL VIEW DESKTOPDA)
Expanded(
child: _buildContentArea(isMasterDetail: isDesktop),
),
],
),
);
}
Widget _buildContentArea({required bool isMasterDetail}) {
if (!isMasterDetail) {
// Yagona ustunli ro'yxat (Mobil va planshet)
return ListView.separated(
padding: const EdgeInsets.all(16),
itemCount: 20,
separatorBuilder: (_, __) => const SizedBox(height: 8),
itemBuilder: (context, i) => ListTile(
tileColor: Colors.grey.shade100,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
title: Text('Loyiha #$i'),
subtitle: const Text('Status: Faol ishlov jarayonida'),
trailing: const Icon(Icons.chevron_right),
onTap: () {
// Mobilda alohida sahifaga o'tiladi
},
),
);
}
// Desktopda ikkita ustun: Chapda Ro'yxat (Master), O'ngda Tafsilot (Detail)
return Row(
children: [
// Master ustun (350px kenglik)
SizedBox(
width: 360,
child: ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: 20,
itemBuilder: (context, i) {
final isSelected = _selectedItemId == i;
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: ListTile(
selected: isSelected,
selectedTileColor: Colors.indigo.shade50,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
title: Text('Mijoz loyihasi #$i', style: TextStyle(fontWeight: isSelected ? FontWeight.bold : FontWeight.normal)),
subtitle: Text('Jami mablag\': \$${(i + 1) * 250}'),
onTap: () => setState(() => _selectedItemId = i),
),
);
},
),
),
const VerticalDivider(width: 1, thickness: 1),
// Detail ustun (Qolgan barcha bo'sh joy)
Expanded(
child: Container(
color: Colors.white,
padding: const EdgeInsets.all(32),
child: _selectedItemId == null
? const Center(child: Text('Tafsilotlarni ko\'rish uchun chapdan elementni tanlang'))
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Loyiha #$_selectedItemId tafsilotlari', style: Theme.of(context).textTheme.headlineMedium),
const SizedBox(height: 16),
const Text('Ushbu qismda loyihaga oid barcha API ma\'lumotlari, jadvallar va boshqaruv tugmalari joylashadi.'),
const SizedBox(height: 24),
Wrap(
spacing: 12,
children: [
ElevatedButton.icon(onPressed: () {}, icon: const Icon(Icons.check), label: const Text('Tasdiqlash')),
OutlinedButton.icon(onPressed: () {}, icon: const Icon(Icons.download), label: const Text('Eksport PDF')),
],
),
],
),
),
),
],
);
}
}
10. Build va Distribution: macOS Sandbox va Windows Installer {#packaging}
Desktop ilovani tayyorlab foydalanuvchiga taqdim etishda ikki muhim qoida mavjud:
10.1 macOS App Sandbox & Tarmoq ruxsati
macOS da ilovangiz internetdan ma'lumot olishi (REST API bilan ishlashi) uchun macos/Runner/DebugProfile.entitlements va Release.entitlements fayllariga tarmoq ruxsatini qo'shish shart:
<key>com.apple.security.network.client</key>
<true/>
*Agar bu qo'shilmasa, ilova simulyatorda ishlashi mumkin, ammo release qilinganda barcha API so'rovlari SocketException bilan qulab tushadi!*
10.2 Windows EXE va Installer
Windows uchun yig'ish:
flutter build windows --release
Natija build/windows/x64/runner/Release/ papkasida hosil bo'ladi. Uni foydalanuvchilarga tarqatish uchun Inno Setup yoki MSIX Packaging Tool vositasidan foydalaniladi:
dart run msix:create
11. Xulosa va Maslahatlar {#xulosa}
- Universal layout qoidasi: Har doim kenglikni
MediaQuery.sizeOf(context).widthorqali tekshiring va 600px hamda 1024px nuqtalarida interfeysni avtomatik qayta quring. - Kursorlarni unutmang: Har qanday bosiladigan card yoki tugmaga
MouseRegion(cursor: SystemMouseCursors.click)yokiInkWellbering. - Klaviatura bilan do'st bo'ling: Qidiruv, saqlash, yopish kabi eng ko'p bajariladigan amallarga
Shortcutsbog'lang.