Flutter ऐप्लिकेशन को बोरिंग से शानदार ऐप्लिकेशन बनाएं

1. परिचय

Flutter, Google का यूज़र इंटरफ़ेस (यूआई) टूलकिट है. इसकी मदद से, एक ही कोड बेस से मोबाइल, वेब, और डेस्कटॉप पर सुंदर और मूल रूप से कंपाइल किए गए ऐप्लिकेशन बनाए जा सकते हैं. Flutter मौजूदा कोड के साथ काम करता है. दुनिया भर के डेवलपर और संगठन इसका इस्तेमाल करते हैं. साथ ही, यह मुफ़्त और ओपन सोर्स है.

इस कोडलैब में, Flutter संगीत ऐप्लिकेशन को बेहतर बनाया गया है. यह ऐप्लिकेशन उबाऊ से खूबसूरत है. ऐसा करने के लिए, यह कोडलैब Material 3 में पेश किए गए टूल और एपीआई का इस्तेमाल करता है.

आप इन चीज़ों के बारे में जानेंगे

  • ऐसा Flutter ऐप्लिकेशन कैसे लिखें जिसे सभी प्लैटफ़ॉर्म पर इस्तेमाल किया जा सके और जो इस्तेमाल में आसान हो.
  • अपने ऐप्लिकेशन में टेक्स्ट कैसे डिज़ाइन करें, ताकि यह पक्का किया जा सके कि इससे उपयोगकर्ता अनुभव बेहतर हो रहा है.
  • अपनी ज़रूरत के हिसाब से सही रंग चुनने, विजेट को पसंद के मुताबिक बनाने, और अपनी थीम बनाने के साथ-साथ, गहरे रंग वाला मोड तुरंत और आसानी से लागू करने का तरीका जानें.
  • क्रॉस-प्लैटफ़ॉर्म की सुविधा वाले अडैप्टिव ऐप्लिकेशन बनाने का तरीका.
  • किसी भी स्क्रीन पर अच्छे दिखने वाले ऐप्लिकेशन बनाने का तरीका.
  • अपने Flutter ऐप्लिकेशन को शानदार बनाने के लिए उसमें मूवमेंट जोड़ें.

ज़रूरी शर्तें:

यह कोडलैब यह मानता है कि आपको Flutter का इस्तेमाल करने का कुछ अनुभव है. अगर ऐसा नहीं है, तो आपको सबसे पहले बुनियादी बातें पता करनी होंगी. यहां दिए गए लिंक से हमें मदद मिलेगी:

आपको क्या बनाना होगा

इस कोडलैब से आपको 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 में खोलें और ऐप्लिकेशन को चलाने के लिए, इसके टूल का इस्तेमाल करें.

a3c16fc17be25f6c.png ऐप्लिकेशन चलाएं.

हो गया! MyArtist की होम स्क्रीन के लिए स्टार्टर कोड काम करना चाहिए. आपको MyArtist की होम स्क्रीन दिखेगी. यह डेस्कटॉप पर ठीक लगता है, लेकिन मोबाइल के लिए... बहुत बढ़िया नहीं. एक बात, तो यह इस नॉक का सम्मान नहीं करता. चिंता न करें, आप इसे ठीक कर देंगे!

1e67c60667821082.png d1139cde225de452.png

कोड के बारे में पूरी जानकारी देखना

इसके बाद, कोड के बारे में जानें.

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 में हर मुख्य रास्ते के लिए टैब होते हैं, लेकिन सबसे बेहतर आइकॉन एक जैसे हैं:

86c5f73b3aa5fd35.png

यह उपयोगी नहीं है, क्योंकि उपयोगकर्ता को अब भी हर टैब का टेक्स्ट पढ़ना होता है. शुरुआत करने के लिए विज़ुअल संकेत जोड़ें, ताकि उपयोगकर्ता तेज़ी से मुख्य आइकॉन पर नज़र डालकर अपनी पसंद का टैब ढूंढ सके. इससे स्थानीय भाषा के मुताबिक अनुवाद और सुलभता सेवाओं को बेहतर बनाने में भी मदद मिलती है.

a3c16fc17be25f6c.png 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',
  ),
];

23278e4f4610fbf4.png

क्या कोई समस्या आ रही है?

अगर आपका ऐप्लिकेशन ठीक से नहीं चल रहा है, तो देखें कि टाइपिंग में कोई गड़बड़ी है या नहीं. अगर ज़रूरी हो, तो ट्रैक पर वापस आने के लिए, नीचे दिए गए लिंक पर कोड का इस्तेमाल करें.

सोच-समझकर फ़ॉन्ट चुनें

फ़ॉन्ट आपके ऐप्लिकेशन की खासियत तय करते हैं, इसलिए सही फ़ॉन्ट चुनना बहुत ज़रूरी है. फ़ॉन्ट चुनते समय, इन बातों का ध्यान रखें:

  • Sans-Serif या Serif: Serif फ़ॉन्ट में सजावटी स्ट्रोक या "टेल" होते हैं इन्हें अक्षरों के अंत में लिखा जाता है. इन्हें ज़्यादा औपचारिक माना जाता है. सान्स-सेरिफ़ फ़ॉन्ट में सजावटी स्ट्रोक नहीं होते हैं और इन्हें ज़्यादा अनौपचारिक माना जाता है. 34bf54e4cad90101.png A San Serif Capital T और सेरिफ़ कैपिटल T
  • सभी कैप फ़ॉन्ट: छोटे टेक्स्ट पर ध्यान देने के लिए, सिर्फ़ बड़े अक्षरों का इस्तेमाल करना सही है. इसे हेडलाइन माना जाता है. हालांकि, ज़्यादा इस्तेमाल किए जाने पर इसे चिल्लाने की वजह से समझा जा सकता है, ताकि उपयोगकर्ता इसे पूरी तरह अनदेखा कर सके.
  • टाइटल केस या वाक्य का केस: टाइटल या लेबल जोड़ते समय, ध्यान रखें कि कैपिटल लेटर का इस्तेमाल कैसे किया जाता है: टाइटल केस, जहां हर शब्द का पहला अक्षर कैपिटल लेटर में होता है ("This is a Titlecase Title"), फ़ॉर्मल टोन में लिखा जाता है. वाक्य का केस, जिसमें सिर्फ़ व्यक्तिवाचक संज्ञाओं को कैपिटल में रखा जाता है और टेक्स्ट में पहला शब्द ("यह वाक्य के केस का टाइटल है"), बातचीत वाला ज़्यादा अनौपचारिक है.
  • केर्निंग (हर अक्षर के बीच की दूरी), लाइन की लंबाई (स्क्रीन पर मौजूद पूरे टेक्स्ट की चौड़ाई), और लाइन की ऊंचाई (टेक्स्ट की हर लाइन कितनी लंबी है): इनमें से किसी भी लाइन में बहुत ज़्यादा या बहुत कम होने की वजह से, आपके ऐप्लिकेशन को पढ़ा नहीं जा सकता. उदाहरण के लिए, टेक्स्ट के किसी बड़े और बिना रुकावट वाले ब्लॉक को पढ़ते समय, हो सकता है कि आप अपनी जगह की जानकारी खो दें.

इसे ध्यान में रखते हुए, Google Fonts में जाएं और Montserrat जैसा कोई Sans-S फ़ॉन्ट चुनें. ऐसा इसलिए, क्योंकि इस ऐप्लिकेशन का मकसद मज़ेदार और दिलचस्प है.

a3c16fc17be25f6c.png कमांड लाइन से, 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>

a3c16fc17be25f6c.png lib/src/shared/extensions.dart में, नया पैकेज इंपोर्ट करें:

lib/src/shared/extensions.dart

import 'package:google_fonts/google_fonts.dart';  // Add this line.

a3c16fc17be25f6c.png मोंसेर्राट सेट करें TextTheme:

TextTheme get textTheme => GoogleFonts.montserratTextTheme(theme.textTheme); // Modify this line

a3c16fc17be25f6c.png बदलावों को चालू करने के लिए, 7f9a9e103c7b5e5.png को फिर से लोड करें. (अपने IDE में मौजूद बटन का इस्तेमाल करें या कमांड लाइन से, हॉट रीलोड के लिए r डालें.):

1e67c60667821082.png

आपको 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);
 }
}

a3c16fc17be25f6c.png प्रोवाइडर का इस्तेमाल करने के लिए, एक इंस्टेंस बनाएं और उसे 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 में लॉन्च किया गया है. इससे आपको अपने ऐप्लिकेशन के लिए, मिलते-जुलते रंगों का सेट चुनने में मदद मिलती है.

a3c16fc17be25f6c.pngऐप्लिकेशन के लिए सोर्स का रंग चुनने के लिए, मटीरियल थीम बिल्डर खोलें और यूज़र इंटरफ़ेस (यूआई) के लिए अलग-अलग रंग एक्सप्लोर करें. ब्रैंड की खूबसूरती और/या आपकी पसंद के मुताबिक रंग चुनना ज़रूरी है.

थीम बनाने के बाद, प्राइमरी कलर बबल पर राइट-क्लिक करें. इससे एक डायलॉग बॉक्स खुलता है, जिसमें मुख्य रंग की हेक्स वैल्यू होती है. इस वैल्यू को कॉपी करें. (इस डायलॉग बॉक्स का इस्तेमाल करके भी रंग सेट किया जा सकता है.)

a3c16fc17be25f6c.pngमुख्य रंग की हेक्स वैल्यू, थीम देने वाले को पास करें. उदाहरण के लिए, #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;

a3c16fc17be25f6c.pngकिसी खास रंग का इस्तेमाल करने के लिए, 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

इसके अलावा, हल्के और गहरे रंग वाली, दोनों थीम के लिए नए डिज़ाइन टोकन इस्तेमाल किए जा सकते हैं:

7b51703ed96196a4.png

रंग वाली इन भूमिकाओं का इस्तेमाल, यूज़र इंटरफ़ेस (यूआई) के अलग-अलग हिस्सों को खास तौर पर समझाने और उनका मतलब बताने के लिए किया जा सकता है. भले ही कोई कॉम्पोनेंट प्रॉमिनेंट न हो, फिर भी वह डाइनैमिक कलर का इस्तेमाल कर सकता है.

a3c16fc17be25f6c.png उपयोगकर्ता, डिवाइस की सिस्टम सेटिंग में जाकर ऐप्लिकेशन की चमक को सेट कर सकता है. 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 पर देखा जा सकता है.

क्रॉस-प्लैटफ़ॉर्म और अडैप्टिव ऐप्लिकेशन बनाते समय प्लैटफ़ॉर्म के इन अंतरों को ध्यान में रखें:

  • इनपुट का तरीका: माउस, टच या गेमपैड
  • फ़ॉन्ट का साइज़, डिवाइस की स्क्रीन की दिशा, और वीडियो देखने की दूरी
  • स्क्रीन का साइज़ और डिवाइस का नाप या आकार: फ़ोन, टैबलेट, फ़ोल्ड किया जा सकने वाला, डेस्कटॉप, वेब

a3c16fc17be25f6c.png 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.
      },
    );
  }
}

a8487a3c4d7890c9.png

सभी स्क्रीन का साइज़ एक जैसा नहीं होता. अगर आपने फ़ोन पर अपने ऐप्लिकेशन का डेस्कटॉप वर्शन दिखाने की कोशिश की, तो सब कुछ देखने के लिए आपको स्क्विंट और ज़ूम करने का कुछ तरीका आज़माना होगा. आप चाहते हैं कि आपका ऐप्लिकेशन, उस स्क्रीन के हिसाब से कैसा दिखे जहां वह दिखता है. रिस्पॉन्सिव डिज़ाइन की मदद से, यह पक्का किया जाता है कि आपका ऐप्लिकेशन हर साइज़ की स्क्रीन पर अच्छा दिखे.

अपने ऐप्लिकेशन को रिस्पॉन्सिव बनाने के लिए, कुछ अडैप्टिव ब्रेकपॉइंट बताएं. साथ ही, डीबग करने वाले ब्रेकपॉइंट के बारे में न सोचें. ये ब्रेकपॉइंट, स्क्रीन के उन साइज़ के बारे में बताते हैं जहां आपके ऐप्लिकेशन को अपना लेआउट बदलना चाहिए.

छोटी स्क्रीन पर कॉन्टेंट का आकार छोटा किए बिना, ज़्यादा बड़ी स्क्रीन नहीं दिखाई जा सकती. ऐप्लिकेशन को छोटा किए गए डेस्कटॉप ऐप्लिकेशन की तरह दिखने से रोकने के लिए, मोबाइल के लिए अलग लेआउट बनाएं. इसमें कॉन्टेंट को अलग-अलग करने के लिए टैब का इस्तेमाल किया जाता है. इससे मोबाइल पर ऐप्लिकेशन बेहतर तरीके से दिखता है.

अलग-अलग टारगेट के लिए, ऑप्टिमाइज़ किए गए लेआउट डिज़ाइन करते समय, एक्सटेंशन के इन तरीकों (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,
                                ),
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );

a3c16fc17be25f6c.pngअडैप्टिव लेआउट में दो लेआउट होने चाहिए: एक मोबाइल के लिए और दूसरा, बड़ी स्क्रीन के लिए. 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,
                                ),
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
     },
   );
 }
}

377cfdda63a9de54.png

क्या कोई समस्या आ रही है?

अगर आपका ऐप्लिकेशन ठीक से नहीं चल रहा है, तो ट्रैक पर वापस आने के लिए नीचे दिए गए लिंक पर कोड का इस्तेमाल करें.

खाली सफ़ेद जगह का इस्तेमाल करें

व्हाइटस्पेस आपके ऐप्लिकेशन के लिए एक अहम विज़ुअल टूल है. इससे अलग-अलग सेक्शन के बीच में एक खाली जगह बनती है.

ज़रूरत के मुताबिक खाली जगह का इस्तेमाल करने से अच्छा है कि बहुत ज़्यादा खाली सफ़ेद जगह हो. स्पेस में ज़्यादा फ़िट करने के लिए, फ़ॉन्ट या विज़ुअल एलिमेंट का साइज़ कम करने की तुलना में, खाली सफ़ेद जगह को ज़्यादा बेहतर तरीके से जोड़ा जा सकता है.

खाली सफ़ेद जगह न होने से उन लोगों को परेशानी हो सकती है जिन्हें देखने में परेशानी होती है. बहुत ज़्यादा खाली सफ़ेद जगह से जुड़ाव की कमी हो सकती है. इस वजह से, आपका यूज़र इंटरफ़ेस (यूआई) खराब तरीके से व्यवस्थित दिख सकता है. उदाहरण के लिए, ये स्क्रीनशॉट देखें:

7f5e3514a7ee1750.png

d5144a50f5b4142c.png

इसके बाद, होम स्क्रीन में खाली जगह जोड़ने के लिए आपको उसमें खाली जगह जोड़नी होगी. इसके बाद, स्पेस को बेहतर बनाने के लिए, लेआउट में और बदलाव किया जा सकता है.

a3c16fc17be25f6c.png किसी विजेट को 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,
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );

a3c16fc17be25f6c.png ऐप्लिकेशन को फिर से लोड करें. यह पहले की तरह ही दिखना चाहिए, लेकिन विजेट के बीच में ज़्यादा खाली सफ़ेद जगह दिखनी चाहिए. अतिरिक्त पैडिंग बेहतर दिखती है, लेकिन सबसे ऊपर मौजूद हाइलाइट बैनर, अब भी किनारों के बहुत पास है.

a3c16fc17be25f6c.png 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'),
            ),
          ),
        ),
      ],
    );
  }
}

a3c16fc17be25f6c.png ऐप्लिकेशन को फिर से लोड करें. नीचे दो प्लेलिस्ट के बीच कोई खाली सफ़ेद जगह नहीं है, जिससे ऐसा लगता है कि वे एक ही टेबल से जुड़ी हैं. ऐसा नहीं है और आपको आगे इसे ठीक करना होगा.

df1d9af97d039cc8.png

a3c16fc17be25f6c.png प्लेलिस्ट के बीच खाली सफ़ेद जगह जोड़ें. इसके लिए, 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,
            ),
          ],
        ),
      ),
    ],
  ),
),

a3c16fc17be25f6c.png ऐप्लिकेशन को फिर से लोड करें. ऐप्लिकेशन ऐसा दिखना चाहिए:

d8b2a3d47736dbab.png

अब होम स्क्रीन पर कॉन्टेंट के लिए बहुत जगह है, लेकिन सब कुछ अलग बहुत अलग दिख रहा है और सेक्शन एक जैसा नहीं है.

a3c16fc17be25f6c.png अब तक आपने होम स्क्रीन पर मौजूद विजेट की सभी पैडिंग (हॉरिज़ॉन्टल और वर्टिकल, दोनों) को 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,
                                ),
                              ),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            ),
          ),
        );

a3c16fc17be25f6c.png 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'),
            ),
          ),
        ),
      ],
    );
  }
}

a3c16fc17be25f6c.png ऐप्लिकेशन को फिर से लोड करें. लेआउट और स्पेसिंग काफ़ी बेहतर दिख रही है! अंतिम रूप देने के लिए, कुछ गति और एनिमेशन जोड़ें.

7f5e3514a7ee1750.png

क्या कोई समस्या आ रही है?

अगर आपका ऐप्लिकेशन ठीक से नहीं चल रहा है, तो ट्रैक पर वापस आने के लिए नीचे दिए गए लिंक पर कोड का इस्तेमाल करें.

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(),
  },
);

a3c16fc17be25f6c.png 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 दिखाती है, जो कर्सर को कर्सर घुमाने पर पॉइंटर में बदल जाती है—हालांकि, आपके पास ज़्यादा विज़ुअल फ़ीडबैक जोड़ने का विकल्प होता है.

a3c16fc17be25f6c.png 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,
        ),
      ),
    );
  }
}

a3c16fc17be25f6c.png ऐप्लिकेशन को फिर से लोड करें और हाल ही में सुनी गई प्लेलिस्ट की टाइल पर कर्सर घुमाएं.

OutlinedCard की ओपैसिटी कम हो जाती है और यह कोनों को गोल कर देता है.

a3c16fc17be25f6c.png आखिर में, 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

a3c16fc17be25f6c.pngऐप्लिकेशन को फिर से लोड करें और कर्सर को आज के सबसे लोकप्रिय गानों या नई रिलीज़ की प्लेलिस्ट में मौजूद गाने के नंबर पर घुमाएं.

ऐसा करने से, नंबर चलाएं बटन के तौर पर दिखने लगता है. बटन पर क्लिक करने से, गाना चलने लगता है.

GitHub पर फ़ाइनल प्रोजेक्ट कोड देखें.

8. बधाई हो!

आपने यह कोडलैब पूरा कर लिया है! आपको पता है कि ऐप्लिकेशन में कई छोटे-छोटे बदलाव किए जा सकते हैं. इन्हें इंटिग्रेट करके, ऐप्लिकेशन को और भी खूबसूरत बनाया जा सकता है. साथ ही, ऐप्लिकेशन को कई प्लैटफ़ॉर्म के लिए और भी बेहतर बनाया जा सकता है. साथ ही, उसे आसानी से स्थानीय भाषा में भी उपलब्ध कराया जा सकता है. इन तकनीकों में ये शामिल हैं, लेकिन इन्हीं तक सीमित नहीं हैं:

  • टाइपोग्राफ़ी: टेक्स्ट सिर्फ़ एक बातचीत टूल से कहीं बढ़कर है. उपयोगकर्ताओं पर पॉज़िटिव असर डालने के लिए टेक्स्ट के दिखाए जाने के तरीके का इस्तेमाल करें और आपके ऐप का अनुभव.
  • थीम बनाना: ऐसा डिज़ाइन सिस्टम बनाएं जिसका इस्तेमाल आप किसी भी विजेट के लिए डिज़ाइन से जुड़े फ़ैसले लिए बिना कर सकें.
  • ज़रूरत के हिसाब से इस्तेमाल: उस डिवाइस और प्लैटफ़ॉर्म को ध्यान में रखें जिस पर उपयोगकर्ता आपका ऐप्लिकेशन चला रहा है और उसकी क्षमताओं का भी ध्यान रखें. स्क्रीन के साइज़ और ऐप्लिकेशन के दिखने के तरीके को ध्यान में रखें.
  • मोशन और ऐनिमेशन: अपने ऐप्लिकेशन में हलचल करने से, उपयोगकर्ता का अनुभव बेहतर होता है. साथ ही, इससे उपयोगकर्ताओं को सुझाव, शिकायत या राय मिलती है.

कुछ छोटे-छोटे बदलावों की मदद से, आपका ऐप्लिकेशन उबाऊ से खूबसूरत हो सकता है:

पहले

1e67c60667821082.png

बाद में

अगले चरण

हमें उम्मीद है कि आपने Flutter में खूबसूरत ऐप्लिकेशन बनाने के बारे में ज़्यादा जान लिया है!

अगर आपने यहां बताए गए सुझावों या तरकीबों में से किसी एक को लागू किया है या आपके पास शेयर करने के लिए कोई सलाह है, तो हमें बताएं. आपका सुझाव पाकर हमें खुशी होगी! हमसे Twitter पर @rodydavis और @khanhnwin पर संपर्क करें!

इन संसाधनों से भी आपको मदद मिल सकती है.

थीम बनाई जा रही है

ज़रूरत के हिसाब से और रिस्पॉन्सिव संसाधन:

डिज़ाइन से जुड़े सामान्य संसाधन:

साथ ही, Flutter कम्यूनिटी से जुड़ें!

आगे बढ़ें और ऐप्लिकेशन की दुनिया को खूबसूरत बनाएं!