1. परिचय
Flutter, Google का यूज़र इंटरफ़ेस (यूआई) टूलकिट है. इसकी मदद से, एक ही कोड बेस से मोबाइल, वेब, और डेस्कटॉप पर सुंदर और मूल रूप से कंपाइल किए गए ऐप्लिकेशन बनाए जा सकते हैं. Flutter मौजूदा कोड के साथ काम करता है. दुनिया भर के डेवलपर और संगठन इसका इस्तेमाल करते हैं. साथ ही, यह मुफ़्त और ओपन सोर्स है.
इस कोडलैब में, Flutter संगीत ऐप्लिकेशन को बेहतर बनाया गया है. यह ऐप्लिकेशन उबाऊ से खूबसूरत है. ऐसा करने के लिए, यह कोडलैब Material 3 में पेश किए गए टूल और एपीआई का इस्तेमाल करता है.
आप इन चीज़ों के बारे में जानेंगे
- ऐसा Flutter ऐप्लिकेशन कैसे लिखें जिसे सभी प्लैटफ़ॉर्म पर इस्तेमाल किया जा सके और जो इस्तेमाल में आसान हो.
- अपने ऐप्लिकेशन में टेक्स्ट कैसे डिज़ाइन करें, ताकि यह पक्का किया जा सके कि इससे उपयोगकर्ता अनुभव बेहतर हो रहा है.
- अपनी ज़रूरत के हिसाब से सही रंग चुनने, विजेट को पसंद के मुताबिक बनाने, और अपनी थीम बनाने के साथ-साथ, गहरे रंग वाला मोड तुरंत और आसानी से लागू करने का तरीका जानें.
- क्रॉस-प्लैटफ़ॉर्म की सुविधा वाले अडैप्टिव ऐप्लिकेशन बनाने का तरीका.
- किसी भी स्क्रीन पर अच्छे दिखने वाले ऐप्लिकेशन बनाने का तरीका.
- अपने Flutter ऐप्लिकेशन को शानदार बनाने के लिए उसमें मूवमेंट जोड़ें.
ज़रूरी शर्तें:
यह कोडलैब यह मानता है कि आपको Flutter का इस्तेमाल करने का कुछ अनुभव है. अगर ऐसा नहीं है, तो आपको सबसे पहले बुनियादी बातें पता करनी होंगी. यहां दिए गए लिंक से हमें मदद मिलेगी:
- Flutter विजेट के फ़्रेमवर्क को एक्सप्लोर करें
- लिखें अपना पहला Flutter ऐप्लिकेशन, पार्ट 1 कोडलैब आज़माएं
आपको क्या बनाना होगा
इस कोडलैब से आपको MyArtist ऐप्लिकेशन की होम स्क्रीन बनाने में मदद मिलती है. यह एक म्यूज़िक प्लेयर ऐप्लिकेशन है, जहां प्रशंसक अपने पसंदीदा कलाकारों के साथ अप-टू-डेट रह सकते हैं. इसमें यह बताया गया है कि अलग-अलग प्लैटफ़ॉर्म पर खूबसूरत दिखने के लिए, अपने ऐप्लिकेशन के डिज़ाइन में किस तरह बदलाव किया जा सकता है.
नीचे दिए गए वीडियो में दिखाया गया है कि कोडलैब के इस मॉड्यूल के पूरा होने के बाद, ऐप्लिकेशन कैसे काम करता है:
आपको इस कोडलैब से क्या सीखना है?
2. Flutter डेवलपमेंट एनवायरमेंट को सेट अप करें
इस लैब को पूरा करने के लिए, आपको सॉफ़्टवेयर के दो हिस्सों की ज़रूरत होगी—Flutter SDK टूल और एडिटर.
इनमें से किसी भी डिवाइस का इस्तेमाल करके, कोडलैब चलाया जा सकता है:
- आपके कंप्यूटर से कनेक्ट किया गया Android या iOS डिवाइस होना चाहिए और वह डेवलपर मोड पर सेट होना चाहिए.
- iOS सिम्युलेटर (Xcode टूल इंस्टॉल करना ज़रूरी है).
- Android Emulator (Android Studio में सेटअप करना ज़रूरी है).
- ब्राउज़र (डीबग करने के लिए Chrome ज़रूरी है).
- Windows, Linux या macOS डेस्कटॉप ऐप्लिकेशन के तौर पर. आपको उस प्लैटफ़ॉर्म पर गेम बनाना होगा जहां आपको इसे डिप्लॉय करना है. इसलिए, अगर आपको Windows डेस्कटॉप ऐप्लिकेशन डेवलप करना है, तो आपको सही बिल्ड चेन ऐक्सेस करने के लिए Windows पर डेवलप करना होगा. ऑपरेटिंग सिस्टम से जुड़ी कुछ खास शर्तें हैं, जिनके बारे में docs.flutter.dev/desktop पर जानकारी दी गई है.
3. कोडलैब स्टार्टर ऐप्लिकेशन डाउनलोड करें
GitHub से इसे क्लोन करें
GitHub से इस कोडलैब का क्लोन बनाने के लिए, ये निर्देश चलाएं:
git clone https://github.com/flutter/codelabs.git cd codelabs/boring_to_beautiful/step_01/
यह पक्का करने के लिए कि सब कुछ काम कर रहा है, Flutter ऐप्लिकेशन को डेस्कटॉप ऐप्लिकेशन के तौर पर चलाएं, जैसा कि नीचे दिखाया गया है. इसके अलावा, इस प्रोजेक्ट को अपने IDE में खोलें और ऐप्लिकेशन को चलाने के लिए, इसके टूल का इस्तेमाल करें.
हो गया! MyArtist की होम स्क्रीन के लिए स्टार्टर कोड काम करना चाहिए. आपको MyArtist की होम स्क्रीन दिखेगी. यह डेस्कटॉप पर ठीक लगता है, लेकिन मोबाइल के लिए... बहुत बढ़िया नहीं. एक बात, तो यह इस नॉक का सम्मान नहीं करता. चिंता न करें, आप इसे ठीक कर देंगे!
कोड के बारे में पूरी जानकारी देखना
इसके बाद, कोड के बारे में जानें.
lib/src/features/home/view/home_screen.dart
खोलें, जिसमें यह जानकारी होती है:
lib/src/features/home/view/home_screen.dart
import 'package:adaptive_components/adaptive_components.dart';
import 'package:flutter/material.dart';
import '../../../shared/classes/classes.dart';
import '../../../shared/extensions.dart';
import '../../../shared/providers/providers.dart';
import '../../../shared/views/views.dart';
import '../../playlists/view/playlist_songs.dart';
import 'view.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
final PlaylistsProvider playlistProvider = PlaylistsProvider();
final List<Playlist> playlists = playlistProvider.playlists;
final Playlist topSongs = playlistProvider.topSongs;
final Playlist newReleases = playlistProvider.newReleases;
final ArtistsProvider artistsProvider = ArtistsProvider();
final List<Artist> artists = artistsProvider.artists;
return LayoutBuilder(
builder: (context, constraints) {
// Add conditional mobile layout
return Scaffold(
body: SingleChildScrollView(
child: AdaptiveColumn(
children: [
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.all(2), // Modify this line
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'Good morning',
style: context.displaySmall,
),
),
const SizedBox(width: 20),
const BrightnessToggle(),
],
),
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
children: [
const HomeHighlight(),
LayoutBuilder(
builder: (context, constraints) => HomeArtists(
artists: artists,
constraints: constraints,
),
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(2), // Modify this line
child: Text(
'Recently played',
style: context.headlineSmall,
),
),
HomeRecent(
playlists: playlists,
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.all(2), // Modify this line
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.all(2), // Modify this line
child: Text(
'Top Songs Today',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: topSongs,
constraints: constraints,
),
),
],
),
),
// Add spacer between tables
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.all(2), // Modify this line
child: Text(
'New Releases',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: newReleases,
constraints: constraints,
),
),
],
),
),
],
),
),
),
],
),
),
);
},
);
}
}
यह फ़ाइल material.dart
को इंपोर्ट करती है और दो क्लास का इस्तेमाल करके एक स्टेटफ़ुल विजेट लागू करती है:
import
स्टेटमेंट, मटीरियल कॉम्पोनेंट उपलब्ध कराता है.HomeScreen
क्लास, दिखने वाला पूरा पेज दिखाती है._HomeScreenState
क्लास काbuild()
तरीका, विजेट ट्री का रूट बनाता है. इससे यूज़र इंटरफ़ेस (यूआई) में सभी विजेट बनाने के तरीके पर असर पड़ता है.
4. टाइपोग्राफ़ी की सुविधा का फ़ायदा लें
टेक्स्ट हर जगह मौजूद है. उपयोगकर्ता के साथ बातचीत करने के लिए टेक्स्ट एक उपयोगी तरीका है. क्या आपका ऐप्लिकेशन दोस्ताना और मज़ेदार या शायद भरोसेमंद और प्रोफ़ेशनल है? आपका पसंदीदा बैंकिंग ऐप्लिकेशन, Comic Sans का इस्तेमाल क्यों नहीं करता है. टेक्स्ट दिखाने का तरीका, उपयोगकर्ता पर आपके ऐप्लिकेशन पर पहली बार आने वाले इंप्रेशन को आकार देता है. यहां टेक्स्ट को और सोच-समझकर इस्तेमाल करने के कुछ तरीके बताए गए हैं.
बताएं नहीं, दिखाएं
जब भी मुमकिन हो, "दिखाएं" की जगह "बताना" होगा. उदाहरण के लिए, स्टार्टर ऐप्लिकेशन के NavigationRail
में हर मुख्य रास्ते के लिए टैब होते हैं, लेकिन सबसे बेहतर आइकॉन एक जैसे हैं:
यह उपयोगी नहीं है, क्योंकि उपयोगकर्ता को अब भी हर टैब का टेक्स्ट पढ़ना होता है. शुरुआत करने के लिए विज़ुअल संकेत जोड़ें, ताकि उपयोगकर्ता तेज़ी से मुख्य आइकॉन पर नज़र डालकर अपनी पसंद का टैब ढूंढ सके. इससे स्थानीय भाषा के मुताबिक अनुवाद और सुलभता सेवाओं को बेहतर बनाने में भी मदद मिलती है.
lib/src/shared/router.dart
में, हर नेविगेशन डेस्टिनेशन (घर, प्लेलिस्ट, और लोग) के लिए, अलग-अलग आगे के आइकॉन जोड़ें:
lib/src/shared/router.dart
const List<NavigationDestination> destinations = [
NavigationDestination(
label: 'Home',
icon: Icon(Icons.home), // Modify this line
route: '/',
),
NavigationDestination(
label: 'Playlists',
icon: Icon(Icons.playlist_add_check), // Modify this line
route: '/playlists',
),
NavigationDestination(
label: 'Artists',
icon: Icon(Icons.people), // Modify this line
route: '/artists',
),
];
क्या कोई समस्या आ रही है?
अगर आपका ऐप्लिकेशन ठीक से नहीं चल रहा है, तो देखें कि टाइपिंग में कोई गड़बड़ी है या नहीं. अगर ज़रूरी हो, तो ट्रैक पर वापस आने के लिए, नीचे दिए गए लिंक पर कोड का इस्तेमाल करें.
सोच-समझकर फ़ॉन्ट चुनें
फ़ॉन्ट आपके ऐप्लिकेशन की खासियत तय करते हैं, इसलिए सही फ़ॉन्ट चुनना बहुत ज़रूरी है. फ़ॉन्ट चुनते समय, इन बातों का ध्यान रखें:
- Sans-Serif या Serif: Serif फ़ॉन्ट में सजावटी स्ट्रोक या "टेल" होते हैं इन्हें अक्षरों के अंत में लिखा जाता है. इन्हें ज़्यादा औपचारिक माना जाता है. सान्स-सेरिफ़ फ़ॉन्ट में सजावटी स्ट्रोक नहीं होते हैं और इन्हें ज़्यादा अनौपचारिक माना जाता है. A San Serif Capital T और सेरिफ़ कैपिटल T
- सभी कैप फ़ॉन्ट: छोटे टेक्स्ट पर ध्यान देने के लिए, सिर्फ़ बड़े अक्षरों का इस्तेमाल करना सही है. इसे हेडलाइन माना जाता है. हालांकि, ज़्यादा इस्तेमाल किए जाने पर इसे चिल्लाने की वजह से समझा जा सकता है, ताकि उपयोगकर्ता इसे पूरी तरह अनदेखा कर सके.
- टाइटल केस या वाक्य का केस: टाइटल या लेबल जोड़ते समय, ध्यान रखें कि कैपिटल लेटर का इस्तेमाल कैसे किया जाता है: टाइटल केस, जहां हर शब्द का पहला अक्षर कैपिटल लेटर में होता है ("This is a Titlecase Title"), फ़ॉर्मल टोन में लिखा जाता है. वाक्य का केस, जिसमें सिर्फ़ व्यक्तिवाचक संज्ञाओं को कैपिटल में रखा जाता है और टेक्स्ट में पहला शब्द ("यह वाक्य के केस का टाइटल है"), बातचीत वाला ज़्यादा अनौपचारिक है.
- केर्निंग (हर अक्षर के बीच की दूरी), लाइन की लंबाई (स्क्रीन पर मौजूद पूरे टेक्स्ट की चौड़ाई), और लाइन की ऊंचाई (टेक्स्ट की हर लाइन कितनी लंबी है): इनमें से किसी भी लाइन में बहुत ज़्यादा या बहुत कम होने की वजह से, आपके ऐप्लिकेशन को पढ़ा नहीं जा सकता. उदाहरण के लिए, टेक्स्ट के किसी बड़े और बिना रुकावट वाले ब्लॉक को पढ़ते समय, हो सकता है कि आप अपनी जगह की जानकारी खो दें.
इसे ध्यान में रखते हुए, Google Fonts में जाएं और Montserrat जैसा कोई Sans-S फ़ॉन्ट चुनें. ऐसा इसलिए, क्योंकि इस ऐप्लिकेशन का मकसद मज़ेदार और दिलचस्प है.
कमांड लाइन से, google_fonts
पैकेज लें. इससे फ़ॉन्ट को ऐप्लिकेशन डिपेंडेंसी के तौर पर जोड़ने के लिए, pubspec फ़ाइल भी अपडेट हो जाती है.
$ flutter pub add google_fonts
macos/Runner/DebugProfile.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<!-- Make sure these lines are present from here... -->
<key>com.apple.security.network.client</key>
<true/>
<!-- To here. -->
<key>com.apple.security.network.server</key>
<true/>
</dict>
</plist>
lib/src/shared/extensions.dart
में, नया पैकेज इंपोर्ट करें:
lib/src/shared/extensions.dart
import 'package:google_fonts/google_fonts.dart'; // Add this line.
मोंसेर्राट सेट करें TextTheme:
TextTheme get textTheme => GoogleFonts.montserratTextTheme(theme.textTheme); // Modify this line
बदलावों को चालू करने के लिए, को फिर से लोड करें. (अपने IDE में मौजूद बटन का इस्तेमाल करें या कमांड लाइन से, हॉट रीलोड के लिए r
डालें.):
आपको Montserrat फ़ॉन्ट में टेक्स्ट के साथ, NavigationRail
के नए आइकॉन दिखेंगे.
क्या कोई समस्या आ रही है?
अगर आपका ऐप्लिकेशन ठीक से नहीं चल रहा है, तो देखें कि टाइपिंग में कोई गड़बड़ी है या नहीं. अगर ज़रूरी हो, तो ट्रैक पर वापस आने के लिए, नीचे दिए गए लिंक पर कोड का इस्तेमाल करें.
5. थीम सेट करना
थीम से, ऐप्लिकेशन में स्ट्रक्चर्ड डिज़ाइन और एक जैसा समानता लाने में मदद मिलती है. इसके लिए, थीम के तहत अलग-अलग कलर और टेक्स्ट स्टाइल का एक सेट तय किया जाता है. थीम की मदद से, यूज़र इंटरफ़ेस (यूआई) को तुरंत लागू किया जा सकता है. इसके लिए, आपको हर विजेट के लिए सटीक रंग जैसी छोटी-मोटी जानकारी देने की ज़रूरत नहीं पड़ती.
Flutter डेवलपर आम तौर पर दो में से किसी एक तरीके से कस्टम-थीम वाले कॉम्पोनेंट बनाते हैं:
- अपनी खुद की थीम के साथ अलग-अलग कस्टम विजेट बनाएं.
- डिफ़ॉल्ट विजेट के लिए, स्कोप वाली थीम बनाएं.
इस उदाहरण में, पूरे ऐप्लिकेशन में एक जैसी थीम वाले विजेट और रंग बनाने के लिए, lib/src/shared/providers/theme.dart
में मौजूद थीम प्रोवाइडर का इस्तेमाल किया गया है:
lib/src/shared/providers/theme.dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:material_color_utilities/material_color_utilities.dart';
class NoAnimationPageTransitionsBuilder extends PageTransitionsBuilder {
const NoAnimationPageTransitionsBuilder();
@override
Widget buildTransitions<T>(
PageRoute<T> route,
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return child;
}
}
class ThemeSettingChange extends Notification {
ThemeSettingChange({required this.settings});
final ThemeSettings settings;
}
class ThemeProvider extends InheritedWidget {
const ThemeProvider(
{super.key,
required this.settings,
required this.lightDynamic,
required this.darkDynamic,
required super.child});
final ValueNotifier<ThemeSettings> settings;
final ColorScheme? lightDynamic;
final ColorScheme? darkDynamic;
final pageTransitionsTheme = const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.linux: NoAnimationPageTransitionsBuilder(),
TargetPlatform.macOS: NoAnimationPageTransitionsBuilder(),
TargetPlatform.windows: NoAnimationPageTransitionsBuilder(),
},
);
Color custom(CustomColor custom) {
if (custom.blend) {
return blend(custom.color);
} else {
return custom.color;
}
}
Color blend(Color targetColor) {
return Color(
Blend.harmonize(targetColor.value, settings.value.sourceColor.value));
}
Color source(Color? target) {
Color source = settings.value.sourceColor;
if (target != null) {
source = blend(target);
}
return source;
}
ColorScheme colors(Brightness brightness, Color? targetColor) {
final dynamicPrimary = brightness == Brightness.light
? lightDynamic?.primary
: darkDynamic?.primary;
return ColorScheme.fromSeed(
seedColor: dynamicPrimary ?? source(targetColor),
brightness: brightness,
);
}
ShapeBorder get shapeMedium => RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
);
CardTheme cardTheme() {
return CardTheme(
elevation: 0,
shape: shapeMedium,
clipBehavior: Clip.antiAlias,
);
}
ListTileThemeData listTileTheme(ColorScheme colors) {
return ListTileThemeData(
shape: shapeMedium,
selectedColor: colors.secondary,
);
}
AppBarTheme appBarTheme(ColorScheme colors) {
return AppBarTheme(
elevation: 0,
backgroundColor: colors.surface,
foregroundColor: colors.onSurface,
);
}
TabBarTheme tabBarTheme(ColorScheme colors) {
return TabBarTheme(
labelColor: colors.secondary,
unselectedLabelColor: colors.onSurfaceVariant,
indicator: BoxDecoration(
border: Border(
bottom: BorderSide(
color: colors.secondary,
width: 2,
),
),
),
);
}
BottomAppBarTheme bottomAppBarTheme(ColorScheme colors) {
return BottomAppBarTheme(
color: colors.surface,
elevation: 0,
);
}
BottomNavigationBarThemeData bottomNavigationBarTheme(ColorScheme colors) {
return BottomNavigationBarThemeData(
type: BottomNavigationBarType.fixed,
backgroundColor: colors.surfaceContainerHighest,
selectedItemColor: colors.onSurface,
unselectedItemColor: colors.onSurfaceVariant,
elevation: 0,
landscapeLayout: BottomNavigationBarLandscapeLayout.centered,
);
}
NavigationRailThemeData navigationRailTheme(ColorScheme colors) {
return const NavigationRailThemeData();
}
DrawerThemeData drawerTheme(ColorScheme colors) {
return DrawerThemeData(
backgroundColor: colors.surface,
);
}
ThemeData light([Color? targetColor]) {
final _colors = colors(Brightness.light, targetColor);
return ThemeData.light().copyWith(
pageTransitionsTheme: pageTransitionsTheme,
colorScheme: _colors,
appBarTheme: appBarTheme(_colors),
cardTheme: cardTheme(),
listTileTheme: listTileTheme(_colors),
bottomAppBarTheme: bottomAppBarTheme(_colors),
bottomNavigationBarTheme: bottomNavigationBarTheme(_colors),
navigationRailTheme: navigationRailTheme(_colors),
tabBarTheme: tabBarTheme(_colors),
drawerTheme: drawerTheme(_colors),
scaffoldBackgroundColor: _colors.background,
useMaterial3: true,
);
}
ThemeData dark([Color? targetColor]) {
final _colors = colors(Brightness.dark, targetColor);
return ThemeData.dark().copyWith(
pageTransitionsTheme: pageTransitionsTheme,
colorScheme: _colors,
appBarTheme: appBarTheme(_colors),
cardTheme: cardTheme(),
listTileTheme: listTileTheme(_colors),
bottomAppBarTheme: bottomAppBarTheme(_colors),
bottomNavigationBarTheme: bottomNavigationBarTheme(_colors),
navigationRailTheme: navigationRailTheme(_colors),
tabBarTheme: tabBarTheme(_colors),
drawerTheme: drawerTheme(_colors),
scaffoldBackgroundColor: _colors.background,
useMaterial3: true,
);
}
ThemeMode themeMode() {
return settings.value.themeMode;
}
ThemeData theme(BuildContext context, [Color? targetColor]) {
final brightness = MediaQuery.of(context).platformBrightness;
return brightness == Brightness.light
? light(targetColor)
: dark(targetColor);
}
static ThemeProvider of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<ThemeProvider>()!;
}
@override
bool updateShouldNotify(covariant ThemeProvider oldWidget) {
return oldWidget.settings != settings;
}
}
class ThemeSettings {
ThemeSettings({
required this.sourceColor,
required this.themeMode,
});
final Color sourceColor;
final ThemeMode themeMode;
}
Color randomColor() {
return Color(Random().nextInt(0xFFFFFFFF));
}
// Custom Colors
const linkColor = CustomColor(
name: 'Link Color',
color: Color(0xFF00B0FF),
);
class CustomColor {
const CustomColor({
required this.name,
required this.color,
this.blend = true,
});
final String name;
final Color color;
final bool blend;
Color value(ThemeProvider provider) {
return provider.custom(this);
}
}
प्रोवाइडर का इस्तेमाल करने के लिए, एक इंस्टेंस बनाएं और उसे MaterialApp
में, स्कोप वाले थीम ऑब्जेक्ट में पास करें. यह ऑब्जेक्ट, lib/src/shared/app.dart
में मौजूद होता है. इसे किसी भी नेस्ट किए गए Theme
ऑब्जेक्ट से इनहेरिट किया जाएगा:
lib/src/shared/app.dart
import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'playback/bloc/bloc.dart';
import 'providers/theme.dart';
import 'router.dart';
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final settings = ValueNotifier(ThemeSettings(
sourceColor: Colors.pink,
themeMode: ThemeMode.system,
));
@override
Widget build(BuildContext context) {
return BlocProvider<PlaybackBloc>(
create: (context) => PlaybackBloc(),
child: DynamicColorBuilder(
builder: (lightDynamic, darkDynamic) => ThemeProvider(
lightDynamic: lightDynamic,
darkDynamic: darkDynamic,
settings: settings,
child: NotificationListener<ThemeSettingChange>(
onNotification: (notification) {
settings.value = notification.settings;
return true;
},
child: ValueListenableBuilder<ThemeSettings>(
valueListenable: settings,
builder: (context, value, _) {
final theme = ThemeProvider.of(context); // Add this line
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: theme.light(settings.value.sourceColor), // Add this line
routeInformationParser: appRouter.routeInformationParser,
routerDelegate: appRouter.routerDelegate,
);
},
),
)),
),
);
}
}
थीम सेट अप हो जाने के बाद, अब ऐप्लिकेशन के लिए रंग चुनें.
रंगों का सही सेट चुनना हमेशा आसान नहीं होता. हो सकता है कि आपको मुख्य रंग का आइडिया हो, लेकिन हो सकता है कि आप अपने ऐप्लिकेशन में एक से ज़्यादा रंग का इस्तेमाल करना चाहें. टेक्स्ट किस रंग का होना चाहिए? शीर्षक? कॉन्टेंट? लिंक? बैकग्राउंड का रंग कैसा होगा? मटीरियल थीम बिल्डर, वेब पर आधारित एक टूल है. इसे Material 3 में लॉन्च किया गया है. इससे आपको अपने ऐप्लिकेशन के लिए, मिलते-जुलते रंगों का सेट चुनने में मदद मिलती है.
ऐप्लिकेशन के लिए सोर्स का रंग चुनने के लिए, मटीरियल थीम बिल्डर खोलें और यूज़र इंटरफ़ेस (यूआई) के लिए अलग-अलग रंग एक्सप्लोर करें. ब्रैंड की खूबसूरती और/या आपकी पसंद के मुताबिक रंग चुनना ज़रूरी है.
थीम बनाने के बाद, प्राइमरी कलर बबल पर राइट-क्लिक करें. इससे एक डायलॉग बॉक्स खुलता है, जिसमें मुख्य रंग की हेक्स वैल्यू होती है. इस वैल्यू को कॉपी करें. (इस डायलॉग बॉक्स का इस्तेमाल करके भी रंग सेट किया जा सकता है.)
मुख्य रंग की हेक्स वैल्यू, थीम देने वाले को पास करें. उदाहरण के लिए, #00cbe6
रंग के हेक्स कोड को Color(0xff00cbe6)
के तौर पर दिखाया जाता है. ThemeProvider
, एक ThemeData
जनरेट करता है, जिसमें मिलते-जुलते रंगों का वह सेट शामिल होता है जिसकी झलक आपने मटीरियल थीम बिल्डर में देखी थी:
final settings = ValueNotifier(ThemeSettings(
sourceColor: Color(0xff00cbe6), // Replace this color
themeMode: ThemeMode.system,
));
ऐप्लिकेशन को रीस्टार्ट करें. मुख्य रंग के मौजूद होने पर, ऐप्लिकेशन ज़्यादा बेहतर महसूस करने लगता है. कॉन्टेक्स्ट में थीम का रेफ़रंस देकर और ColorScheme
की मदद से, सभी नए रंगों को ऐक्सेस करें:
final colors = Theme.of(context).colorScheme;
किसी खास रंग का इस्तेमाल करने के लिए, colorScheme
में रंग से जुड़ी भूमिका को ऐक्सेस करें. lib/src/shared/views/outlined_card.dart
पर जाएं और OutlinedCard
को बॉर्डर दें:
lib/src/shared/views/outlined_card.dart
class _OutlinedCardState extends State<OutlinedCard> {
@override
Widget build(BuildContext context) {
return MouseRegion(
cursor: widget.clickable
? SystemMouseCursors.click
: SystemMouseCursors.basic,
child: Container(
child: widget.child,
// Add from here...
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.outline,
width: 1,
),
),
// ... To here.
),
);
}
}
मटीरियल 3 में बारीक कलर भूमिकाएं उपलब्ध कराई जाती हैं, जो एक-दूसरे के साथ बेहतर तरीके से काम करती हैं. इसका इस्तेमाल पूरे यूज़र इंटरफ़ेस (यूआई) में किया जा सकता है, ताकि एक्सप्रेशन की नई लेयर जोड़ी जा सकें. कलर से जुड़ी इन नई भूमिकाओं में ये शामिल हैं:
Primary
,OnPrimary
,PrimaryContainer
,OnPrimaryContainer
Secondary
,OnSecondary
,SecondaryContainer
,OnSecondaryContainer
Tertiary
,OnTertiary
,TertiaryContainer
,OnTertiaryContainer
Error
,OnError
,ErrorContainer
,OnErrorContainer
Background
,OnBackground
Surface
,OnSurface
,SurfaceVariant
,OnSurfaceVariant
Shadow
,Outline
,InversePrimary
इसके अलावा, हल्के और गहरे रंग वाली, दोनों थीम के लिए नए डिज़ाइन टोकन इस्तेमाल किए जा सकते हैं:
रंग वाली इन भूमिकाओं का इस्तेमाल, यूज़र इंटरफ़ेस (यूआई) के अलग-अलग हिस्सों को खास तौर पर समझाने और उनका मतलब बताने के लिए किया जा सकता है. भले ही कोई कॉम्पोनेंट प्रॉमिनेंट न हो, फिर भी वह डाइनैमिक कलर का इस्तेमाल कर सकता है.
उपयोगकर्ता, डिवाइस की सिस्टम सेटिंग में जाकर ऐप्लिकेशन की चमक को सेट कर सकता है. lib/src/shared/app.dart
में, जब डिवाइस को गहरे रंग वाले मोड पर सेट किया गया हो, तो MaterialApp
में गहरे रंग वाली थीम और थीम मोड को वापस लाएं.
lib/src/shared/app.dart
return MaterialApp.router(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: theme.light(settings.value.sourceColor),
darkTheme: theme.dark(settings.value.sourceColor), // Add this line
themeMode: theme.themeMode(), // Add this line
routeInformationParser: appRouter.routeInformationParser,
routerDelegate: appRouter.routerDelegate,
);
सबसे ऊपर दाएं कोने में मौजूद चांद के आइकॉन पर क्लिक करके, गहरे रंग वाला मोड चालू करें.
क्या कोई समस्या आ रही है?
अगर आपका ऐप्लिकेशन ठीक से नहीं चल रहा है, तो ट्रैक पर वापस आने के लिए नीचे दिए गए लिंक पर कोड का इस्तेमाल करें.
6. अडैप्टिव डिज़ाइन जोड़ें
Flutter की मदद से ऐसे ऐप्लिकेशन बनाए जा सकते हैं जो करीब-करीब हर जगह काम करते हैं. हालांकि, इसका मतलब यह नहीं है कि हर ऐप्लिकेशन के काम करने का तरीका हर जगह एक जैसा होता है. लोग, अलग-अलग प्लैटफ़ॉर्म पर अलग-अलग तरह की सुविधाओं और व्यवहार की उम्मीद रखने लगे हैं.
मटीरियल अलग-अलग तरह के पैकेज उपलब्ध कराता है, ताकि ज़रूरत के हिसाब से लेआउट के साथ काम करना आसान हो जाए. इन Flutter पैकेज को GitHub पर देखा जा सकता है.
क्रॉस-प्लैटफ़ॉर्म और अडैप्टिव ऐप्लिकेशन बनाते समय प्लैटफ़ॉर्म के इन अंतरों को ध्यान में रखें:
- इनपुट का तरीका: माउस, टच या गेमपैड
- फ़ॉन्ट का साइज़, डिवाइस की स्क्रीन की दिशा, और वीडियो देखने की दूरी
- स्क्रीन का साइज़ और डिवाइस का नाप या आकार: फ़ोन, टैबलेट, फ़ोल्ड किया जा सकने वाला, डेस्कटॉप, वेब
lib/src/shared/views/adaptive_navigation.dart
फ़ाइल में एक नेविगेशन क्लास होती है, जहां मुख्य हिस्से को रेंडर करने के लिए डेस्टिनेशन और कॉन्टेंट की सूची दी जा सकती है. इस लेआउट को एक से ज़्यादा स्क्रीन पर इस्तेमाल करने की वजह से, यहां एक शेयर किया गया बेस लेआउट होता है, जिसे हर चाइल्ड खाते को भेजा जा सकता है. नेविगेशन रेल, डेस्कटॉप और बड़ी स्क्रीन पर अच्छी तरह से काम करती हैं. हालांकि, इसके बजाय मोबाइल पर सबसे नीचे वाला नेविगेशन बार दिखाकर, लेआउट को मोबाइल के हिसाब से बनाएं.
lib/src/shared/views/adaptive_navigation.dart
import 'package:flutter/material.dart';
class AdaptiveNavigation extends StatelessWidget {
const AdaptiveNavigation({
super.key,
required this.destinations,
required this.selectedIndex,
required this.onDestinationSelected,
required super.child,
});
final List<NavigationDestination> destinations;
final int selectedIndex;
final void Function(int index) onDestinationSelected;
final Widget child;
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, dimens) {
// Tablet Layout
if (dimens.maxWidth >= 600) { // Add this line
return Scaffold(
body: Row(
children: [
NavigationRail(
extended: dimens.maxWidth >= 800,
minExtendedWidth: 180,
destinations: destinations
.map((e) => NavigationRailDestination(
icon: e.icon,
label: Text(e.label),
))
.toList(),
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
),
Expanded(child: child),
],
),
);
} // Add this line
// Mobile Layout
// Add from here...
return Scaffold(
body: child,
bottomNavigationBar: NavigationBar(
destinations: destinations,
selectedIndex: selectedIndex,
onDestinationSelected: onDestinationSelected,
),
);
// ... To here.
},
);
}
}
सभी स्क्रीन का साइज़ एक जैसा नहीं होता. अगर आपने फ़ोन पर अपने ऐप्लिकेशन का डेस्कटॉप वर्शन दिखाने की कोशिश की, तो सब कुछ देखने के लिए आपको स्क्विंट और ज़ूम करने का कुछ तरीका आज़माना होगा. आप चाहते हैं कि आपका ऐप्लिकेशन, उस स्क्रीन के हिसाब से कैसा दिखे जहां वह दिखता है. रिस्पॉन्सिव डिज़ाइन की मदद से, यह पक्का किया जाता है कि आपका ऐप्लिकेशन हर साइज़ की स्क्रीन पर अच्छा दिखे.
अपने ऐप्लिकेशन को रिस्पॉन्सिव बनाने के लिए, कुछ अडैप्टिव ब्रेकपॉइंट बताएं. साथ ही, डीबग करने वाले ब्रेकपॉइंट के बारे में न सोचें. ये ब्रेकपॉइंट, स्क्रीन के उन साइज़ के बारे में बताते हैं जहां आपके ऐप्लिकेशन को अपना लेआउट बदलना चाहिए.
छोटी स्क्रीन पर कॉन्टेंट का आकार छोटा किए बिना, ज़्यादा बड़ी स्क्रीन नहीं दिखाई जा सकती. ऐप्लिकेशन को छोटा किए गए डेस्कटॉप ऐप्लिकेशन की तरह दिखने से रोकने के लिए, मोबाइल के लिए अलग लेआउट बनाएं. इसमें कॉन्टेंट को अलग-अलग करने के लिए टैब का इस्तेमाल किया जाता है. इससे मोबाइल पर ऐप्लिकेशन बेहतर तरीके से दिखता है.
अलग-अलग टारगेट के लिए, ऑप्टिमाइज़ किए गए लेआउट डिज़ाइन करते समय, एक्सटेंशन के इन तरीकों (lib/src/shared/extensions.dart
में MyArtist प्रोजेक्ट में बताए गए) से शुरुआत की जा सकती है.
lib/src/shared/extensions.dart
extension BreakpointUtils on BoxConstraints {
bool get isTablet => maxWidth > 730;
bool get isDesktop => maxWidth > 1200;
bool get isMobile => !isTablet && !isDesktop;
}
ऐसी स्क्रीन जो 730 पिक्सल (सबसे लंबी दिशा में) से बड़ी, लेकिन 1200 पिक्सल से छोटी है, उसे टैबलेट माना जाता है. किसी भी ऑब्जेक्ट का साइज़, 1200 पिक्सल से ज़्यादा को डेस्कटॉप माना जाता है. अगर कोई डिवाइस टैबलेट या डेस्कटॉप नहीं है, तो उसे मोबाइल माना जाता है. अडैप्टिव ब्रेकपॉइंट के बारे में ज़्यादा जानने के लिए, material.io पर जाएं. आप adaptive_breakpoints पैकेज का इस्तेमाल कर सकते हैं.
होम स्क्रीन का रिस्पॉन्सिव लेआउट, मटीरियल डिज़ाइन में रिस्पॉन्सिव ग्रिड लेआउट लागू करने के लिए, adaptive_components और adaptive_breakpoints पैकेज का इस्तेमाल करके 12 कॉलम वाले ग्रिड के आधार पर AdaptiveContainer
और AdaptiveColumn
का इस्तेमाल करता है.
return LayoutBuilder(
builder: (context, constraints) {
return Scaffold(
body: SingleChildScrollView(
child: AdaptiveColumn(
children: [
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 40,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'Good morning',
style: context.displaySmall,
),
),
const SizedBox(width: 20),
const BrightnessToggle(),
],
),
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
children: [
const HomeHighlight(),
LayoutBuilder(
builder: (context, constraints) => HomeArtists(
artists: artists,
constraints: constraints,
),
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 20,
),
child: Text(
'Recently played',
style: context.headlineSmall,
),
),
HomeRecent(
playlists: playlists,
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.all(15),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.only(left: 8, bottom: 8),
child: Text(
'Top Songs Today',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: topSongs,
constraints: constraints,
),
),
],
),
),
const SizedBox(width: 25),
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.only(left: 8, bottom: 8),
child: Text(
'New Releases',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: newReleases,
constraints: constraints,
),
),
],
),
),
],
),
),
),
],
),
),
);
},
);
अडैप्टिव लेआउट में दो लेआउट होने चाहिए: एक मोबाइल के लिए और दूसरा, बड़ी स्क्रीन के लिए. LayoutBuilder
फ़िलहाल सिर्फ़ डेस्कटॉप का लेआउट दिखाता है. lib/src/features/home/view/home_screen.dart
में, TabBar
और TabBarView
के तौर पर मोबाइल लेआउट बनाएं. इसमें चार टैब होने चाहिए.
lib/src/features/home/view/home_screen.dart
import 'package:adaptive_components/adaptive_components.dart';
import 'package:flutter/material.dart';
import '../../../shared/classes/classes.dart';
import '../../../shared/extensions.dart';
import '../../../shared/providers/providers.dart';
import '../../../shared/views/views.dart';
import '../../playlists/view/playlist_songs.dart';
import 'view.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
final PlaylistsProvider playlistProvider = PlaylistsProvider();
final List<Playlist> playlists = playlistProvider.playlists;
final Playlist topSongs = playlistProvider.topSongs;
final Playlist newReleases = playlistProvider.newReleases;
final ArtistsProvider artistsProvider = ArtistsProvider();
final List<Artist> artists = artistsProvider.artists;
return LayoutBuilder(
builder: (context, constraints) {
// Add from here...
if (constraints.isMobile) {
return DefaultTabController(
length: 4,
child: Scaffold(
appBar: AppBar(
centerTitle: false,
title: const Text('Good morning'),
actions: const [BrightnessToggle()],
bottom: const TabBar(
isScrollable: true,
tabs: [
Tab(text: 'Home'),
Tab(text: 'Recently Played'),
Tab(text: 'New Releases'),
Tab(text: 'Top Songs'),
],
),
),
body: LayoutBuilder(
builder: (context, constraints) => TabBarView(
children: [
SingleChildScrollView(
child: Column(
children: [
const HomeHighlight(),
HomeArtists(
artists: artists,
constraints: constraints,
),
],
),
),
HomeRecent(
playlists: playlists,
axis: Axis.vertical,
),
PlaylistSongs(
playlist: topSongs,
constraints: constraints,
),
PlaylistSongs(
playlist: newReleases,
constraints: constraints,
),
],
),
),
),
);
}
// ... To here.
return Scaffold(
body: SingleChildScrollView(
child: AdaptiveColumn(
children: [
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 40,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'Good morning',
style: context.displaySmall,
),
),
const SizedBox(width: 20),
const BrightnessToggle(),
],
),
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
children: [
const HomeHighlight(),
LayoutBuilder(
builder: (context, constraints) => HomeArtists(
artists: artists,
constraints: constraints,
),
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 20,
),
child: Text(
'Recently played',
style: context.headlineSmall,
),
),
HomeRecent(
playlists: playlists,
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.all(15),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.only(left: 8, bottom: 8),
child: Text(
'Top Songs Today',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: topSongs,
constraints: constraints,
),
),
],
),
),
const SizedBox(width: 25),
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.only(left: 8, bottom: 8),
child: Text(
'New Releases',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: newReleases,
constraints: constraints,
),
),
],
),
),
],
),
),
),
],
),
),
);
},
);
}
}
क्या कोई समस्या आ रही है?
अगर आपका ऐप्लिकेशन ठीक से नहीं चल रहा है, तो ट्रैक पर वापस आने के लिए नीचे दिए गए लिंक पर कोड का इस्तेमाल करें.
खाली सफ़ेद जगह का इस्तेमाल करें
व्हाइटस्पेस आपके ऐप्लिकेशन के लिए एक अहम विज़ुअल टूल है. इससे अलग-अलग सेक्शन के बीच में एक खाली जगह बनती है.
ज़रूरत के मुताबिक खाली जगह का इस्तेमाल करने से अच्छा है कि बहुत ज़्यादा खाली सफ़ेद जगह हो. स्पेस में ज़्यादा फ़िट करने के लिए, फ़ॉन्ट या विज़ुअल एलिमेंट का साइज़ कम करने की तुलना में, खाली सफ़ेद जगह को ज़्यादा बेहतर तरीके से जोड़ा जा सकता है.
खाली सफ़ेद जगह न होने से उन लोगों को परेशानी हो सकती है जिन्हें देखने में परेशानी होती है. बहुत ज़्यादा खाली सफ़ेद जगह से जुड़ाव की कमी हो सकती है. इस वजह से, आपका यूज़र इंटरफ़ेस (यूआई) खराब तरीके से व्यवस्थित दिख सकता है. उदाहरण के लिए, ये स्क्रीनशॉट देखें:
इसके बाद, होम स्क्रीन में खाली जगह जोड़ने के लिए आपको उसमें खाली जगह जोड़नी होगी. इसके बाद, स्पेस को बेहतर बनाने के लिए, लेआउट में और बदलाव किया जा सकता है.
किसी विजेट को Padding
ऑब्जेक्ट के साथ रैप करें, ताकि उस विजेट के आस-पास खाली सफ़ेद जगह जोड़ी जा सके. lib/src/features/home/view/home_screen.dart
में मौजूद सभी पैडिंग वैल्यू को बढ़ाकर 35 करें:
lib/src/features/home/view/home_screen.dart
Scaffold(
body: SingleChildScrollView(
child: AdaptiveColumn(
children: [
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.all(35), // Modify this line
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'Good morning',
style: context.displaySmall,
),
),
const SizedBox(width: 20),
const BrightnessToggle(),
],
),
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
children: [
const HomeHighlight(),
LayoutBuilder(
builder: (context, constraints) => HomeArtists(
artists: artists,
constraints: constraints,
),
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(35), // Modify this line
child: Text(
'Recently played',
style: context.headlineSmall,
),
),
HomeRecent(
playlists: playlists,
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.all(35), // Modify this line
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.all(35), // Modify this line
child: Text(
'Top Songs Today',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) => PlaylistSongs(
playlist: topSongs,
constraints: constraints,
),
),
],
),
),
// Add spacer between tables
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding:
const EdgeInsets.all(35), // Modify this line
child: Text(
'New Releases',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) => PlaylistSongs(
playlist: newReleases,
constraints: constraints,
),
),
],
),
),
],
),
),
),
],
),
),
);
ऐप्लिकेशन को फिर से लोड करें. यह पहले की तरह ही दिखना चाहिए, लेकिन विजेट के बीच में ज़्यादा खाली सफ़ेद जगह दिखनी चाहिए. अतिरिक्त पैडिंग बेहतर दिखती है, लेकिन सबसे ऊपर मौजूद हाइलाइट बैनर, अब भी किनारों के बहुत पास है.
lib/src/features/home/view/home_highlight.dart
में, बैनर की पैडिंग (जगह) को बदलकर 35 करें:
lib/src/features/home/view/home_highlight.dart
class HomeHighlight extends StatelessWidget {
const HomeHighlight({super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(35), // Modify this line
child: Clickable(
child: SizedBox(
height: 275,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.asset(
'assets/images/news/concert.jpeg',
fit: BoxFit.cover,
),
),
),
onTap: () => launch('https://docs.flutter.dev'),
),
),
),
],
);
}
}
ऐप्लिकेशन को फिर से लोड करें. नीचे दो प्लेलिस्ट के बीच कोई खाली सफ़ेद जगह नहीं है, जिससे ऐसा लगता है कि वे एक ही टेबल से जुड़ी हैं. ऐसा नहीं है और आपको आगे इसे ठीक करना होगा.
प्लेलिस्ट के बीच खाली सफ़ेद जगह जोड़ें. इसके लिए, Row
में साइज़ का विजेट जोड़ें. lib/src/features/home/view/home_screen.dart
में, 35 चौड़ाई वाला SizedBox
जोड़ें:
lib/src/features/home/view/home_screen.dart
Padding(
padding: const EdgeInsets.all(35),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(35),
child: Text(
'Top Songs Today',
style: context.titleLarge,
),
),
PlaylistSongs(
playlist: topSongs,
constraints: constraints,
),
],
),
),
const SizedBox(width: 35), // Add this line
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(35),
child: Text(
'New Releases',
style: context.titleLarge,
),
),
PlaylistSongs(
playlist: newReleases,
constraints: constraints,
),
],
),
),
],
),
),
ऐप्लिकेशन को फिर से लोड करें. ऐप्लिकेशन ऐसा दिखना चाहिए:
अब होम स्क्रीन पर कॉन्टेंट के लिए बहुत जगह है, लेकिन सब कुछ अलग बहुत अलग दिख रहा है और सेक्शन एक जैसा नहीं है.
अब तक आपने होम स्क्रीन पर मौजूद विजेट की सभी पैडिंग (हॉरिज़ॉन्टल और वर्टिकल, दोनों) को EdgeInsets.all(35)
पर सेट कर लिया है. हालांकि, आप हर किनारे के लिए अलग-अलग पैडिंग (जगह) सेट कर सकते हैं. स्पेस में बेहतर तरीके से फ़िट करने के लिए पैडिंग को पसंद के मुताबिक बनाएं.
EdgeInsets.LTRB()
बाएं, ऊपर, दाएं, और नीचे के हिस्से को अलग-अलग सेट करता हैEdgeInsets.symmetric()
, वर्टिकल (ऊपर और नीचे) के लिए पैडिंग को बराबर पर सेट करता है. साथ ही, हॉरिज़ॉन्टल (बाएं और दाएं) के लिए पैडिंग को इसके बराबर सेट करता हैEdgeInsets.only()
सिर्फ़ तय किए गए किनारों को सेट करता है.
Scaffold(
body: SingleChildScrollView(
child: AdaptiveColumn(
children: [
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.fromLTRB(20, 25, 20, 10), // Modify this line
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'Good morning',
style: context.displaySmall,
),
),
const SizedBox(width: 20),
const BrightnessToggle(),
],
),
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
children: [
const HomeHighlight(),
LayoutBuilder(
builder: (context, constraints) => HomeArtists(
artists: artists,
constraints: constraints,
),
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 15,
vertical: 10,
), // Modify this line
child: Text(
'Recently played',
style: context.headlineSmall,
),
),
HomeRecent(
playlists: playlists,
),
],
),
),
AdaptiveContainer(
columnSpan: 12,
child: Padding(
padding: const EdgeInsets.all(15), // Modify this line
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 8, bottom: 8), // Modify this line
child: Text(
'Top Songs Today',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: topSongs,
constraints: constraints,
),
),
],
),
),
const SizedBox(width: 25),
Flexible(
flex: 10,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(left: 8, bottom: 8), // Modify this line
child: Text(
'New Releases',
style: context.titleLarge,
),
),
LayoutBuilder(
builder: (context, constraints) =>
PlaylistSongs(
playlist: newReleases,
constraints: constraints,
),
),
],
),
),
],
),
),
),
],
),
),
);
lib/src/features/home/view/home_highlight.dart
में, बैनर के बाईं और दाईं पैडिंग (जगह) को 35 पर सेट करें. साथ ही, सबसे ऊपर और सबसे नीचे की पैडिंग (जगह) को 5 पर सेट करें:
lib/src/features/home/view/home_highlight.dart
class HomeHighlight extends StatelessWidget {
const HomeHighlight({super.key});
@override
Widget build(BuildContext context) {
return Row(
children: [
Expanded(
child: Padding(
// Modify this line
padding: const EdgeInsets.symmetric(horizontal: 35, vertical: 5),
child: Clickable(
child: SizedBox(
height: 275,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.asset(
'assets/images/news/concert.jpeg',
fit: BoxFit.cover,
),
),
),
onTap: () => launch('https://docs.flutter.dev'),
),
),
),
],
);
}
}
ऐप्लिकेशन को फिर से लोड करें. लेआउट और स्पेसिंग काफ़ी बेहतर दिख रही है! अंतिम रूप देने के लिए, कुछ गति और एनिमेशन जोड़ें.
क्या कोई समस्या आ रही है?
अगर आपका ऐप्लिकेशन ठीक से नहीं चल रहा है, तो ट्रैक पर वापस आने के लिए नीचे दिए गए लिंक पर कोड का इस्तेमाल करें.
7. मोशन और ऐनिमेशन जोड़ें
मोशन और ऐनिमेशन, इन बेहतरीन तरीकों में से कोई भी काम कर सकता है: हलचल और ऊर्जा पैदा करने के साथ-साथ, जब उपयोगकर्ता ऐप्लिकेशन से इंटरैक्ट करे, तब इन बातों का सुझाव भी दिया जा सकता है.
अलग-अलग स्क्रीन के बीच ऐनिमेट करें
यह ThemeProvider
, मोबाइल प्लैटफ़ॉर्म (iOS, Android) के लिए स्क्रीन ट्रांज़िशन ऐनिमेशन वाले PageTransitionsTheme
के बारे में जानकारी देता है. डेस्कटॉप का इस्तेमाल करने वाले लोगों को पहले से ही माउस या ट्रैकपैड पर क्लिक करने से सुझाव मिल जाता है, इसलिए पेज ट्रांज़िशन ऐनिमेशन की ज़रूरत नहीं पड़ती.
Flutter, स्क्रीन ट्रांज़िशन के ऐनिमेशन उपलब्ध कराता है. इन्हें टारगेट प्लैटफ़ॉर्म के हिसाब से, अपने ऐप्लिकेशन के लिए कॉन्फ़िगर किया जा सकता है. इसके बारे में lib/src/shared/providers/theme.dart
में बताया गया है:
lib/src/shared/providers/theme.dart
final pageTransitionsTheme = const PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
TargetPlatform.android: FadeUpwardsPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
TargetPlatform.linux: NoAnimationPageTransitionsBuilder(),
TargetPlatform.macOS: NoAnimationPageTransitionsBuilder(),
TargetPlatform.windows: NoAnimationPageTransitionsBuilder(),
},
);
lib/src/shared/providers/theme.dart में PageTransitionsTheme
को हल्के और गहरे रंग, दोनों थीम के लिए पास करें
lib/src/shared/providers/theme.dart
ThemeData light([Color? targetColor]) {
final _colors = colors(Brightness.light, targetColor);
return ThemeData.light().copyWith(
pageTransitionsTheme: pageTransitionsTheme, // Add this line
colorScheme: ColorScheme.fromSeed(
seedColor: source(targetColor),
brightness: Brightness.light,
),
appBarTheme: appBarTheme(_colors),
cardTheme: cardTheme(),
listTileTheme: listTileTheme(),
tabBarTheme: tabBarTheme(_colors),
scaffoldBackgroundColor: _colors.background,
);
}
ThemeData dark([Color? targetColor]) {
final _colors = colors(Brightness.dark, targetColor);
return ThemeData.dark().copyWith(
pageTransitionsTheme: pageTransitionsTheme, // Add this line
colorScheme: ColorScheme.fromSeed(
seedColor: source(targetColor),
brightness: Brightness.dark,
),
appBarTheme: appBarTheme(_colors),
cardTheme: cardTheme(),
listTileTheme: listTileTheme(),
tabBarTheme: tabBarTheme(_colors),
scaffoldBackgroundColor: _colors.background,
);
}
iOS पर ऐनिमेशन के बिना
iOS पर ऐनिमेशन के साथ
क्या कोई समस्या आ रही है?
अगर आपका ऐप्लिकेशन ठीक से नहीं चल रहा है, तो ट्रैक पर वापस आने के लिए नीचे दिए गए लिंक पर कोड का इस्तेमाल करें.
कर्सर घुमाने पर दिखने वाली स्थितियां जोड़ना
किसी डेस्कटॉप ऐप्लिकेशन में मोशन जोड़ने का एक तरीका होवर स्टेट है. जब उपयोगकर्ता कर्सर पर कर्सर घुमाता है, तो विजेट अपनी स्थिति (जैसे कि रंग, आकार या कॉन्टेंट) बदलता है.
डिफ़ॉल्ट रूप से, _OutlinedCardState
क्लास (इसे "हाल ही में चलाई गई" प्लेलिस्ट टाइल के लिए इस्तेमाल किया जाता है) एक MouseRegion
दिखाती है, जो कर्सर को कर्सर घुमाने पर पॉइंटर में बदल जाती है—हालांकि, आपके पास ज़्यादा विज़ुअल फ़ीडबैक जोड़ने का विकल्प होता है.
lib/src/shared/views/outlined_card.dart को खोलें और _hovered
की स्थिति बताने के लिए, इसके कॉन्टेंट को यहां दिए गए तरीके से बदलें.
lib/src/shared/views/outlined_card.dart
import 'package:flutter/material.dart';
class OutlinedCard extends StatefulWidget {
const OutlinedCard({
super.key,
required this.child,
this.clickable = true,
});
final Widget child;
final bool clickable;
@override
State<OutlinedCard> createState() => _OutlinedCardState();
}
class _OutlinedCardState extends State<OutlinedCard> {
bool _hovered = false;
@override
Widget build(BuildContext context) {
final borderRadius = BorderRadius.circular(_hovered ? 20 : 8);
const animationCurve = Curves.easeInOut;
return MouseRegion(
onEnter: (_) {
if (!widget.clickable) return;
setState(() {
_hovered = true;
});
},
onExit: (_) {
if (!widget.clickable) return;
setState(() {
_hovered = false;
});
},
cursor: widget.clickable ? SystemMouseCursors.click : SystemMouseCursors.basic,
child: AnimatedContainer(
duration: kThemeAnimationDuration,
curve: animationCurve,
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.outline,
width: 1,
),
borderRadius: borderRadius,
),
foregroundDecoration: BoxDecoration(
color: Theme.of(context).colorScheme.onSurface.withOpacity(
_hovered ? 0.12 : 0,
),
borderRadius: borderRadius,
),
child: TweenAnimationBuilder<BorderRadius>(
duration: kThemeAnimationDuration,
curve: animationCurve,
tween: Tween(begin: BorderRadius.zero, end: borderRadius),
builder: (context, borderRadius, child) => ClipRRect(
clipBehavior: Clip.antiAlias,
borderRadius: borderRadius,
child: child,
),
child: widget.child,
),
),
);
}
}
ऐप्लिकेशन को फिर से लोड करें और हाल ही में सुनी गई प्लेलिस्ट की टाइल पर कर्सर घुमाएं.
OutlinedCard
की ओपैसिटी कम हो जाती है और यह कोनों को गोल कर देता है.
आखिर में, lib/src/shared/views/hoverable_song_play_button.dart
में बताए गए HoverableSongPlayButton
विजेट का इस्तेमाल करके, किसी प्लेलिस्ट में मौजूद गाने के नंबर को प्ले बटन में ऐनिमेट करें. lib/src/features/playlists/view/playlist_songs.dart
में, Center
विजेट (जिसमें गाने का नंबर होता है) को HoverableSongPlayButton
के साथ रैप करें:
lib/src/features/playlists/view/playlist_songs.dart
HoverableSongPlayButton( // Add this line
hoverMode: HoverMode.overlay, // Add this line
song: playlist.songs[index], // Add this line
child: Center( // Modify this line
child: Text(
(index + 1).toString(),
textAlign: TextAlign.center,
),
),
), // Add this line
ऐप्लिकेशन को फिर से लोड करें और कर्सर को आज के सबसे लोकप्रिय गानों या नई रिलीज़ की प्लेलिस्ट में मौजूद गाने के नंबर पर घुमाएं.
ऐसा करने से, नंबर चलाएं बटन के तौर पर दिखने लगता है. बटन पर क्लिक करने से, गाना चलने लगता है.
GitHub पर फ़ाइनल प्रोजेक्ट कोड देखें.
8. बधाई हो!
आपने यह कोडलैब पूरा कर लिया है! आपको पता है कि ऐप्लिकेशन में कई छोटे-छोटे बदलाव किए जा सकते हैं. इन्हें इंटिग्रेट करके, ऐप्लिकेशन को और भी खूबसूरत बनाया जा सकता है. साथ ही, ऐप्लिकेशन को कई प्लैटफ़ॉर्म के लिए और भी बेहतर बनाया जा सकता है. साथ ही, उसे आसानी से स्थानीय भाषा में भी उपलब्ध कराया जा सकता है. इन तकनीकों में ये शामिल हैं, लेकिन इन्हीं तक सीमित नहीं हैं:
- टाइपोग्राफ़ी: टेक्स्ट सिर्फ़ एक बातचीत टूल से कहीं बढ़कर है. उपयोगकर्ताओं पर पॉज़िटिव असर डालने के लिए टेक्स्ट के दिखाए जाने के तरीके का इस्तेमाल करें और आपके ऐप का अनुभव.
- थीम बनाना: ऐसा डिज़ाइन सिस्टम बनाएं जिसका इस्तेमाल आप किसी भी विजेट के लिए डिज़ाइन से जुड़े फ़ैसले लिए बिना कर सकें.
- ज़रूरत के हिसाब से इस्तेमाल: उस डिवाइस और प्लैटफ़ॉर्म को ध्यान में रखें जिस पर उपयोगकर्ता आपका ऐप्लिकेशन चला रहा है और उसकी क्षमताओं का भी ध्यान रखें. स्क्रीन के साइज़ और ऐप्लिकेशन के दिखने के तरीके को ध्यान में रखें.
- मोशन और ऐनिमेशन: अपने ऐप्लिकेशन में हलचल करने से, उपयोगकर्ता का अनुभव बेहतर होता है. साथ ही, इससे उपयोगकर्ताओं को सुझाव, शिकायत या राय मिलती है.
कुछ छोटे-छोटे बदलावों की मदद से, आपका ऐप्लिकेशन उबाऊ से खूबसूरत हो सकता है:
पहले
बाद में
अगले चरण
हमें उम्मीद है कि आपने Flutter में खूबसूरत ऐप्लिकेशन बनाने के बारे में ज़्यादा जान लिया है!
अगर आपने यहां बताए गए सुझावों या तरकीबों में से किसी एक को लागू किया है या आपके पास शेयर करने के लिए कोई सलाह है, तो हमें बताएं. आपका सुझाव पाकर हमें खुशी होगी! हमसे Twitter पर @rodydavis और @khanhnwin पर संपर्क करें!
इन संसाधनों से भी आपको मदद मिल सकती है.
थीम बनाई जा रही है
- मटीरियल थीम बिल्डर (टूल)
ज़रूरत के हिसाब से और रिस्पॉन्सिव संसाधन:
- अडैप्टिव बनाम रिस्पॉन्सिव मोड पर Flutter का इस्तेमाल करना (वीडियो)
- अडैप्टिव लेआउट (द बोरिंग फ़्लटर डेवलपमेंट शो से वीडियो)
- रिस्पॉन्सिव और अडैप्टिव ऐप्लिकेशन बनाना (flutter.dev)
- Flutter के लिए अडैप्टिव मटीरियल कॉम्पोनेंट (GitHub पर लाइब्रेरी)
- बड़ी स्क्रीन के लिए अपने ऐप्लिकेशन को तैयार करने के लिए 5 तरीके (Google I/O 2021 का वीडियो)
डिज़ाइन से जुड़े सामान्य संसाधन:
- छोटी-छोटी चीज़ें: काल्पनिक डिज़ाइनर-डेवलपर बनना (Flutter Engage से वीडियो)
- फ़ोल्ड किए जा सकने वाले डिवाइसों के लिए मटीरियल डिज़ाइन 3 (material.io)
साथ ही, Flutter कम्यूनिटी से जुड़ें!
आगे बढ़ें और ऐप्लिकेशन की दुनिया को खूबसूरत बनाएं!