انیمیشن در فلاتر

1. مقدمه

انیمیشن ها روشی عالی برای بهبود تجربه کاربری برنامه شما، انتقال اطلاعات مهم به کاربر، و ساخت اپلیکیشن شما جذاب تر و لذت بخش تر برای استفاده هستند.

مروری بر چارچوب انیمیشن فلاتر

Flutter با ساخت مجدد بخشی از درخت ویجت در هر فریم، جلوه های انیمیشن را نمایش می دهد. این افکت های انیمیشن از پیش ساخته شده و سایر API ها را برای ایجاد و ساخت انیمیشن ها آسان تر می کند.

  • انیمیشن های ضمنی افکت های انیمیشن از پیش ساخته شده ای هستند که کل انیمیشن را به صورت خودکار اجرا می کنند. هنگامی که مقدار هدف انیمیشن تغییر می کند، انیمیشن را از مقدار فعلی به مقدار هدف اجرا می کند و هر مقدار را در بین آن نمایش می دهد تا ویجت به آرامی متحرک شود. نمونه هایی از انیمیشن های ضمنی عبارتند از AnimatedSize ، AnimatedScale و AnimatedPositioned .
  • انیمیشن‌های واضح نیز افکت‌های انیمیشن از پیش ساخته شده‌اند، اما برای کار کردن به یک شی Animation نیاز دارند. به عنوان مثال می توان به SizeTransition ، ScaleTransition یا PositionedTransition اشاره کرد.
  • انیمیشن کلاسی است که یک انیمیشن در حال اجرا یا متوقف شده را نشان می دهد و از مقداری تشکیل شده است که نشان دهنده مقدار هدفی است که انیمیشن روی آن اجرا می شود و وضعیت که نشان دهنده مقدار فعلی است که انیمیشن در هر زمان معین روی صفحه نمایش می دهد. این یک زیر کلاس از Listenable است و به شنوندگان خود هنگام تغییر وضعیت در حین اجرا شدن انیمیشن، اطلاع می دهد.
  • AnimationController راهی برای ایجاد انیمیشن و کنترل وضعیت آن است. روش های آن مانند forward() ، reset() ، stop() و repeat() را می توان برای کنترل انیمیشن بدون نیاز به تعریف افکت انیمیشنی که نمایش داده می شود، مانند مقیاس، اندازه یا موقعیت استفاده کرد.
  • Tweens برای درون یابی مقادیر بین یک مقدار آغاز و پایان استفاده می شود و می تواند هر نوع را نشان دهد، مانند دو، Offset یا Color .
  • منحنی ها برای تنظیم نرخ تغییر یک پارامتر در طول زمان استفاده می شوند. هنگامی که یک انیمیشن اجرا می شود، معمول است که یک منحنی کاهش را اعمال کنید تا سرعت تغییر در ابتدا یا انتهای انیمیشن سریعتر یا کندتر شود. منحنی ها یک مقدار ورودی بین 0.0 و 1.0 می گیرند و یک مقدار خروجی بین 0.0 و 1.0 برمی گردند.

چیزی که خواهی ساخت

در این کد لبه، شما قصد دارید یک بازی مسابقه چند گزینه ای بسازید که دارای افکت ها و تکنیک های مختلف انیمیشن است.

3026390ad413769c.gif

خواهید دید که چگونه ...

  • ویجتی بسازید که اندازه و رنگ آن را متحرک کند
  • یک جلوه برگردان کارت سه بعدی بسازید
  • از جلوه های انیمیشن فانتزی از پیش ساخته شده از بسته انیمیشن استفاده کنید
  • پشتیبانی پیش‌بینی حرکت برگشتی را که در آخرین نسخه اندروید موجود است اضافه کنید

چیزی که یاد خواهید گرفت

در این کد لبه یاد خواهید گرفت:

  • نحوه استفاده از افکت‌های متحرک ضمنی برای دستیابی به انیمیشن‌های عالی بدون نیاز به کد زیاد.
  • نحوه استفاده از جلوه های متحرک صریح برای پیکربندی جلوه های خود با استفاده از ویجت های متحرک از پیش ساخته شده مانند AnimatedSwitcher یا AnimationController .
  • نحوه استفاده از AnimationController برای تعریف ویجت خود که یک افکت سه بعدی را نمایش می دهد.
  • نحوه استفاده از بسته animations برای نمایش جلوه های انیمیشن فانتزی با حداقل تنظیمات.

آنچه شما نیاز دارید

  • فلاتر SDK
  • یک IDE، مانند VSCode یا Android Studio / IntelliJ

2. محیط توسعه Flutter خود را تنظیم کنید

برای تکمیل این آزمایشگاه به دو نرم افزار نیاز دارید - Flutter SDK و یک ویرایشگر .

شما می توانید کدلب را با استفاده از هر یک از این دستگاه ها اجرا کنید:

  • یک Android فیزیکی ( توصیه شده برای اجرای پیش‌بینی در مرحله 7 ) یا دستگاه iOS که به رایانه شما متصل شده و روی حالت برنامه‌نویس تنظیم شده است.
  • شبیه ساز iOS (نیاز به نصب ابزار Xcode دارد).
  • شبیه ساز اندروید (نیاز به نصب در Android Studio دارد).
  • یک مرورگر (Chrome برای اشکال زدایی لازم است).
  • یک رایانه رومیزی Windows ، Linux ، یا macOS . شما باید روی پلتفرمی که قصد استقرار در آن را دارید توسعه دهید. بنابراین، اگر می خواهید یک برنامه دسکتاپ ویندوز توسعه دهید، باید در ویندوز توسعه دهید تا به زنجیره ساخت مناسب دسترسی داشته باشید. الزامات خاص سیستم عامل وجود دارد که به طور مفصل در 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. برنامه استارتر را اجرا کنید

برنامه شروع را دانلود کنید

از git برای کلون کردن برنامه شروع از مخزن flutter/samples در GitHub استفاده کنید.

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

همچنین، می‌توانید کد منبع را به‌عنوان یک فایل zip. دانلود کنید .

برنامه را اجرا کنید

برای اجرای برنامه، از دستور flutter run استفاده کنید و دستگاه مورد نظر مانند android ، ios یا chrome را مشخص کنید. برای لیست کامل پلتفرم های پشتیبانی شده، صفحه پلتفرم های پشتیبانی شده را ببینید.

$ flutter run -d android

همچنین می توانید برنامه را با استفاده از IDE انتخابی خود اجرا و اشکال زدایی کنید. برای اطلاعات بیشتر به مستندات رسمی فلاتر مراجعه کنید.

کد را بگردید

برنامه شروع یک بازی مسابقه چند گزینه ای است که از دو صفحه تشکیل شده است که از الگوی طراحی model-view-view-model یا MVVM پیروی می کنند. QuestionScreen (View) از کلاس QuizViewModel (View-Model) برای پرسیدن سوالات چند گزینه ای از کلاس QuestionBank (Model) از کاربر استفاده می کند.

  • home_screen.dart - صفحه ای را با دکمه بازی جدید نمایش می دهد
  • main.dart - MaterialApp را برای استفاده از Material 3 و نمایش صفحه اصلی پیکربندی می کند
  • model.dart - کلاس های اصلی مورد استفاده در برنامه را تعریف می کند
  • question_screen.dart - رابط کاربری بازی مسابقه را نشان می دهد
  • view_model.dart - وضعیت و منطق بازی مسابقه را ذخیره می کند که توسط QuestionScreen نمایش داده می شود.

fbb1e1f7b6c91e21.png

این برنامه هنوز هیچ افکت متحرکی را پشتیبانی نمی کند، به جز تغییر نمای پیش فرض که توسط کلاس Flutter's Navigator نمایش داده می شود زمانی که کاربر دکمه بازی جدید را فشار می دهد.

4. از جلوه های انیمیشن ضمنی استفاده کنید

انیمیشن های ضمنی در بسیاری از موقعیت ها یک انتخاب عالی هستند، زیرا نیازی به پیکربندی خاصی ندارند. در این بخش، ویجت StatusBar را به‌روزرسانی می‌کنید تا یک تابلوی امتیازی متحرک نمایش داده شود. برای یافتن افکت‌های متحرک ضمنی متداول، اسناد API 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,
            )
        ],
      ),
    );
  }
}

سپس ویجت Scoreboard را در فرزندان ویجت StatusBar اضافه کنید و جایگزین ویجت‌های 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(                                      // NEW
              isActive: score > i,                             // NEW
            )                                                  // NEW
        ],
      ),
    );
  }
}

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 اندازه آن را با استفاده از یک انیمیشن ضمنی به روز می کند. color Icon در اینجا متحرک نیست، فقط scale است که توسط ویجت AnimatedScale انجام می شود.

84aec4776e70b870.gif

از یک Tween برای درون یابی بین دو مقدار استفاده کنید

توجه داشته باشید که رنگ ویجت AnimatedStar بلافاصله پس از تغییر فیلد isActive به true تغییر می کند.

برای دستیابی به جلوه رنگ متحرک، ممکن است سعی کنید از ویجت AnimatedContainer (که زیر کلاس دیگری از ImplicitlyAnimatedWidget است) استفاده کنید، زیرا می تواند به طور خودکار تمام ویژگی های آن، از جمله رنگ را متحرک کند. متأسفانه، ویجت ما نیاز به نمایش نماد دارد، نه یک ظرف.

همچنین می‌توانید AnimatedIcon امتحان کنید، که جلوه‌های انتقال بین اشکال نمادها را پیاده‌سازی می‌کند. اما اجرای پیش‌فرض نماد ستاره در کلاس AnimatedIcons وجود ندارد.

در عوض، از زیر کلاس دیگری از ImplicitlyAnimatedWidget به نام TweenAnimationBuilder استفاده می کنیم که یک Tween به عنوان پارامتر می گیرد. یک tween کلاسی است که دو مقدار ( begin و end ) می گیرد و مقادیر بین آن را محاسبه می کند، به طوری که یک انیمیشن بتواند آنها را نمایش دهد. در این مثال، ما از ColorTween استفاده می کنیم که رابط کاربری Tween<Color> مورد نیاز برای ساخت افکت انیمیشن ما را برآورده می کند.

ویجت Icon را انتخاب کنید و از اقدام سریع "Wrap with Builder" در IDE خود استفاده کنید، نام را به 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,                                      // Modify from here...
          );
        },                                                     // To here.
      ),
    );
  }
}

اکنون، برنامه را دوباره بارگیری کنید تا انیمیشن جدید را ببینید.

8b0911f4af299a60.gif

توجه داشته باشید که مقدار end ColorTween ما بر اساس مقدار پارامتر isActive تغییر می کند. این به این دلیل است که TweenAnimationBuilder هر زمان که مقدار Tween.end تغییر کند انیمیشن خود را دوباره اجرا می کند. هنگامی که این اتفاق می‌افتد، انیمیشن جدید از مقدار فعلی انیمیشن به مقدار پایانی جدید اجرا می‌شود، که به شما امکان می‌دهد رنگ را در هر زمان (حتی زمانی که انیمیشن در حال اجرا است) تغییر دهید و یک افکت انیمیشن صاف با مقادیر صحیح بین نمایش دهید. .

منحنی را اعمال کنید

هر دوی این افکت‌های متحرک با سرعت ثابتی اجرا می‌شوند، اما انیمیشن‌ها اغلب از نظر بصری جالب‌تر و آموزنده‌تر هستند که سرعت آن‌ها افزایش یا کاهش یابد.

یک Curve یک تابع کاهش را اعمال می کند، که نرخ تغییر یک پارامتر را در طول زمان تعریف می کند. فلاتر با مجموعه‌ای از منحنی‌های از پیش ساخته شده در کلاس Curves ، مانند easeIn یا easeOut عرضه می‌شود.

5dabe68d1210b8a1.gif

3a9e7490c594279a.gif

این نمودارها (در صفحه مستندات Curves API موجود است) سرنخی از نحوه کار منحنی ها ارائه می دهد. منحنی ها یک مقدار ورودی بین 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، مطمئن شوید که برنامه در حالت اشکال زدایی اجرا می شود و با انتخاب آن در نوار ابزار Debug در VSCode یا با انتخاب دکمه Open Flutter DevTools در پنجره Debug tool در IntelliJ / Android Studio، Widget Inspector را باز کنید.

3ce33dc01d096b14.png

363ae0fbcd0c2395.png

هنگامی که بازرس ویجت باز شد، روی دکمه Slow animations در نوار ابزار کلیک کنید.

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 آن را لغو کنید. سازنده انتقال، ویجت فرزند را که به AnimatedSwitcher ارسال شده و یک شی Animation ارائه می‌کند. این یک فرصت عالی برای استفاده از یک انیمیشن واضح است.

برای این نرم افزار کد، اولین انیمیشن صریح که استفاده خواهیم کرد SlideTransition است، که یک Animation<Offset> را می گیرد که افست شروع و پایان را تعریف می کند که ویجت های ورودی و خروجی بین آنها حرکت می کنند.

توئین ها یک تابع کمکی animate() دارند که هر Animation با استفاده از tween به Animation دیگری تبدیل می کند. این بدان معناست که یک Tween<Offset> می تواند برای تبدیل Animation<double> ارائه شده توسط AnimatedSwitcher به Animation<Offset> برای ارائه به ویجت 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 برای اعمال یک Curve به Animation و سپس برای تبدیل آن از Tween<double> که از 0.0 به 1.0 متغیر است، به T ween<Offset> که از 0.1- به 0.0 در x تغییر می کند، استفاده می کند. -محور

از طرف دیگر، کلاس 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 به یک سؤال جدید تغییر می کند، آن را در مرکز فضای موجود در حالی که انیمیشن در حال اجرا است قرار می دهد، اما وقتی انیمیشن متوقف می شود، ویجت به بالای صفحه می چسبد. این باعث ایجاد انیمیشن janky می شود زیرا موقعیت نهایی کارت سؤال با موقعیت زمانی که انیمیشن در حال اجرا است مطابقت ندارد.

d77de181bdde58f7.gif

برای رفع این مشکل، AnimatedSwitcher یک پارامتر layoutBuilder نیز دارد که می توان از آن برای تعریف layout استفاده کرد. از این تابع برای پیکربندی layout builder برای تراز کردن کارت با بالای صفحه استفاده کنید:

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

این کد یک نسخه اصلاح شده از defaultLayoutBuilder از کلاس AnimatedSwitcher است، اما از Alignment.topCenter به جای Alignment.center استفاده می کند.

خلاصه

  • انیمیشن‌های واضح افکت‌های انیمیشنی هستند که یک شی Animation را می‌گیرند (بر خلاف ImplicitlyAnimatedWidgets که مقدار و مدت زمان هدف را می‌گیرند)
  • کلاس Animation یک انیمیشن در حال اجرا را نشان می دهد، اما افکت خاصی را تعریف نمی کند.
  • از Tween().animate یا Animation.drive() برای اعمال Tweens و Curves (با استفاده از CurveTween) روی یک انیمیشن استفاده کنید.
  • از پارامتر layoutBuilder AnimatedSwitcher برای تنظیم نحوه چیدمان فرزندان خود استفاده کنید.

6. کنترل وضعیت یک انیمیشن

تاکنون هر انیمیشن به صورت خودکار توسط فریمورک اجرا شده است. انیمیشن‌های ضمنی به‌طور خودکار اجرا می‌شوند، و افکت‌های انیمیشن صریح به یک انیمیشن نیاز دارند تا به درستی کار کنند. در این بخش، یاد خواهید گرفت که چگونه با استفاده از AnimationController، اشیاء انیمیشن خود را ایجاد کنید و از TweenSequence برای ترکیب Tweens با یکدیگر استفاده کنید.

با استفاده از AnimationController یک انیمیشن را اجرا کنید

برای ایجاد یک انیمیشن با استفاده از AnimationController، باید این مراحل را دنبال کنید:

  1. یک StatefulWidget ایجاد کنید
  2. از میکسین SingleTickerProviderStateMixin در کلاس State خود برای ارائه یک Ticker به AnimationController خود استفاده کنید.
  3. AnimationController را در روش چرخه حیات initState راه‌اندازی کنید و شی State فعلی را به پارامتر vsync (TickerProvider) ارائه دهید.
  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 برای اعمال افکت چرخش سه بعدی برای شبیه سازی ورق زدن کارت استفاده می شود.

برای استفاده از این ویجت، هر کارت پاسخ را با ویجت CardFlipEffect بپیچید. مطمئن شوید که یک 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 را مستقیماً برای پیاده سازی نسخه خود گسترش دهید. متأسفانه، از آنجایی که این کلاس باید ویجت قبلی را در وضعیت خود ذخیره کند، باید از StatefulWidget استفاده کند. برای کسب اطلاعات بیشتر در مورد ایجاد جلوه های انیمیشن صریح خود، به مستندات API برای 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 ، یک انیمیشن جدید ایجاد کنید که تاخیر را با استفاده از 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>([   // NEW
      if (widget.delayAmount > 0)                   // NEW
        TweenSequenceItem(                          // NEW
          tween: ConstantTween<double>(0.0),        // NEW
          weight: widget.delayAmount,               // NEW
        ),                                          // NEW
      TweenSequenceItem(                            // NEW
        tween: Tween(begin: 0.0, end: 1.0),         // NEW
        weight: 1.0,                                // NEW
      ),                                            // NEW
    ]).animate(_animationController);               // NEW
  }

در نهایت انیمیشن 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 آزمایش کنید.

28b5291de9b3f55f.gif

7. از انتقال ناوبری سفارشی استفاده کنید

تا کنون، نحوه سفارشی سازی افکت ها را روی یک صفحه نمایش دیده ایم، اما راه دیگر برای استفاده از انیمیشن ها استفاده از آنها برای انتقال بین صفحه نمایش ها است. در این بخش، نحوه اعمال افکت های انیمیشن را بر روی تغییر صفحه نمایش با استفاده از جلوه های انیمیشن داخلی و جلوه های انیمیشن از پیش ساخته شده فانتزی ارائه شده توسط بسته رسمی انیمیشن ها در pub.dev خواهید آموخت.

یک انتقال ناوبری را متحرک کنید

کلاس 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(                                         // NEW
        pageBuilder: (context, animation, secondaryAnimation) { // NEW
          return QuestionScreen();                              // NEW
        },                                                      // NEW
        transitionsBuilder:                                     // NEW
            (context, animation, secondaryAnimation, child) {   // NEW
          return FadeTransition(                                // NEW
            opacity: animation,                                 // NEW
            child: child,                                       // NEW
          );                                                    // NEW
        },                                                      // NEW
      ),                                                        // NEW
    );
  },
  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(                          // NEW
            animation: animation,                                // NEW
            secondaryAnimation: secondaryAnimation,              // NEW
            child: child,                                        // NEW
          );                                                     // NEW
        },
      ),
    );
  },
  child: Text('New Game'),
),

سفارشی کردن انیمیشن پیشگویانه

1c0558ffa3b76439.gif

پیش‌بینی‌کننده یک ویژگی جدید اندروید است که به کاربر این امکان را می‌دهد تا قبل از پیمایش، پشت مسیر یا برنامه فعلی را نگاه کند تا ببیند پشت آن چه چیزی وجود دارد. انیمیشن زیرچشمی توسط مکان انگشت کاربر در حالی که روی صفحه به عقب کشیده می شود هدایت می شود.

Flutter با فعال کردن این ویژگی در سطح سیستم، زمانی که Flutter هیچ مسیری برای نمایش در پشته ناوبری خود ندارد، یا به عبارت دیگر، زمانی که پشتی از برنامه خارج می‌شود، از بازگشت پیش‌بینی سیستم پشتیبانی می‌کند. این انیمیشن توسط سیستم مدیریت می شود نه توسط خود Flutter.

Flutter همچنین هنگام پیمایش بین مسیرها در یک برنامه Flutter از برگشت پیش بینی کننده پشتیبانی می کند. یک PageTransitionsBuilder ویژه به نام PredictiveBackPageTransitionsBuilder به ژست‌های برگشتی پیش‌بینی‌کننده سیستم گوش می‌دهد و تغییر صفحه خود را با پیشرفت حرکت انجام می‌دهد.

پیش‌بینی‌کننده فقط در Android U و بالاتر پشتیبانی می‌شود، اما Flutter به‌خوبی به رفتار اشاره‌ای برگشت و ZoomPageTransitionBuilder برمی‌گردد. برای اطلاعات بیشتر، از جمله بخشی در مورد نحوه تنظیم آن در برنامه خود، به پست وبلاگ ما مراجعه کنید.

در پیکربندی ThemeData برای برنامه خود، PageTransitionsTheme را برای استفاده از PredictiveBack در Android و جلوه انتقال محو از بسته انیمیشن ها در سایر پلتفرم ها پیکربندی کنید:

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),
        useMaterial3: true,
        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(builder: (context) {       // NEW
        return const QuestionScreen();             // NEW
      }),                                          // NEW
    );
  },
  child: Text('New Game'),
),

برای تغییر سوال فعلی از FadeThroughTransition استفاده کنید

ویجت AnimatedSwitcher تنها یک انیمیشن در پاسخ به تماس سازنده خود ارائه می دهد. برای رفع این مشکل، بسته 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(                                          // NEW
      layoutBuilder: (entries) {                                            // NEW
        return Stack(                                                       // NEW
          alignment: Alignment.topCenter,                                   // NEW
          children: entries,                                                // NEW
        );                                                                  // NEW
      },                                                                    // NEW
      transitionBuilder: (child, animation, secondaryAnimation) {           // NEW
        return FadeThroughTransition(                                       // NEW
          animation: animation,                                             // NEW
          secondaryAnimation: secondaryAnimation,                           // NEW
          child: child,                                                     // NEW
        );                                                                  // NEW
      },                                                                    // NEW
      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

ویجت OpenContainer از بسته animations یک جلوه انیمیشن تبدیل ظرف را ارائه می دهد که برای ایجاد یک ارتباط بصری بین دو ویجت گسترش می یابد.

ویجت بازگردانده شده توسط closedBuilder در ابتدا نمایش داده می شود و هنگامی که روی کانتینر ضربه زده می شود یا زمانی که بازخوانی openContainer فراخوانی می شود به ویجت بازگردانده شده توسط openBuilder گسترش می یابد.

برای اتصال بازخوانی openContainer به view-model، یک پاس جدید viewModel را به ویجت QuestionCard اضافه کنید و یک callback ذخیره کنید که برای نمایش صفحه "Game Over" استفاده می شود:

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، کارت را با یک ویجت 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 یاد گرفتید. به طور خاص، شما یاد گرفتید:

  • نحوه استفاده از ویجت ImplicitlyAnimated
  • نحوه استفاده از ویجت Explicitly Animated
  • نحوه اعمال Curves و Tweens در یک انیمیشن
  • نحوه استفاده از ویجت های انتقال از پیش ساخته شده مانند AnimatedSwitcher یا PageRouteBuilder
  • نحوه استفاده از جلوه های انیمیشن فانتزی از پیش ساخته شده از بسته animations ، مانند FadeThroughTransition و OpenContainer
  • نحوه سفارشی کردن انیمیشن انتقال پیش‌فرض، از جمله افزودن پشتیبانی از Predictive Back در اندروید.

3026390ad413769c.gif

بعدش چی؟

برخی از این کدها را بررسی کنید:

یا برنامه نمونه انیمیشن را دانلود کنید که تکنیک های مختلف انیمیشن را به نمایش می گذارد

در ادامه مطلب

می توانید منابع انیمیشن های بیشتری را در flutter.dev بیابید:

یا این مقالات را در Medium بررسی کنید:

اسناد مرجع