Flutter में ऐनिमेशन

1. परिचय

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

Flutter के ऐनिमेशन फ़्रेमवर्क की खास जानकारी

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

  • इंप्लिसिट ऐनिमेशन, पहले से बने हुए ऐनिमेशन इफ़ेक्ट होते हैं. ये पूरे ऐनिमेशन को अपने-आप चलाते हैं. एनिमेशन की टारगेट वैल्यू बदलने पर, यह एनिमेशन को मौजूदा वैल्यू से टारगेट वैल्यू तक चलाता है. साथ ही, बीच की हर वैल्यू को दिखाता है, ताकि विजेट आसानी से ऐनिमेट हो सके. इंप्लिसिट ऐनिमेशन के उदाहरणों में AnimatedSize, AnimatedScale, और AnimatedPositioned शामिल हैं.
  • एक्सप्लिसिट ऐनिमेशन भी पहले से बने हुए ऐनिमेशन इफ़ेक्ट होते हैं. हालांकि, इनके काम करने के लिए Animation ऑब्जेक्ट की ज़रूरत होती है. उदाहरण के लिए, SizeTransition, ScaleTransition या PositionedTransition.
  • Animation एक क्लास है. यह चालू या बंद किए गए एनिमेशन को दिखाती है. इसमें value शामिल होती है. यह टारगेट वैल्यू को दिखाती है जिस पर एनिमेशन चल रहा है. साथ ही, इसमें status शामिल होता है. यह किसी भी समय स्क्रीन पर एनिमेशन की मौजूदा वैल्यू को दिखाता है. यह Listenable का सबक्लास है. साथ ही, ऐनिमेशन के चालू होने पर स्थिति में बदलाव होने पर, यह अपने लिसनर को सूचना देता है.
  • AnimationController की मदद से, ऐनिमेशन बनाया जा सकता है और उसकी स्थिति को कंट्रोल किया जा सकता है. इसके forward(), reset(), stop(), और repeat() जैसे तरीकों का इस्तेमाल करके, ऐनिमेशन को कंट्रोल किया जा सकता है. इसके लिए, ऐनिमेशन के इफ़ेक्ट को तय करने की ज़रूरत नहीं होती. जैसे, स्केल, साइज़ या पोज़िशन.
  • ट्वीन का इस्तेमाल, शुरुआती और आखिरी वैल्यू के बीच की वैल्यू को इंटरपोलेट करने के लिए किया जाता है. ये किसी भी टाइप की हो सकती हैं, जैसे कि डबल, Offset या Color.
  • कर्व का इस्तेमाल, समय के साथ किसी पैरामीटर के बदलाव की दर को अडजस्ट करने के लिए किया जाता है. जब कोई ऐनिमेशन चलता है, तो ऐनिमेशन की शुरुआत या आखिर में बदलाव की दर को तेज़ या धीमा करने के लिए, ईज़िंग कर्व लागू करना आम बात है. कर्व, 0.0 और 1.0 के बीच की इनपुट वैल्यू लेते हैं और 0.0 और 1.0 के बीच की आउटपुट वैल्यू दिखाते हैं.

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

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

3026390ad413769c.gif

आपको इनके बारे में जानकारी मिलेगी...

  • ऐसा विजेट बनाएं जिसके साइज़ और रंग में ऐनिमेशन हो
  • कार्ड फ़्लिप करने का 3D इफ़ेक्ट बनाना
  • ऐनिमेशन पैकेज में मौजूद, पहले से तैयार किए गए शानदार ऐनिमेशन इफ़ेक्ट का इस्तेमाल करना
  • Android के नए वर्शन पर उपलब्ध, पीछे जाने पर झलक दिखाने वाले हाथ के जेस्चर की सुविधा जोड़ी गई

आपको क्या सीखने को मिलेगा

इस कोडलैब में, आपको इनके बारे में जानकारी मिलेगी:

  • इंप्लिसिट ऐनिमेटेड इफ़ेक्ट का इस्तेमाल करके, शानदार दिखने वाले ऐनिमेशन कैसे बनाएं. इसके लिए, ज़्यादा कोड लिखने की ज़रूरत नहीं होती.
  • AnimatedSwitcher या AnimationController जैसे पहले से बने ऐनिमेटेड विजेट का इस्तेमाल करके, अपने इफ़ेक्ट कॉन्फ़िगर करने के लिए, साफ़ तौर पर ऐनिमेट किए गए इफ़ेक्ट का इस्तेमाल कैसे करें.
  • AnimationController का इस्तेमाल करके, 3D इफ़ेक्ट दिखाने वाला अपना विजेट बनाने का तरीका.
  • animations पैकेज का इस्तेमाल करके, कम से कम सेटअप में शानदार ऐनिमेशन इफ़ेक्ट कैसे दिखाए जाएं.

आपको किन चीज़ों की ज़रूरत होगी

  • Flutter SDK
  • कोई IDE, जैसे कि VSCode या Android Studio / IntelliJ

2. Flutter डेवलपमेंट एनवायरमेंट सेट अप करना

इस लैब को पूरा करने के लिए, आपको दो सॉफ़्टवेयर की ज़रूरत होगी. पहला, Flutter SDK और दूसरा, एडिटर.

इनमें से किसी भी डिवाइस पर कोडलैब चलाया जा सकता है:

  • आपके पास कोई फ़िज़िकल Android (सातवें चरण में, अनुमान लगाकर वापस जाने की सुविधा लागू करने के लिए सुझाव दिया गया है) या iOS डिवाइस होना चाहिए. यह डिवाइस आपके कंप्यूटर से कनेक्ट होना चाहिए और डेवलपर मोड पर सेट होना चाहिए.
  • iOS सिम्युलेटर (इसके लिए, Xcode टूल इंस्टॉल करना ज़रूरी है).
  • Android Emulator (इसे Android Studio में सेट अप करना ज़रूरी है).
  • कोई ब्राउज़र (डीबग करने के लिए Chrome ज़रूरी है).
  • Windows, Linux या macOS वाला डेस्कटॉप कंप्यूटर. आपको उसी प्लैटफ़ॉर्म पर डेवलपमेंट करना होगा जहां आपको ऐप्लिकेशन डिप्लॉय करना है. इसलिए, अगर आपको Windows डेस्कटॉप ऐप्लिकेशन डेवलप करना है, तो आपको Windows पर ही डेवलप करना होगा, ताकि सही बिल्ड चेन को ऐक्सेस किया जा सके. ऑपरेटिंग सिस्टम के हिसाब से कुछ ज़रूरी शर्तें होती हैं. इनके बारे में docs.flutter.dev/desktop पर पूरी जानकारी दी गई है.

अपने इंस्टॉलेशन की पुष्टि करना

यह पुष्टि करने के लिए कि आपका Flutter SDK सही तरीके से कॉन्फ़िगर किया गया है और आपने ऊपर दी गई टारगेट प्लैटफ़ॉर्म में से कम से कम एक को इंस्टॉल किया है, Flutter Doctor टूल का इस्तेमाल करें:

$ flutter doctor

Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.24.2, on macOS 14.6.1 23G93 darwin-arm64, locale
    en)
[✓] Android toolchain - develop for Android devices
[✓] Xcode - develop for iOS and macOS
[✓] Chrome - develop for the web
[✓] Android Studio
[✓] IntelliJ IDEA Ultimate Edition
[✓] VS Code
[✓] Connected device (4 available)
[✓] Network resources

• No issues found!

3. स्टार्टर ऐप्लिकेशन चलाना

स्टार्टर ऐप्लिकेशन डाउनलोड करना

GitHub पर मौजूद flutter/samples रिपॉज़िटरी से, स्टार्ट ऐप्लिकेशन को क्लोन करने के लिए git का इस्तेमाल करें.

git clone https://github.com/flutter/codelabs.git
cd codelabs/animations/step_01/

इसके अलावा, सोर्स कोड को Zip फ़ाइल के तौर पर डाउनलोड किया जा सकता है.

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

ऐप्लिकेशन चलाने के लिए, flutter run कमांड का इस्तेमाल करें. साथ ही, टारगेट डिवाइस के बारे में बताएं. जैसे, android, ios या chrome. जिन प्लैटफ़ॉर्म पर यह सुविधा काम करती है उनकी पूरी सूची देखने के लिए, जिन प्लैटफ़ॉर्म पर यह सुविधा काम करती है पेज पर जाएं.

flutter run -d android

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

कोड के बारे में जानकारी

स्टार्टर ऐप्लिकेशन, कई विकल्पों वाला क्विज़ गेम है. इसमें मॉडल-व्यू-व्यू-मॉडल या MVVM डिज़ाइन पैटर्न का इस्तेमाल करके दो स्क्रीन बनाई गई हैं. QuestionScreen (व्यू), QuizViewModel (व्यू-मॉडल) क्लास का इस्तेमाल करके, उपयोगकर्ता से QuestionBank (मॉडल) क्लास के कई विकल्प वाले सवाल पूछता है.

  • home_screen.dart - यह स्क्रीन पर नया गेम बटन दिखाता है
  • main.dart - यह MaterialApp को Material 3 का इस्तेमाल करने और होम स्क्रीन दिखाने के लिए कॉन्फ़िगर करता है
  • model.dart - इसमें ऐप्लिकेशन में इस्तेमाल होने वाली मुख्य क्लास तय की जाती हैं
  • question_screen.dart - यह क्विज़ गेम के लिए यूज़र इंटरफ़ेस (यूआई) दिखाता है
  • view_model.dart - इसमें क्विज़ गेम की स्थिति और लॉजिक सेव होता है. इसे QuestionScreen दिखाता है

fbb1e1f7b6c91e21.png

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

4. इम्प्लिसिट ऐनिमेशन इफ़ेक्ट का इस्तेमाल करना

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

206dd8d9c1fae95.gif

बिना ऐनिमेशन वाला स्कोरबोर्ड विजेट बनाना

नीचे दिए गए कोड का इस्तेमाल करके, एक नई फ़ाइल lib/scoreboard.dart बनाएं:

lib/scoreboard.dart

import 'package:flutter/material.dart';

class Scoreboard extends StatelessWidget {
  final int score;
  final int totalQuestions;

  const Scoreboard({
    super.key,
    required this.score,
    required this.totalQuestions,
  });

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          for (var i = 0; i < totalQuestions; i++)
            Icon(
              Icons.star,
              size: 50,
              color: score < i + 1
                  ? Colors.grey.shade400
                  : Colors.yellow.shade700,
            ),
        ],
      ),
    );
  }
}

इसके बाद, StatusBar विजेट के चाइल्ड में Scoreboard विजेट जोड़ें. साथ ही, उन Text विजेट को बदलें जो पहले स्कोर और सवालों की कुल संख्या दिखाते थे. आपका एडिटर, फ़ाइल में सबसे ऊपर ज़रूरी import "scoreboard.dart" अपने-आप जोड़ देगा.

lib/question_screen.dart

class StatusBar extends StatelessWidget {
  final QuizViewModel viewModel;

  const StatusBar({required this.viewModel, super.key});

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 4,
      child: Padding(
        padding: EdgeInsets.all(8.0),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.spaceAround,
          children: [
            Scoreboard(                                        // NEW
              score: viewModel.score,                          // NEW
              totalQuestions: viewModel.totalQuestions,        // NEW
            ),
          ],
        ),
      ),
    );
  }
}

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

इम्प्लिसिट ऐनिमेशन इफ़ेक्ट का इस्तेमाल करना

AnimatedStar नाम का एक नया विजेट बनाएं. यह AnimatedScale विजेट का इस्तेमाल करके, स्टार के चालू होने पर scale की वैल्यू को 0.5 से 1.0 में बदलता है:

lib/scoreboard.dart

import 'package:flutter/material.dart';

class Scoreboard extends StatelessWidget {
  final int score;
  final int totalQuestions;

  const Scoreboard({
    super.key,
    required this.score,
    required this.totalQuestions,
  });

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.all(8.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          for (var i = 0; i < totalQuestions; i++)
            AnimatedStar(isActive: score > i),                 // Edit this line.
        ],
      ),
    );
  }
}

class AnimatedStar extends StatelessWidget {                   // Add from here...
  final bool isActive;
  final Duration _duration = const Duration(milliseconds: 1000);
  final Color _deactivatedColor = Colors.grey.shade400;
  final Color _activatedColor = Colors.yellow.shade700;

  AnimatedStar({super.key, required this.isActive});

  @override
  Widget build(BuildContext context) {
    return AnimatedScale(
      scale: isActive ? 1.0 : 0.5,
      duration: _duration,
      child: Icon(
        Icons.star,
        size: 50,
        color: isActive ? _activatedColor : _deactivatedColor,
      ),
    );
  }
}                                                              // To here.

अब जब उपयोगकर्ता किसी सवाल का सही जवाब देता है, तो AnimatedStar विजेट, इंप्लिसिट ऐनिमेशन का इस्तेमाल करके अपने साइज़ को अपडेट करता है. यहां Icon के color को ऐनिमेट नहीं किया गया है. सिर्फ़ scale को ऐनिमेट किया गया है. यह काम AnimatedScale विजेट की मदद से किया गया है.

84aec4776e70b870.gif

दो वैल्यू के बीच इंटरपोलेट करने के लिए, Tween का इस्तेमाल करना

ध्यान दें कि isActive फ़ील्ड की वैल्यू सही में बदलने के तुरंत बाद, AnimatedStar विजेट का रंग बदल जाता है.

रंग बदलने का ऐनिमेशन बनाने के लिए, AnimatedContainer विजेट (यह ImplicitlyAnimatedWidget का एक और सबक्लास है) का इस्तेमाल किया जा सकता है. ऐसा इसलिए, क्योंकि यह अपने सभी एट्रिब्यूट को अपने-आप ऐनिमेट कर सकता है. इनमें रंग भी शामिल है. माफ़ करें, हमारे विजेट को कंटेनर नहीं, बल्कि आइकॉन दिखाना होता है.

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

इसके बजाय, हम ImplicitlyAnimatedWidget की एक और सबक्लास TweenAnimationBuilder का इस्तेमाल करेंगे. यह Tween को पैरामीटर के तौर पर लेता है. ट्वीन एक क्लास है. यह दो वैल्यू (begin और end) लेती है और उनके बीच की वैल्यू का हिसाब लगाती है, ताकि ऐनिमेशन उन्हें दिखा सके. इस उदाहरण में, हम ColorTween का इस्तेमाल करेंगे. यह Tween इंटरफ़ेस की ज़रूरी शर्तों को पूरा करता है. इससे हम ऐनिमेशन इफ़ेक्ट बना पाएंगे.

Icon विजेट चुनें. इसके बाद, अपने आईडीई में "बिल्डर के साथ रैप करें" क्विक ऐक्शन का इस्तेमाल करें. साथ ही, नाम को बदलकर TweenAnimationBuilder करें. इसके बाद, अवधि और ColorTween की जानकारी दें.

lib/scoreboard.dart

class AnimatedStar extends StatelessWidget {
  final bool isActive;
  final Duration _duration = const Duration(milliseconds: 1000);
  final Color _deactivatedColor = Colors.grey.shade400;
  final Color _activatedColor = Colors.yellow.shade700;

  AnimatedStar({super.key, required this.isActive});

  @override
  Widget build(BuildContext context) {
    return AnimatedScale(
      scale: isActive ? 1.0 : 0.5,
      duration: _duration,
      child: TweenAnimationBuilder(                            // Add from here...
        duration: _duration,
        tween: ColorTween(
          begin: _deactivatedColor,
          end: isActive ? _activatedColor : _deactivatedColor,
        ),
        builder: (context, value, child) {                     // To here.
          return Icon(Icons.star, size: 50, color: value);     // And modify this line.
        },
      ),
    );
  }
}

अब, नए ऐनिमेशन को देखने के लिए, ऐप्लिकेशन को हॉट-रीलोड करें.

8b0911f4af299a60.gif

ध्यान दें कि isActive पैरामीटर की वैल्यू के आधार पर, हमारे ColorTween की end वैल्यू बदल जाती है. ऐसा इसलिए होता है, क्योंकि TweenAnimationBuilder की वैल्यू में बदलाव होने पर, TweenAnimationBuilder का ऐनिमेशन फिर से शुरू हो जाता है.Tween.end ऐसा होने पर, नया ऐनिमेशन, मौजूदा ऐनिमेशन वैल्यू से नई आखिरी वैल्यू तक चलता है. इससे आपको किसी भी समय (ऐनिमेशन के दौरान भी) रंग बदलने की सुविधा मिलती है. साथ ही, बीच की सही वैल्यू के साथ ऐनिमेशन का बेहतर इफ़ेक्ट दिखता है.

कर्व लागू करना

ये दोनों ऐनिमेशन इफ़ेक्ट एक ही स्पीड से चलते हैं. हालांकि, ऐनिमेशन की स्पीड कम या ज़्यादा होने पर, वे ज़्यादा दिलचस्प और जानकारी देने वाले लगते हैं.

Curve, ईज़िंग फ़ंक्शन लागू करता है. यह फ़ंक्शन, समय के साथ किसी पैरामीटर के बदलाव की दर तय करता है. Flutter, Curves क्लास में पहले से बनी हुई ईज़िंग कर्व का कलेक्शन उपलब्ध कराता है. जैसे, easeIn या easeOut.

5dabe68d1210b8a1.gif

3a9e7490c594279a.gif

इन डायग्राम (Curves एपीआई के दस्तावेज़ वाले पेज पर उपलब्ध) से पता चलता है कि कर्व कैसे काम करते हैं. कर्व, 0.0 और 1.0 के बीच की इनपुट वैल्यू (x ऐक्सिस पर दिखाई गई) को 0.0 और 1.0 के बीच की आउटपुट वैल्यू (y ऐक्सिस पर दिखाई गई) में बदलते हैं. इन डायग्राम में, यह भी दिखाया गया है कि ईज़िंग कर्व का इस्तेमाल करने पर, अलग-अलग ऐनिमेशन इफ़ेक्ट कैसे दिखते हैं.

AnimatedStar में _curve नाम का एक नया फ़ील्ड बनाएं और इसे AnimatedScale और TweenAnimationBuilder विजेट में पैरामीटर के तौर पर पास करें.

lib/scoreboard.dart

class AnimatedStar extends StatelessWidget {
  final bool isActive;
  final Duration _duration = const Duration(milliseconds: 1000);
  final Color _deactivatedColor = Colors.grey.shade400;
  final Color _activatedColor = Colors.yellow.shade700;
  final Curve _curve = Curves.elasticOut;                       // NEW

  AnimatedStar({super.key, required this.isActive});

  @override
  Widget build(BuildContext context) {
    return AnimatedScale(
      scale: isActive ? 1.0 : 0.5,
      curve: _curve,                                           // NEW
      duration: _duration,
      child: TweenAnimationBuilder(
        curve: _curve,                                         // NEW
        duration: _duration,
        tween: ColorTween(
          begin: _deactivatedColor,
          end: isActive ? _activatedColor : _deactivatedColor,
        ),
        builder: (context, value, child) {
          return Icon(Icons.star, size: 50, color: value);
        },
      ),
    );
  }
}

इस उदाहरण में, elasticOut कर्व से स्प्रिंग वाला इफ़ेक्ट मिलता है. यह इफ़ेक्ट, स्प्रिंग की तरह हिलने से शुरू होता है और आखिर में स्थिर हो जाता है.

8f84142bff312373.gif

AnimatedSize और TweenAnimationBuilder पर इस कर्व को लागू करने के लिए, ऐप्लिकेशन को हॉट रिलोड करें.

206dd8d9c1fae95.gif

धीमे ऐनिमेशन चालू करने के लिए, DevTools का इस्तेमाल करना

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

DevTools खोलने के लिए, पक्का करें कि ऐप्लिकेशन डीबग मोड में चल रहा हो. इसके बाद, VSCode में डीबग टूलबार में जाकर विजेट इंस्पेक्टर को चुनें या IntelliJ / Android Studio में डीबग टूल विंडो में जाकर Flutter DevTools खोलें बटन को चुनें.

3ce33dc01d096b14.png

363ae0fbcd0c2395.png

विजेट इंस्पेक्टर खुलने के बाद, टूलबार में मौजूद ऐनिमेशन की स्पीड कम करें बटन पर क्लिक करें.

adea0a16d01127ad.png

5. ऐनिमेशन के साफ़ तौर पर दिखने वाले इफ़ेक्ट इस्तेमाल करना

इम्प्लिसिट ऐनिमेशन की तरह, एक्सप्लिसिट ऐनिमेशन भी पहले से बने ऐनिमेशन इफ़ेक्ट होते हैं. हालांकि, ये टारगेट वैल्यू लेने के बजाय, Animation ऑब्जेक्ट को पैरामीटर के तौर पर लेते हैं. इसलिए, इनका इस्तेमाल उन स्थितियों में किया जा सकता है जहां ऐनिमेशन को नेविगेशन ट्रांज़िशन, AnimatedSwitcher या AnimationController से पहले ही तय कर दिया गया हो.

ऐनिमेशन के किसी इफ़ेक्ट का इस्तेमाल करना

एनीमेशन के साफ़ तौर पर दिखने वाले इफ़ेक्ट का इस्तेमाल करने के लिए, Card विजेट को AnimatedSwitcher में रैप करें.

lib/question_screen.dart

class QuestionCard extends StatelessWidget {
  final String? question;

  const QuestionCard({required this.question, super.key});

  @override
  Widget build(BuildContext context) {
    return AnimatedSwitcher(                                 // NEW
      duration: const Duration(milliseconds: 300),           // NEW
      child: Card(
        key: ValueKey(question),
        elevation: 4,
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Text(
            question ?? '',
            style: Theme.of(context).textTheme.displaySmall,
          ),
        ),
      ),                                                     // NEW
    );
  }
}

AnimatedSwitcher डिफ़ॉल्ट रूप से क्रॉस-फ़ेड इफ़ेक्ट का इस्तेमाल करता है. हालांकि, transitionBuilder पैरामीटर का इस्तेमाल करके इसे बदला जा सकता है. ट्रांज़िशन बिल्डर, चाइल्ड विजेट और Animation ऑब्जेक्ट उपलब्ध कराता है. चाइल्ड विजेट को AnimatedSwitcher में पास किया गया था. एक्सप्लिसिट ऐनिमेशन का इस्तेमाल करने का यह एक शानदार मौका है.

इस कोडलैब के लिए, हम जिस पहले ऐनिमेशन का इस्तेमाल करेंगे वह SlideTransition है. यह एक Animation<Offset> लेता है, जो आने वाले और जाने वाले विजेट के बीच की शुरुआत और खत्म होने की ऑफ़सेट को तय करता है.

ट्वीन में हेल्पर फ़ंक्शन, animate() होता है. यह किसी भी Animation को, ट्वीट किए गए Animation में बदल देता है. इसका मतलब है कि Tween का इस्तेमाल, AnimatedSwitcher से मिले Animation को Animation में बदलने के लिए किया जा सकता है, ताकि इसे SlideTransition विजेट को दिया जा सके.

lib/question_screen.dart

class QuestionCard extends StatelessWidget {
  final String? question;

  const QuestionCard({required this.question, super.key});

  @override
  Widget build(BuildContext context) {
    return AnimatedSwitcher(
      transitionBuilder: (child, animation) {               // Add from here...
        final curveAnimation = CurveTween(
          curve: Curves.easeInCubic,
        ).animate(animation);
        final offsetAnimation = Tween<Offset>(
          begin: Offset(-0.1, 0.0),
          end: Offset.zero,
        ).animate(curveAnimation);
        return SlideTransition(position: offsetAnimation, child: child);
      },                                                    // To here.
      duration: const Duration(milliseconds: 300),
      child: Card(
        key: ValueKey(question),
        elevation: 4,
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Text(
            question ?? '',
            style: Theme.of(context).textTheme.displaySmall,
          ),
        ),
      ),
    );
  }
}

ध्यान दें कि यह Tween.animate का इस्तेमाल करके, Animation पर Curve लागू करता है. इसके बाद, इसे Tween से Tween में बदलता है. Tween की वैल्यू 0.0 से 1.0 तक होती है, जबकि Tween की वैल्यू x-ऐक्सिस पर -0.1 से 0.0 तक होती है.

इसके अलावा, Animation क्लास में एक drive() फ़ंक्शन होता है. यह किसी भी Tween (या Animatable) को लेता है और उसे नए Animation में बदल देता है. इससे ट्विन को "चेन" किया जा सकता है. इससे कोड ज़्यादा छोटा हो जाता है:

lib/question_screen.dart

transitionBuilder: (child, animation) {
  var offsetAnimation = animation
      .drive(CurveTween(curve: Curves.easeInCubic))
      .drive(Tween<Offset>(begin: Offset(-0.1, 0.0), end: Offset.zero));
  return SlideTransition(position: offsetAnimation, child: child);
},

एक्सप्लिसिट ऐनिमेशन का एक और फ़ायदा यह है कि इन्हें एक साथ कंपोज़ किया जा सकता है. एक और एक्सप्लिसिट ऐनिमेशन जोड़ें, FadeTransition जो SlideTransition विजेट को रैप करके, उसी घुमावदार ऐनिमेशन का इस्तेमाल करता है.

lib/question_screen.dart

return AnimatedSwitcher(
  transitionBuilder: (child, animation) {
    final curveAnimation = CurveTween(
      curve: Curves.easeInCubic,
    ).animate(animation);
    final offsetAnimation = Tween<Offset>(
      begin: Offset(-0.1, 0.0),
      end: Offset.zero,
    ).animate(curveAnimation);
    final fadeInAnimation = curveAnimation;                            // NEW
    return FadeTransition(                                             // NEW
      opacity: fadeInAnimation,                                        // NEW
      child: SlideTransition(position: offsetAnimation, child: child), // NEW
    );                                                                 // NEW
  },

layoutBuilder को पसंद के मुताबिक बनाना

आपको AnimationSwitcher में कोई छोटी-मोटी समस्या दिख सकती है. जब QuestionCard किसी नए सवाल पर स्विच करता है, तो ऐनिमेशन के दौरान वह सवाल, उपलब्ध जगह के बीच में दिखता है. हालांकि, ऐनिमेशन बंद होने पर, विजेट स्क्रीन के सबसे ऊपर दिखता है. इस वजह से, ऐनिमेशन ठीक से नहीं चलता. ऐसा इसलिए होता है, क्योंकि ऐनिमेशन के दौरान सवाल वाले कार्ड की पोज़िशन, ऐनिमेशन खत्म होने के बाद की पोज़िशन से मेल नहीं खाती.

d77de181bdde58f7.gif

इसे ठीक करने के लिए, AnimatedSwitcher में layoutBuilder पैरामीटर भी होता है. इसका इस्तेमाल लेआउट तय करने के लिए किया जा सकता है. कार्ड को स्क्रीन पर सबसे ऊपर अलाइन करने के लिए, लेआउट बिल्डर को कॉन्फ़िगर करने के लिए इस फ़ंक्शन का इस्तेमाल करें:

lib/question_screen.dart

@override
Widget build(BuildContext context) {
  return AnimatedSwitcher(
    layoutBuilder: (currentChild, previousChildren) {
      return Stack(
        alignment: Alignment.topCenter,
        children: <Widget>[
          ...previousChildren,
          if (currentChild != null) currentChild,
        ],
      );
    },

यह कोड, AnimatedSwitcher क्लास के defaultLayoutBuilder का बदला हुआ वर्शन है. हालांकि, इसमें Alignment.center के बजाय Alignment.topCenter का इस्तेमाल किया गया है.

खास जानकारी

  • एक्सप्लिसिट ऐनिमेशन, ऐनिमेशन इफ़ेक्ट होते हैं. ये Animation ऑब्जेक्ट लेते हैं. इसके उलट, ImplicitlyAnimatedWidgets, टारगेट value और duration लेते हैं
  • Animation क्लास, ऐनिमेशन को चालू करने के लिए इस्तेमाल की जाती है. हालांकि, इससे कोई खास इफ़ेक्ट तय नहीं होता.
  • किसी ऐनिमेशन पर Tweens और Curves (CurveTween का इस्तेमाल करके) लागू करने के लिए, Tween().animate या Animation.drive() का इस्तेमाल करें.
  • AnimatedSwitcher के layoutBuilder पैरामीटर का इस्तेमाल करके, यह तय करें कि चाइल्ड आइटम किस तरह से लेआउट किए जाएंगे.

6. ऐनिमेशन की स्थिति को कंट्रोल करना

अब तक, हर ऐनिमेशन को फ़्रेमवर्क ने अपने-आप चलाया है. इंप्लिसिट ऐनिमेशन अपने-आप चलते हैं. वहीं, एक्सप्लिसिट ऐनिमेशन इफ़ेक्ट को सही तरीके से काम करने के लिए, Animation की ज़रूरत होती है. इस सेक्शन में, AnimationController का इस्तेमाल करके अपने Animation ऑब्जेक्ट बनाने का तरीका बताया गया है. साथ ही, Tween को एक साथ जोड़ने के लिए, TweenSequence का इस्तेमाल करने का तरीका बताया गया है.

AnimationController का इस्तेमाल करके ऐनिमेशन चलाना

AnimationController का इस्तेमाल करके ऐनिमेशन बनाने के लिए, आपको यह तरीका अपनाना होगा:

  1. StatefulWidget बनाएं
  2. AnimationController को Ticker देने के लिए, अपनी State क्लास में SingleTickerProviderStateMixin मिक्सइन का इस्तेमाल करें
  3. initState लाइफ़साइकल के तरीके में AnimationController को शुरू करें. साथ ही, vsync (TickerProvider) पैरामीटर को मौजूदा State ऑब्जेक्ट दें.
  4. पक्का करें कि जब भी AnimationController अपने लिसनर को सूचना दे, तब आपका विजेट फिर से बन जाए. इसके लिए, AnimatedBuilder का इस्तेमाल करें या listen() और setState को मैन्युअल तरीके से कॉल करें.

एक नई फ़ाइल flip_effect.dart बनाएं और इसमें यह कोड कॉपी करके चिपकाएं:

lib/flip_effect.dart

import 'dart:math' as math;

import 'package:flutter/widgets.dart';

class CardFlipEffect extends StatefulWidget {
  final Widget child;
  final Duration duration;

  const CardFlipEffect({
    super.key,
    required this.child,
    required this.duration,
  });

  @override
  State<CardFlipEffect> createState() => _CardFlipEffectState();
}

class _CardFlipEffectState extends State<CardFlipEffect>
    with SingleTickerProviderStateMixin {
  late final AnimationController _animationController;
  Widget? _previousChild;

  @override
  void initState() {
    super.initState();

    _animationController = AnimationController(
      vsync: this,
      duration: widget.duration,
    );

    _animationController.addListener(() {
      if (_animationController.value == 1) {
        _animationController.reset();
      }
    });
  }

  @override
  void didUpdateWidget(covariant CardFlipEffect oldWidget) {
    super.didUpdateWidget(oldWidget);

    if (widget.child.key != oldWidget.child.key) {
      _handleChildChanged(widget.child, oldWidget.child);
    }
  }

  void _handleChildChanged(Widget newChild, Widget previousChild) {
    _previousChild = previousChild;
    _animationController.forward();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _animationController,
      builder: (context, child) {
        return Transform(
          alignment: Alignment.center,
          transform: Matrix4.identity()
            ..rotateX(_animationController.value * math.pi),
          child: _animationController.isAnimating
              ? _animationController.value < 0.5
                    ? _previousChild
                    : Transform.flip(flipY: true, child: child)
              : child,
        );
      },
      child: widget.child,
    );
  }
}

यह क्लास, AnimationController को सेट अप करती है. साथ ही, जब भी फ़्रेमवर्क didUpdateWidget को कॉल करके यह सूचना देता है कि विजेट कॉन्फ़िगरेशन बदल गया है और हो सकता है कि कोई नया चाइल्ड विजेट हो, तब यह क्लास ऐनिमेशन को फिर से चलाती है.

AnimatedBuilder यह पक्का करता है कि जब भी AnimationController अपने लिसनर को सूचना दे, तब विजेट ट्री को फिर से बनाया जाए. साथ ही, Transform विजेट का इस्तेमाल, कार्ड को फ़्लिप किए जाने का सिम्युलेट करने के लिए, 3D रोटेशन इफ़ेक्ट लागू करने के लिए किया जाता है.

इस विजेट का इस्तेमाल करने के लिए, हर जवाब कार्ड को CardFlipEffect विजेट में रैप करें. Card विजेट में key ज़रूर दें:

lib/question_screen.dart

@override
Widget build(BuildContext context) {
  return GridView.count(
    shrinkWrap: true,
    crossAxisCount: 2,
    childAspectRatio: 5 / 2,
    children: List.generate(answers.length, (index) {
      var color = Theme.of(context).colorScheme.primaryContainer;
      if (correctAnswer == index) {
        color = Theme.of(context).colorScheme.tertiaryContainer;
      }
      return CardFlipEffect(                                    // NEW
        duration: const Duration(milliseconds: 300),            // NEW
        child: Card.filled(                                     // NEW
          key: ValueKey(answers[index]),                        // NEW
          color: color,
          elevation: 2,
          margin: EdgeInsets.all(8),
          clipBehavior: Clip.hardEdge,
          child: InkWell(
            onTap: () => onTapped(index),
            child: Padding(
              padding: EdgeInsets.all(16.0),
              child: Center(
                child: Text(
                  answers.length > index ? answers[index] : '',
                  style: Theme.of(context).textTheme.titleMedium,
                  overflow: TextOverflow.clip,
                ),
              ),
            ),
          ),
        ),                                                      // NEW
      );
    }),
  );
}

अब ऐप्लिकेशन को हॉट-रीलोड करें, ताकि CardFlipEffect विजेट का इस्तेमाल करके जवाब वाले कार्ड को पलटा जा सके.

5455def725b866f6.gif

आपको लग सकता है कि यह क्लास, ऐनिमेशन के इफ़ेक्ट की तरह दिखती है. दरअसल, अक्सर अपने वर्शन को लागू करने के लिए, AnimatedWidget क्लास को सीधे तौर पर बढ़ाना बेहतर होता है. माफ़ करें, इस क्लास को अपने State में पिछले विजेट को सेव करना होता है. इसलिए, इसे StatefulWidget का इस्तेमाल करना पड़ता है. अपने हिसाब से ऐनिमेशन इफ़ेक्ट बनाने के बारे में ज़्यादा जानने के लिए, AnimatedWidget के लिए एपीआई दस्तावेज़ देखें.

TweenSequence का इस्तेमाल करके, देरी जोड़ना

इस सेक्शन में, आपको CardFlipEffect विजेट में देरी करनी होगी, ताकि हर कार्ड एक-एक करके फ़्लिप हो. शुरू करने के लिए, delayAmount नाम का नया फ़ील्ड जोड़ें.

lib/flip_effect.dart

class CardFlipEffect extends StatefulWidget {
  final Widget child;
  final Duration duration;
  final double delayAmount;                      // NEW

  const CardFlipEffect({
    super.key,
    required this.child,
    required this.duration,
    required this.delayAmount,                   // NEW
  });

  @override
  State<CardFlipEffect> createState() => _CardFlipEffectState();
}

इसके बाद, delayAmount को AnswerCards बिल्ड मेथड में जोड़ें.

lib/question_screen.dart

@override
Widget build(BuildContext context) {
  return GridView.count(
    shrinkWrap: true,
    crossAxisCount: 2,
    childAspectRatio: 5 / 2,
    children: List.generate(answers.length, (index) {
      var color = Theme.of(context).colorScheme.primaryContainer;
      if (correctAnswer == index) {
        color = Theme.of(context).colorScheme.tertiaryContainer;
      }
      return CardFlipEffect(
        delayAmount: index.toDouble() / 2,                     // NEW
        duration: const Duration(milliseconds: 300),
        child: Card.filled(
          key: ValueKey(answers[index]),

इसके बाद, _CardFlipEffectState में, एक नया Animation बनाएं. इसमें TweenSequence का इस्तेमाल करके, देरी लागू करें. ध्यान दें कि इसमें dart:async लाइब्रेरी की किसी भी सुविधा का इस्तेमाल नहीं किया गया है. जैसे, Future.delayed. ऐसा इसलिए है, क्योंकि देरी ऐनिमेशन का हिस्सा है. साथ ही, जब विजेट AnimationController का इस्तेमाल करता है, तब यह देरी को साफ़ तौर पर कंट्रोल नहीं करता. इससे DevTools में धीमे ऐनिमेशन चालू करने पर, ऐनिमेशन इफ़ेक्ट को डीबग करना आसान हो जाता है. ऐसा इसलिए, क्योंकि यह एक ही TickerProvider का इस्तेमाल करता है.

TweenSequence का इस्तेमाल करने के लिए, दो TweenSequenceItem बनाएं. इनमें से एक में ConstantTween होना चाहिए, जो ऐनिमेशन को कुछ समय के लिए 0 पर रखता है. साथ ही, एक सामान्य Tween होना चाहिए, जो 0.0 से 1.0 तक जाता है.

lib/flip_effect.dart

class _CardFlipEffectState extends State<CardFlipEffect>
    with SingleTickerProviderStateMixin {
  late final AnimationController _animationController;
  Widget? _previousChild;
  late final Animation<double> _animationWithDelay;            // NEW

  @override
  void initState() {
    super.initState();

    _animationController = AnimationController(
      vsync: this,
      duration: widget.duration * (widget.delayAmount + 1),
    );

    _animationController.addListener(() {
      if (_animationController.value == 1) {
        _animationController.reset();
      }
    });

    _animationWithDelay = TweenSequence<double>([              // Add from here...
      if (widget.delayAmount > 0)
        TweenSequenceItem(
          tween: ConstantTween<double>(0.0),
          weight: widget.delayAmount,
        ),
      TweenSequenceItem(tween: Tween(begin: 0.0, end: 1.0), weight: 1.0),
    ]).animate(_animationController);                          // To here.
  }

आखिर में, AnimationController के ऐनिमेशन को build तरीके में, देर से शुरू होने वाले नए ऐनिमेशन से बदलें.

lib/flip_effect.dart

@override
Widget build(BuildContext context) {
  return AnimatedBuilder(
    animation: _animationWithDelay,                            // Modify this line
    builder: (context, child) {
      return Transform(
        alignment: Alignment.center,
        transform: Matrix4.identity()
          ..rotateX(_animationWithDelay.value * math.pi),      // And this line
        child: _animationController.isAnimating
            ? _animationWithDelay.value < 0.5                  // And this one.
                  ? _previousChild
                  : Transform.flip(flipY: true, child: child)
            : child,
      );
    },
    child: widget.child,
  );
}

अब ऐप्लिकेशन को हॉट रिलोड करें और देखें कि कार्ड एक-एक करके फ़्लिप हो रहे हैं. चैलेंज के लिए, Transform विजेट से मिले 3D इफ़ेक्ट के नज़ारे को बदलने का एक्सपेरिमेंट करें.

28b5291de9b3f55f.gif

7. नेविगेशन ट्रांज़िशन को पसंद के मुताबिक बनाना

अब तक हमने देखा कि एक स्क्रीन पर इफ़ेक्ट को कैसे पसंद के मुताबिक बनाया जाता है. हालांकि, ऐनिमेशन का इस्तेमाल करने का एक और तरीका है. इसका इस्तेमाल स्क्रीन के बीच ट्रांज़िशन के लिए किया जाता है. इस सेक्शन में, आपको स्क्रीन ट्रांज़िशन पर ऐनिमेशन इफ़ेक्ट लागू करने का तरीका बताया जाएगा. इसके लिए, बिल्ट-इन ऐनिमेशन इफ़ेक्ट और pub.dev पर animations पैकेज से मिले फ़ैंसी प्रीबिल्ट ऐनिमेशन इफ़ेक्ट का इस्तेमाल किया जाता है.

नेविगेशन ट्रांज़िशन को ऐनिमेट करना

PageRouteBuilder क्लास एक Route है. इसकी मदद से, ट्रांज़िशन ऐनिमेशन को अपनी पसंद के मुताबिक बनाया जा सकता है. इससे आपको इसके transitionBuilder कॉलबैक को बदलने की सुविधा मिलती है. यह दो ऐनिमेशन ऑब्जेक्ट उपलब्ध कराता है. ये ऑब्जेक्ट, Navigator की ओर से चलाए जाने वाले इनकमिंग और आउटगोइंग ऐनिमेशन को दिखाते हैं.

ट्रांज़िशन ऐनिमेशन को पसंद के मुताबिक बनाने के लिए, MaterialPageRoute को PageRouteBuilder से बदलें. साथ ही, जब उपयोगकर्ता HomeScreen से QuestionScreen पर जाता है, तब ट्रांज़िशन ऐनिमेशन को पसंद के मुताबिक बनाएं. नई स्क्रीन को पिछली स्क्रीन के ऊपर फ़ेड इन करने के लिए, FadeTransition (साफ़ तौर पर ऐनिमेशन वाला विजेट) का इस्तेमाल करें.

lib/home_screen.dart

ElevatedButton(
  onPressed: () {
    // Show the question screen to start the game
    Navigator.push(
      context,
      PageRouteBuilder(                                         // Add from here...
        pageBuilder: (context, animation, secondaryAnimation) {
          return const QuestionScreen();
        },
        transitionsBuilder:
            (context, animation, secondaryAnimation, child) {
              return FadeTransition(
                opacity: animation,
                child: child,
              );
            },
      ),                                                        // To here.
    );
  },
  child: Text('New Game'),
),

ऐनिमेशन पैकेज में, पहले से बने हुए शानदार ऐनिमेशन इफ़ेक्ट मिलते हैं. जैसे, FadeThroughTransition. ऐनिमेशन पैकेज इंपोर्ट करें और FadeTransition को FadeThroughTransition विजेट से बदलें:

lib/home_screen.dart

import 'package;animations/animations.dart';

ElevatedButton(
  onPressed: () {
    // Show the question screen to start the game
    Navigator.push(
      context,
      PageRouteBuilder(
        pageBuilder: (context, animation, secondaryAnimation) {
          return const QuestionScreen();
        },
        transitionsBuilder:
            (context, animation, secondaryAnimation, child) {
              return FadeThroughTransition(                     // Add from here...
                animation: animation,
                secondaryAnimation: secondaryAnimation,
                child: child,
              );                                                // To here.
            },
      ),
    );
  },
  child: Text('New Game'),
),

प्रिडिक्टिव बैक ऐनिमेशन को पसंद के मुताबिक बनाना

1c0558ffa3b76439.gif

अनुमान लगाकर पीछे जाने की सुविधा, Android की एक नई सुविधा है. इसकी मदद से उपयोगकर्ता, नेविगेट करने से पहले मौजूदा रूट या ऐप्लिकेशन के पीछे की जानकारी देख सकता है. पीक ऐनिमेशन, उपयोगकर्ता की उंगली की जगह के हिसाब से चलता है. ऐसा तब होता है, जब उपयोगकर्ता स्क्रीन पर वापस खींचता है.

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

Flutter, Flutter ऐप्लिकेशन में एक से दूसरी स्क्रीन पर जाने के दौरान, अनुमानित तौर पर वापस जाने की सुविधा भी देता है. PageTransitionsBuilder नाम का एक खास विजेट, सिस्टम के अनुमानित तौर पर वापस जाने के जेस्चर को सुनता है. साथ ही, जेस्चर की प्रोग्रेस के साथ पेज ट्रांज़िशन को मैनेज करता है.PredictiveBackPageTransitionsBuilder

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

अपने ऐप्लिकेशन के ThemeData कॉन्फ़िगरेशन में, PageTransitionsTheme को कॉन्फ़िगर करें, ताकि Android पर PredictiveBack का इस्तेमाल किया जा सके. साथ ही, अन्य प्लैटफ़ॉर्म पर ऐनिमेशन पैकेज से फ़ेड-थ्रू ट्रांज़िशन इफ़ेक्ट का इस्तेमाल किया जा सके:

lib/main.dart

import 'package:animations/animations.dart';                                 // NEW
import 'package:flutter/material.dart';

import 'home_screen.dart';

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

class MainApp extends StatelessWidget {
  const MainApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        pageTransitionsTheme: PageTransitionsTheme(
          builders: {
            TargetPlatform.android: PredictiveBackPageTransitionsBuilder(),  // NEW
            TargetPlatform.iOS: FadeThroughPageTransitionsBuilder(),         // NEW
            TargetPlatform.macOS: FadeThroughPageTransitionsBuilder(),       // NEW
            TargetPlatform.windows: FadeThroughPageTransitionsBuilder(),     // NEW
            TargetPlatform.linux: FadeThroughPageTransitionsBuilder(),       // NEW
          },
        ),
      ),
      home: HomeScreen(),
    );
  }
}

अब Navigator.push() कॉल को MaterialPageRoute में बदला जा सकता है.

lib/home_screen.dart

ElevatedButton(
  onPressed: () {
    // Show the question screen to start the game
    Navigator.push(
      context,
      MaterialPageRoute(                                        // Add from here...
        builder: (context) {
          return const QuestionScreen();
        },
      ),                                                        // To here.
    );
  },
  child: Text('New Game'),
),

मौजूदा सवाल को बदलने के लिए, FadeThroughTransition का इस्तेमाल करें

AnimatedSwitcher विजेट, बिल्डर कॉलबैक में सिर्फ़ एक Animation उपलब्ध कराता है. इस समस्या को हल करने के लिए, animations पैकेज में PageTransitionSwitcher उपलब्ध है.

lib/question_screen.dart

class QuestionCard extends StatelessWidget {
  final String? question;

  const QuestionCard({required this.question, super.key});

  @override
  Widget build(BuildContext context) {
    return PageTransitionSwitcher(                              // Add from here...
      layoutBuilder: (entries) {
        return Stack(alignment: Alignment.topCenter, children: entries);
      },
      transitionBuilder: (child, animation, secondaryAnimation) {
        return FadeThroughTransition(
          animation: animation,
          secondaryAnimation: secondaryAnimation,
          child: child,
        );
      },                                                        // To here.
      duration: const Duration(milliseconds: 300),
      child: Card(
        key: ValueKey(question),
        elevation: 4,
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Text(
            question ?? '',
            style: Theme.of(context).textTheme.displaySmall,
          ),
        ),
      ),
    );
  }
}

OpenContainer का इस्तेमाल करना

77358e5776eb104c.png

animations पैकेज में मौजूद OpenContainer विजेट, कंटेनर ट्रांसफ़ॉर्म ऐनिमेशन इफ़ेक्ट देता है. यह दो विजेट के बीच विज़ुअल कनेक्शन बनाने के लिए फैलता है.

closedBuilder से मिला विजेट शुरू में दिखता है. इसके बाद, जब कंटेनर पर टैप किया जाता है या openContainer कॉलबैक को कॉल किया जाता है, तब यह openBuilder से मिले विजेट में बदल जाता है.

openContainer कॉलबैक को व्यू-मॉडल से कनेक्ट करने के लिए, QuestionCard विजेट में एक नया पास जोड़ें और एक ऐसा कॉलबैक सेव करें जिसका इस्तेमाल "गेम खत्म" स्क्रीन दिखाने के लिए किया जाएगा:viewModel

lib/question_screen.dart

class QuestionScreen extends StatefulWidget {
  const QuestionScreen({super.key});

  @override
  State<QuestionScreen> createState() => _QuestionScreenState();
}

class _QuestionScreenState extends State<QuestionScreen> {
  late final QuizViewModel viewModel = QuizViewModel(
    onGameOver: _handleGameOver,
  );
  VoidCallback? _showGameOverScreen;                                    // NEW

  @override
  Widget build(BuildContext context) {
    return ListenableBuilder(
      listenable: viewModel,
      builder: (context, child) {
        return Scaffold(
          appBar: AppBar(
            actions: [
              TextButton(
                onPressed:
                    viewModel.hasNextQuestion && viewModel.didAnswerQuestion
                    ? () {
                        viewModel.getNextQuestion();
                      }
                    : null,
                child: const Text('Next'),
              ),
            ],
          ),
          body: Center(
            child: Column(
              children: [
                QuestionCard(                                           // NEW
                  onChangeOpenContainer: _handleChangeOpenContainer,    // NEW
                  question: viewModel.currentQuestion?.question,        // NEW
                  viewModel: viewModel,                                 // NEW
                ),                                                      // NEW
                Spacer(),
                AnswerCards(
                  onTapped: (index) {
                    viewModel.checkAnswer(index);
                  },
                  answers: viewModel.currentQuestion?.possibleAnswers ?? [],
                  correctAnswer: viewModel.didAnswerQuestion
                      ? viewModel.currentQuestion?.correctAnswer
                      : null,
                ),
                StatusBar(viewModel: viewModel),
              ],
            ),
          ),
        );
      },
    );
  }

  void _handleChangeOpenContainer(VoidCallback openContainer) {        // NEW
    _showGameOverScreen = openContainer;                               // NEW
  }                                                                    // NEW

  void _handleGameOver() {                                             // NEW
    if (_showGameOverScreen != null) {                                 // NEW
      _showGameOverScreen!();                                          // NEW
    }                                                                  // NEW
  }                                                                    // NEW
}

नया विजेट, GameOverScreen जोड़ें:

lib/question_screen.dart

class GameOverScreen extends StatelessWidget {
  final QuizViewModel viewModel;
  const GameOverScreen({required this.viewModel, super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(automaticallyImplyLeading: false),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Scoreboard(
              score: viewModel.score,
              totalQuestions: viewModel.totalQuestions,
            ),
            Text('You Win!', style: Theme.of(context).textTheme.displayLarge),
            Text(
              'Score: ${viewModel.score} / ${viewModel.totalQuestions}',
              style: Theme.of(context).textTheme.displaySmall,
            ),
            ElevatedButton(
              child: Text('OK'),
              onPressed: () {
                Navigator.popUntil(context, (route) => route.isFirst);
              },
            ),
          ],
        ),
      ),
    );
  }
}

QuestionCard विजेट में, Card को animations पैकेज के OpenContainer विजेट से बदलें. साथ ही, viewModel और ओपन कंटेनर कॉलबैक के लिए दो नए फ़ील्ड जोड़ें:

lib/question_screen.dart

class QuestionCard extends StatelessWidget {
  final String? question;

  const QuestionCard({
    required this.onChangeOpenContainer,
    required this.question,
    required this.viewModel,
    super.key,
  });

  final ValueChanged<VoidCallback> onChangeOpenContainer;
  final QuizViewModel viewModel;

  static const _backgroundColor = Color(0xfff2f3fa);

  @override
  Widget build(BuildContext context) {
    return PageTransitionSwitcher(
      duration: const Duration(milliseconds: 200),
      transitionBuilder: (child, animation, secondaryAnimation) {
        return FadeThroughTransition(
          animation: animation,
          secondaryAnimation: secondaryAnimation,
          child: child,
        );
      },
      child: OpenContainer(                                         // NEW
        key: ValueKey(question),                                    // NEW
        tappable: false,                                            // NEW
        closedColor: _backgroundColor,                              // NEW
        closedShape: const RoundedRectangleBorder(                  // NEW
          borderRadius: BorderRadius.all(Radius.circular(12.0)),    // NEW
        ),                                                          // NEW
        closedElevation: 4,                                         // NEW
        closedBuilder: (context, openContainer) {                   // NEW
          onChangeOpenContainer(openContainer);                     // NEW
          return ColoredBox(                                        // NEW
            color: _backgroundColor,                                // NEW
            child: Padding(                                         // NEW
              padding: const EdgeInsets.all(16.0),                  // NEW
              child: Text(
                question ?? '',
                style: Theme.of(context).textTheme.displaySmall,
              ),
            ),
          );
        },
        openBuilder: (context, closeContainer) {                    // NEW
          return GameOverScreen(viewModel: viewModel);              // NEW
        },                                                          // NEW
      ),
    );
  }
}

4120f9395857d218.gif

8. बधाई हो

बधाई हो, आपने Flutter ऐप्लिकेशन में ऐनिमेशन इफ़ेक्ट जोड़ लिए हैं. साथ ही, आपने Flutter के ऐनिमेशन सिस्टम के मुख्य कॉम्पोनेंट के बारे में जान लिया है. खास तौर पर, आपने इन विषयों के बारे में सीखा:

  • ImplicitlyAnimatedWidget को इस्तेमाल करने का तरीका
  • ExplicitlyAnimatedWidget को इस्तेमाल करने का तरीका
  • किसी ऐनिमेशन में Curves और Tweens कैसे लागू करें
  • AnimatedSwitcher या PageRouteBuilder जैसे पहले से बने ट्रांज़िशन विजेट इस्तेमाल करने का तरीका
  • animations पैकेज में मौजूद, पहले से बने ऐनिमेशन इफ़ेक्ट का इस्तेमाल कैसे करें. जैसे, FadeThroughTransition और OpenContainer
  • डिफ़ॉल्ट ट्रांज़िशन ऐनिमेशन को पसंद के मुताबिक बनाने का तरीका. इसमें Android पर पीछे जाने पर झलक दिखाने की सुविधा जोड़ने का तरीका भी शामिल है.

3026390ad413769c.gif

आगे क्या करना है?

इन कोडलैब को आज़माएं:

इसके अलावा, ऐनिमेशन का सैंपल ऐप्लिकेशन डाउनलोड करें. इसमें ऐनिमेशन की अलग-अलग तकनीकें दिखाई गई हैं.

इस बारे में और पढ़ें

आपको flutter.dev पर, ऐनिमेशन से जुड़े ज़्यादा संसाधन मिल सकते हैं:

इसके अलावा, Medium पर ये लेख पढ़ें:

रेफ़रंस दस्तावेज़