ফ্লাটারে অ্যানিমেশন

1. ভূমিকা

আপনার অ্যাপের ব্যবহারকারীর অভিজ্ঞতা উন্নত করতে, ব্যবহারকারীর কাছে গুরুত্বপূর্ণ তথ্য পৌঁছে দিতে এবং অ্যাপটিকে আরও পরিশীলিত ও ব্যবহারে আনন্দদায়ক করে তোলার জন্য অ্যানিমেশন একটি চমৎকার উপায়।

ফ্লাটারের অ্যানিমেশন ফ্রেমওয়ার্কের সংক্ষিপ্ত বিবরণ

ফ্লাটার প্রতিটি ফ্রেমে উইজেট ট্রি-র একটি অংশ পুনর্নির্মাণ করে অ্যানিমেশন ইফেক্ট প্রদর্শন করে। অ্যানিমেশন তৈরি ও বিন্যাস করা সহজ করার জন্য এটি আগে থেকে তৈরি অ্যানিমেশন ইফেক্ট এবং অন্যান্য এপিআই (API) প্রদান করে।

  • ইমপ্লিসিট অ্যানিমেশন হলো আগে থেকে তৈরি করা অ্যানিমেশন ইফেক্ট, যা সম্পূর্ণ অ্যানিমেশনটি স্বয়ংক্রিয়ভাবে চালায়। যখন অ্যানিমেশনের টার্গেট ভ্যালু পরিবর্তিত হয়, তখন এটি বর্তমান ভ্যালু থেকে টার্গেট ভ্যালু পর্যন্ত অ্যানিমেশনটি চালায় এবং এর মধ্যবর্তী প্রতিটি ভ্যালু প্রদর্শন করে, যাতে উইজেটটি মসৃণভাবে অ্যানিমেট হয়। ইমপ্লিসিট অ্যানিমেশনের উদাহরণগুলোর মধ্যে রয়েছে AnimatedSize , AnimatedScale এবং AnimatedPositioned
  • এক্সপ্লিসিট অ্যানিমেশনগুলোও পূর্ব-নির্মিত অ্যানিমেশন ইফেক্ট, কিন্তু এগুলো কাজ করার জন্য একটি Animation অবজেক্টের প্রয়োজন হয়। উদাহরণস্বরূপ, SizeTransition , ScaleTransition বা PositionedTransition
  • Animation হলো একটি ক্লাস যা একটি চলমান বা থেমে থাকা অ্যানিমেশনকে উপস্থাপন করে। এটি একটি ' value ' এবং একটি 'status' দ্বারা গঠিত, যেখানে 'value' অ্যানিমেশনটির লক্ষ্য মানকে এবং ' status ' যেকোনো নির্দিষ্ট সময়ে স্ক্রিনে প্রদর্শিত বর্তমান মানকে নির্দেশ করে। এটি Listenable এর একটি সাবক্লাস এবং অ্যানিমেশন চলার সময় স্ট্যাটাস পরিবর্তিত হলে এর লিসেনারদের অবহিত করে।
  • AnimationController হলো একটি অ্যানিমেশন তৈরি এবং তার অবস্থা নিয়ন্ত্রণ করার একটি উপায়। এর forward() , reset() , stop() , এবং repeat() এর মতো মেথডগুলো ব্যবহার করে প্রদর্শিত অ্যানিমেশন ইফেক্টের (যেমন স্কেল, সাইজ বা পজিশন) মান নির্ধারণ না করেই অ্যানিমেশনটি নিয়ন্ত্রণ করা যায়।
  • টুইন ব্যবহার করা হয় কোনো শুরুর ও শেষের মানের মধ্যে ইন্টারপোলেট করার জন্য, এবং এটি ডাবল, Offset বা Color মতো যেকোনো ধরনের ডেটা টাইপ উপস্থাপন করতে পারে।
  • সময়ের সাথে সাথে কোনো প্যারামিটারের পরিবর্তনের হার সামঞ্জস্য করতে কার্ভ ব্যবহার করা হয়। যখন কোনো অ্যানিমেশন চলে, তখন সেটির শুরুতে বা শেষে পরিবর্তনের হারকে দ্রুত বা ধীর করার জন্য একটি ইজিং কার্ভ প্রয়োগ করা হয়ে থাকে। কার্ভ ০.০ থেকে ১.০-এর মধ্যে একটি ইনপুট মান গ্রহণ করে এবং ০.০ থেকে ১.০-এর মধ্যে একটি আউটপুট মান প্রদান করে।

আপনি যা তৈরি করবেন

এই কোডল্যাবে, আপনারা বিভিন্ন অ্যানিমেশন ইফেক্ট ও কৌশল ব্যবহার করে একটি বহুনির্বাচনী কুইজ গেম তৈরি করবেন।

3026390ad413769c.gif

আপনি দেখতে পাবেন কিভাবে...

  • এমন একটি উইজেট তৈরি করুন যা নিজের আকার ও রঙ পরিবর্তন করে।
  • একটি 3D কার্ড ফ্লিপ এফেক্ট তৈরি করুন
  • অ্যানিমেশন প্যাকেজ থেকে চমৎকার পূর্ব-নির্মিত অ্যানিমেশন ইফেক্ট ব্যবহার করুন
  • অ্যান্ড্রয়েডের সর্বশেষ সংস্করণে প্রিডিক্টিভ ব্যাক জেসচার সাপোর্ট যুক্ত করা হয়েছে।

আপনি যা শিখবেন

এই কোডল্যাবে আপনি শিখবেন:

  • বেশি কোড ব্যবহার না করেই কীভাবে ইমপ্লিসিটলি অ্যানিমেটেড এফেক্ট ব্যবহার করে চমৎকার অ্যানিমেশন তৈরি করা যায়।
  • AnimatedSwitcher বা AnimationController এর মতো আগে থেকে তৈরি অ্যানিমেটেড উইজেট ব্যবহার করে কীভাবে সুস্পষ্টভাবে অ্যানিমেটেড ইফেক্টের মাধ্যমে আপনার নিজস্ব ইফেক্ট কনফিগার করবেন।
  • AnimationController ব্যবহার করে কীভাবে আপনার নিজস্ব উইজেট তৈরি করবেন যা একটি 3D ইফেক্ট প্রদর্শন করে।
  • ন্যূনতম সেটআপে কীভাবে animations প্যাকেজ ব্যবহার করে চমৎকার অ্যানিমেশন ইফেক্ট দেখানো যায়।

আপনার যা যা লাগবে

  • ফ্লাটার এসডিকে
  • একটি IDE, যেমন VSCode বা Android Studio / IntelliJ

২. আপনার ফ্লাটার ডেভেলপমেন্ট পরিবেশ সেট আপ করুন।

এই ল্যাবটি সম্পন্ন করার জন্য আপনার দুটি সফটওয়্যার প্রয়োজন — ফ্লাটার এসডিকে এবং একটি এডিটর

আপনি এই ডিভাইসগুলোর যেকোনো একটি ব্যবহার করে কোডল্যাবটি চালাতে পারেন:

  • আপনার কম্পিউটারের সাথে সংযুক্ত এবং ডেভেলপার মোডে সেট করা একটি ফিজিক্যাল অ্যান্ড্রয়েড ( ধাপ ৭-এ প্রেডিক্টিভ ব্যাক বাস্তবায়নের জন্য প্রস্তাবিত ) বা আইওএস ডিভাইস।
  • iOS সিমুলেটর (এর জন্য Xcode টুলস ইনস্টল করা প্রয়োজন)।
  • অ্যান্ড্রয়েড এমুলেটর (অ্যান্ড্রয়েড স্টুডিওতে সেটআপ করা প্রয়োজন)।
  • একটি ব্রাউজার (ডিবাগিংয়ের জন্য ক্রোম আবশ্যক)।
  • একটি উইন্ডোজ , লিনাক্স বা ম্যাকওএস ডেস্কটপ কম্পিউটার। আপনি যে প্ল্যাটফর্মে ডেপ্লয় করার পরিকল্পনা করছেন, আপনাকে অবশ্যই সেখানেই ডেভেলপ করতে হবে। সুতরাং, আপনি যদি একটি উইন্ডোজ ডেস্কটপ অ্যাপ তৈরি করতে চান, তবে উপযুক্ত বিল্ড চেইন অ্যাক্সেস করার জন্য আপনাকে অবশ্যই উইন্ডোজে ডেভেলপ করতে হবে। অপারেটিং সিস্টেম-ভিত্তিক কিছু নির্দিষ্ট আবশ্যকতা রয়েছে, যা docs.flutter.dev/desktop- এ বিস্তারিতভাবে আলোচনা করা হয়েছে।

আপনার ইনস্টলেশন যাচাই করুন

আপনার ফ্লাটার এসডিকে সঠিকভাবে কনফিগার করা আছে কিনা এবং উপরে উল্লিখিত টার্গেট প্ল্যাটফর্মগুলোর মধ্যে অন্তত একটি ইনস্টল করা আছে কিনা, তা যাচাই করতে ফ্লাটার ডক্টর টুলটি ব্যবহার করুন:

$ 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!

৩. স্টার্টার অ্যাপটি চালান।

স্টার্টার অ্যাপটি ডাউনলোড করুন

git ব্যবহার করে গিটহাবের flutter/samples রিপোজিটরি থেকে স্টার্ট অ্যাপটি ক্লোন করুন।

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

বিকল্পভাবে, আপনি সোর্স কোডটি একটি জিপ ফাইল হিসেবে ডাউনলোড করতে পারেন।

অ্যাপটি চালান

অ্যাপটি চালানোর জন্য, flutter run কমান্ডটি ব্যবহার করুন এবং একটি টার্গেট ডিভাইস নির্দিষ্ট করুন, যেমন android , ios বা chrome । সমর্থিত প্ল্যাটফর্মগুলির সম্পূর্ণ তালিকার জন্য, `Supported platforms` পৃষ্ঠাটি দেখুন।

flutter run -d android

এছাড়াও আপনি আপনার পছন্দের IDE ব্যবহার করে অ্যাপটি রান ও ডিবাগ করতে পারেন। আরও তথ্যের জন্য ফ্লাটারের অফিশিয়াল ডকুমেন্টেশন দেখুন।

কোডটি ঘুরে দেখুন

স্টার্টার অ্যাপটি একটি বহুনির্বাচনী কুইজ গেম, যা মডেল-ভিউ-ভিউ-মডেল বা MVVM ডিজাইন প্যাটার্ন অনুসরণ করে দুটি স্ক্রিন নিয়ে গঠিত। QuestionScreen (ভিউ) ক্লাসটি QuestionBank (মডেল) ক্লাস থেকে ব্যবহারকারীকে বহুনির্বাচনী প্রশ্ন জিজ্ঞাসা করার জন্য QuizViewModel (ভিউ-মডেল) ক্লাস ব্যবহার করে।

  • home_screen.dart - একটি নতুন গেম বাটন সহ একটি স্ক্রিন প্রদর্শন করে
  • main.dart - MaterialApp Material 3 ব্যবহার করতে এবং হোম স্ক্রিন দেখাতে কনফিগার করে।
  • model.dart - অ্যাপ জুড়ে ব্যবহৃত মূল ক্লাসগুলো সংজ্ঞায়িত করে।
  • question_screen.dart - কুইজ গেমের UI প্রদর্শন করে
  • view_model.dart - কুইজ গেমের অবস্থা এবং লজিক সংরক্ষণ করে, যা QuestionScreen এ প্রদর্শিত হয়।

fbb1e1f7b6c91e21.png

ব্যবহারকারী ' New Game' বাটন চাপলে ফ্লাটারের Navigator ক্লাস দ্বারা প্রদর্শিত ডিফল্ট ভিউ ট্রানজিশনটি ছাড়া, অ্যাপটিতে এখনও কোনো অ্যানিমেটেড ইফেক্ট সাপোর্ট করে না।

৪. অন্তর্নিহিত অ্যানিমেশন প্রভাব ব্যবহার করুন

অনেক পরিস্থিতিতেই ইমপ্লিসিট অ্যানিমেশন একটি চমৎকার পছন্দ, কারণ এর জন্য কোনো বিশেষ কনফিগারেশনের প্রয়োজন হয় না। এই অংশে, আপনি StatusBar উইজেটটি আপডেট করবেন যাতে এটি একটি অ্যানিমেটেড স্কোরবোর্ড প্রদর্শন করে। প্রচলিত ইমপ্লিসিট অ্যানিমেশন ইফেক্টগুলো খুঁজে পেতে, ImplicitlyAnimatedWidget API ডকুমেন্টেশনটি ব্রাউজ করুন।

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

দুটি মানের মধ্যে ইন্টারপোলেট করতে একটি টুইন ব্যবহার করুন।

লক্ষ্য করুন যে, isActive ফিল্ডটি true-তে পরিবর্তিত হওয়ার সাথে সাথেই AnimatedStar উইজেটটির রঙ বদলে যায়।

অ্যানিমেটেড রঙের প্রভাব আনতে, আপনি একটি AnimatedContainer উইজেট (যা ImplicitlyAnimatedWidget এর আরেকটি সাবক্লাস) ব্যবহার করে দেখতে পারেন, কারণ এটি রঙ সহ এর সমস্ত অ্যাট্রিবিউট স্বয়ংক্রিয়ভাবে অ্যানিমেট করতে পারে। দুর্ভাগ্যবশত, আমাদের উইজেটটিকে একটি আইকন প্রদর্শন করতে হবে, কোনো কন্টেইনার নয়।

আপনি AnimatedIcon ও ব্যবহার করে দেখতে পারেন, যা আইকনগুলোর আকারের মধ্যে রূপান্তর প্রভাব প্রয়োগ করে। কিন্তু AnimatedIcons ক্লাসে একটি তারকা আইকনের কোনো ডিফল্ট বাস্তবায়ন নেই।

এর পরিবর্তে, আমরা ImplicitlyAnimatedWidget এর TweenAnimationBuilder নামক আরেকটি সাবক্লাস ব্যবহার করব, যা প্যারামিটার হিসেবে একটি Tween গ্রহণ করে। একটি tween হলো এমন একটি ক্লাস যা দুটি মান ( begin এবং end ) গ্রহণ করে এবং তাদের মধ্যবর্তী মানগুলো গণনা করে, যাতে একটি অ্যানিমেশন সেগুলো প্রদর্শন করতে পারে। এই উদাহরণে, আমরা একটি ColorTween ব্যবহার করব, যা Tween এর শর্ত পূরণ করে। Tween আমাদের অ্যানিমেশন ইফেক্ট তৈরি করার জন্য প্রয়োজনীয় ইন্টারফেস।

আপনার IDE-তে Icon উইজেটটি নির্বাচন করুন এবং 'Wrap with Builder' কুইক অ্যাকশনটি ব্যবহার করে নামটি পরিবর্তন করে TweenAnimationBuilder রাখুন। এরপর সময়কাল (duration) এবং 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

লক্ষ্য করুন যে, আমাদের ColorTween এর end মানটি isActive প্যারামিটারের মানের উপর ভিত্তি করে পরিবর্তিত হয়। এর কারণ হলো, যখনই Tween.end এর মান পরিবর্তিত হয়, TweenAnimationBuilder তার অ্যানিমেশনটি পুনরায় চালায়। যখন এটি ঘটে, তখন নতুন অ্যানিমেশনটি বর্তমান অ্যানিমেশন মান থেকে নতুন শেষ মান পর্যন্ত চলে, যা আপনাকে যেকোনো সময় (এমনকি অ্যানিমেশন চলার সময়েও) রঙ পরিবর্তন করতে এবং সঠিক মধ্যবর্তী মানগুলো সহ একটি মসৃণ অ্যানিমেশন প্রভাব প্রদর্শন করতে দেয়।

একটি বক্ররেখা প্রয়োগ করুন

এই দুটি অ্যানিমেশন ইফেক্টই একটি স্থির গতিতে চলে, কিন্তু অ্যানিমেশনগুলোর গতি বাড়ালে বা কমালে সেগুলো প্রায়শই আরও দৃষ্টিনন্দন ও তথ্যপূর্ণ হয়ে ওঠে।

একটি Curve একটি ইজিং ফাংশন প্রয়োগ করে, যা সময়ের সাথে সাথে কোনো প্যারামিটারের পরিবর্তনের হার নির্ধারণ করে। ফ্লাটারের Curves ক্লাসে আগে থেকে তৈরি করা কিছু ইজিং কার্ভ রয়েছে, যেমন easeIn বা easeOut

5dabe68d1210b8a1.gif

3a9e7490c594279a.gif

এই ডায়াগ্রামগুলো ( Curves এপিআই ডকুমেন্টেশন পেজে উপলব্ধ) কার্ভ কীভাবে কাজ করে সে সম্পর্কে একটি ধারণা দেয়। কার্ভ ০.০ থেকে ১.০-এর মধ্যে একটি ইনপুট মানকে (যা x-অক্ষে প্রদর্শিত হয়) ০.০ থেকে ১.০-এর মধ্যে একটি আউটপুট মানে (যা y-অক্ষে প্রদর্শিত হয়) রূপান্তরিত করে। এই ডায়াগ্রামগুলো আরও দেখায় যে, ইজিং কার্ভ ব্যবহার করা হলে বিভিন্ন অ্যানিমেশন ইফেক্ট দেখতে কেমন হয়।

AnimatedStar-এ _curve নামে একটি নতুন ফিল্ড তৈরি করুন এবং এটিকে AnimatedScaleTweenAnimationBuilder উইজেটগুলিতে প্যারামিটার হিসেবে পাস করুন।

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 খোলার জন্য, নিশ্চিত করুন যে অ্যাপটি ডিবাগ মোডে চলছে, এবং VSCode-এর ডিবাগ টুলবার থেকে উইজেট ইন্সপেক্টর নির্বাচন করে অথবা IntelliJ / Android Studio-এর ডিবাগ টুল উইন্ডোতে থাকা ' Open Flutter DevTools' বোতামটি নির্বাচন করে এটি খুলুন।

3ce33dc01d096b14.png

363ae0fbcd0c2395.png

উইজেট ইন্সপেক্টরটি খুলে গেলে, টুলবারে থাকা স্লো অ্যানিমেশন বাটনটিতে ক্লিক করুন।

adea0a16d01127ad.png

৫. সুস্পষ্ট অ্যানিমেশন প্রভাব ব্যবহার করুন

ইমপ্লিসিট অ্যানিমেশনের মতোই, এক্সপ্লিসিট অ্যানিমেশনও হলো আগে থেকে তৈরি করা অ্যানিমেশন ইফেক্ট, কিন্তু এগুলো কোনো টার্গেট ভ্যালু নেওয়ার পরিবর্তে প্যারামিটার হিসেবে একটি 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 প্যারামিটার ব্যবহার করে এটি ওভাররাইড করতে পারেন। ট্রানজিশন বিল্ডারটি AnimatedSwitcher এ পাস করা চাইল্ড উইজেট এবং একটি Animation অবজেক্ট প্রদান করে। এটি একটি সুস্পষ্ট অ্যানিমেশন ব্যবহার করার দারুণ সুযোগ।

এই কোডল্যাবের জন্য, আমরা প্রথম যে সুস্পষ্ট অ্যানিমেশনটি ব্যবহার করব তা হলো SlideTransition , যা একটি Animation<Offset> গ্রহণ করে। এই <Offset> ইনকামিং এবং আউটগোয়িং উইজেটগুলোর চলাচলের শুরু ও শেষের অফসেট নির্ধারণ করে।

টুইন-এর একটি সহায়ক ফাংশন আছে, animate() , যা যেকোনো Animation টুইন প্রয়োগ করা অন্য একটি Animation রূপান্তরিত করে। এর মানে হলো যে একটি Tween Animation রূপান্তর করতে ব্যবহার করা যেতে পারে Animation 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,
          ),
        ),
      ),
    );
  }
}

উল্লেখ্য যে, এটি Animation একটি Curve প্রয়োগ করতে এবং তারপর এটিকে একটি Tween থেকে রূপান্তর করতে Tween.animate ব্যবহার করে। Tween যার পরিসর ০.০ থেকে ১.০, একটি Tween পর্যন্ত Tween যা এক্স-অক্ষে -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);
},

এক্সপ্লিসিট অ্যানিমেশন ব্যবহারের আরেকটি সুবিধা হলো এগুলোকে একসাথে কম্পোজ করা যায়। SlideTransition উইজেটটিকে র‍্যাপ করে FadeTransition আরেকটি এক্সপ্লিসিট অ্যানিমেশন যোগ করুন, যা একই কার্ভড অ্যানিমেশন ব্যবহার করে।

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
  },

লেআউটবিল্ডার কাস্টমাইজ করুন

আপনি 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 প্যারামিটারটি ব্যবহার করুন।

৬. একটি অ্যানিমেশনের অবস্থা নিয়ন্ত্রণ করুন

এখন পর্যন্ত, প্রতিটি অ্যানিমেশন ফ্রেমওয়ার্ক দ্বারা স্বয়ংক্রিয়ভাবে চালিত হয়েছে। ইমপ্লিসিট অ্যানিমেশনগুলো স্বয়ংক্রিয়ভাবে চলে, এবং এক্সপ্লিসিট অ্যানিমেশন ইফেক্টগুলো সঠিকভাবে কাজ করার জন্য একটি Animation প্রয়োজন হয়। এই অংশে, আপনি শিখবেন কীভাবে একটি AnimationController ব্যবহার করে আপনার নিজস্ব Animation অবজেক্ট তৈরি করতে হয়, এবং Tween গুলোকে একত্রিত করার জন্য একটি TweenSequence ব্যবহার করতে হয়।

AnimationController ব্যবহার করে একটি অ্যানিমেশন চালান

AnimationController ব্যবহার করে অ্যানিমেশন তৈরি করতে, আপনাকে এই ধাপগুলো অনুসরণ করতে হবে:

  1. একটি StatefulWidget তৈরি করুন
  2. আপনার AnimationController এ একটি Ticker সরবরাহ করতে আপনার State ক্লাসে SingleTickerProviderStateMixin মিক্সিনটি ব্যবহার করুন।
  3. initState লাইফসাইকেল মেথডে vsync ( TickerProvider ) প্যারামিটারে বর্তমান State অবজেক্টটি প্রদান করে AnimationController কে ইনিশিয়ালাইজ করুন।
  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- এর API ডকুমেন্টেশন দেখুন।

TweenSequence ব্যবহার করে একটি ডিলে যোগ করুন

এই অংশে, আপনি CardFlipEffect উইজেটে একটি ডিলে (delay) যোগ করবেন, যাতে প্রতিটি কার্ড এক এক করে উল্টে যায়। শুরু করার জন্য, 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();
}

এরপর AnswerCards এর বিল্ড মেথডে delayAmount যোগ করুন।

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 মধ্যে, একটি TweenSequence ব্যবহার করে ডিলে প্রয়োগ করার জন্য একটি নতুন Animation তৈরি করুন। লক্ষ্য করুন যে, এটি 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.
  }

অবশেষে, build মেথডে AnimationController এর অ্যানিমেশনটিকে নতুন ডিলেড অ্যানিমেশনটি দিয়ে প্রতিস্থাপন করুন।

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

৭. কাস্টম নেভিগেশন ট্রানজিশন ব্যবহার করুন

এখন পর্যন্ত আমরা দেখেছি কীভাবে একটি স্ক্রিনে ইফেক্ট কাস্টমাইজ করতে হয়, কিন্তু অ্যানিমেশন ব্যবহারের আরেকটি উপায় হলো স্ক্রিনগুলোর মধ্যে ট্রানজিশন ঘটানো। এই অংশে, আপনি শিখবেন কীভাবে বিল্ট-ইন অ্যানিমেশন ইফেক্ট এবং pub.dev- এ থাকা অফিসিয়াল অ্যানিমেশন প্যাকেজ দ্বারা প্রদত্ত চমৎকার প্রি-বিল্ট অ্যানিমেশন ইফেক্ট ব্যবহার করে স্ক্রিন ট্রানজিশনে অ্যানিমেশন ইফেক্ট প্রয়োগ করতে হয়।

একটি নেভিগেশন ট্রানজিশন অ্যানিমেট করুন

PageRouteBuilder ক্লাসটি একটি Route যা আপনাকে ট্রানজিশন অ্যানিমেশন কাস্টমাইজ করার সুযোগ দেয়। এটি আপনাকে এর transitionBuilder কলব্যাকটি ওভাররাইড করার সুযোগ দেয়, যা দুটি Animation অবজেক্ট সরবরাহ করে। এই অবজেক্টগুলো 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 widget) দিয়ে প্রতিস্থাপন করুন:

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

প্রেডিক্টিভ ব্যাক হলো অ্যান্ড্রয়েডের একটি নতুন ফিচার, যা ব্যবহারকারীকে নেভিগেট করার আগে বর্তমান রুট বা অ্যাপের পিছনে কী আছে তা দেখার সুযোগ দেয়। ব্যবহারকারী যখন স্ক্রিনের উপর দিয়ে আঙুল টেনে পিছনে নিয়ে যান, তখন তার অবস্থানের উপর ভিত্তি করে এই উঁকি দেওয়ার অ্যানিমেশনটি চালিত হয়।

ফ্লাটার সিস্টেম প্রেডিক্টিভ ব্যাক সমর্থন করে। যখন ফ্লাটারের নেভিগেশন স্ট্যাকে পপ করার মতো কোনো রাউট থাকে না, বা অন্য কথায়, যখন একটি ব্যাক বাটন অ্যাপ থেকে বেরিয়ে যাওয়ার কথা আসে, তখন সিস্টেম লেভেলে এই ফিচারটি সক্রিয় করা হয়। এই অ্যানিমেশনটি সিস্টেম দ্বারা পরিচালিত হয়, ফ্লাটার নিজে থেকে নয়।

একটি ফ্লাটার অ্যাপের মধ্যে বিভিন্ন রুটের মধ্যে নেভিগেট করার সময় ফ্লাটার প্রেডিক্টিভ ব্যাক-ও সমর্থন করে। PredictiveBackPageTransitionsBuilder নামক একটি বিশেষ PageTransitionsBuilder সিস্টেমের প্রেডিক্টিভ ব্যাক জেসচারগুলো শোনে এবং সেই জেসচারের অগ্রগতির সাথে তাল মিলিয়ে এর পেজ ট্রানজিশন পরিচালনা করে।

প্রেডিক্টিভ ব্যাক শুধুমাত্র অ্যান্ড্রয়েড ইউ এবং তার উপরের সংস্করণগুলিতে সমর্থিত, কিন্তু ফ্লাটার স্বয়ংক্রিয়ভাবে মূল ব্যাক জেসচার আচরণ এবং ZoomPageTransitionBuilder- এ ফিরে যাবে। আরও তথ্যের জন্য আমাদের ব্লগ পোস্টটি দেখুন, যেখানে আপনার নিজের অ্যাপে এটি কীভাবে সেট আপ করবেন সে সম্পর্কে একটি বিভাগও রয়েছে।

আপনার অ্যাপের ThemeData কনফিগারেশনে, Android-এর জন্য PredictiveBack এবং অন্যান্য প্ল্যাটফর্মের জন্য animations প্যাকেজ থেকে fade-through ট্রানজিশন ইফেক্ট ব্যবহার করতে PageTransitionsTheme টি কনফিগার করুন:

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

ওপেনকন্টেইনার ব্যবহার করুন

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 ও open container callback-এর জন্য দুটি নতুন ফিল্ড যোগ করুন:

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

৮. অভিনন্দন

অভিনন্দন, আপনি সফলভাবে একটি ফ্লাটার অ্যাপে অ্যানিমেশন ইফেক্ট যোগ করেছেন এবং ফ্লাটারের অ্যানিমেশন সিস্টেমের মূল উপাদানগুলো সম্পর্কে জেনেছেন। বিশেষত, আপনি শিখেছেন:

  • ImplicitlyAnimatedWidget কীভাবে ব্যবহার করবেন
  • ExplicitlyAnimatedWidget কীভাবে ব্যবহার করবেন
  • অ্যানিমেশনে Curves এবং Tweens কীভাবে প্রয়োগ করবেন
  • AnimatedSwitcher বা PageRouteBuilder এর মতো আগে থেকে তৈরি ট্রানজিশন উইজেটগুলি কীভাবে ব্যবহার করবেন
  • animations প্যাকেজ থেকে FadeThroughTransition এবং OpenContainer মতো চমৎকার আগে থেকে তৈরি অ্যানিমেশন ইফেক্টগুলো কীভাবে ব্যবহার করবেন
  • অ্যান্ড্রয়েডে প্রেডিক্টিভ ব্যাক সাপোর্ট যোগ করা সহ ডিফল্ট ট্রানজিশন অ্যানিমেশন কীভাবে কাস্টমাইজ করবেন।

3026390ad413769c.gif

এরপর কী?

এই কোডল্যাবগুলো দেখে নিন:

অথবা অ্যানিমেশন স্যাম্পল অ্যাপটি ডাউনলোড করুন, যেখানে বিভিন্ন অ্যানিমেশন কৌশল প্রদর্শন করা হয়েছে।

আরও পড়ুন

আপনি flutter.dev-এ আরও অ্যানিমেশন রিসোর্স খুঁজে পেতে পারেন:

অথবা মিডিয়ামে এই নিবন্ধগুলো দেখুন:

রেফারেন্স নথি