कंटेंट पर जाएँ
charmotion

Flutter ऐप में एनिमेशन कैसे जोड़ें

Charmotion टीम द्वारा · अपडेट:

गाइड का कवर इलस्ट्रेशन: Flutter ऐप में एनिमेशन कैसे जोड़ें, Charmotion डेमो कैरेक्टर के साथ

Flutter ऐप में एनिमेशन जोड़ने के लिए सरल UI बदलावों के लिए AnimatedContainer जैसे implicit widgets इस्तेमाल करें, और timing पर कंट्रोल चाहिए तो AnimationController। shared-element transitions के लिए Hero, डिज़ाइनर के बनाए वेक्टर लूप के लिए lottie पैकेज, इंटरैक्टिव कैरेक्टर के लिए rive पैकेज, और क्लिप के लिए video_player इस्तेमाल करें।

मुख्य बातें

  • Implicit एनिमेशन आपके लिए पुरानी value से नई value तक एनिमेट करते हैं; explicit एनिमेशन AnimationController पर चलते हैं।
  • Hero routes के बीच shared-element transitions लागू करता है।
  • lottie पैकेज (3.6.x) pure Dart है और Lottie.asset से फ़ाइलें चलाता है।
  • rive पैकेज (0.14.x) को RiveNative.init() चाहिए, फिर File.asset और एक RiveWidgetController।
  • यूज़र की मोशन सेटिंग का सम्मान करने के लिए MediaQuery.disableAnimationsOf(context) चेक करें।

पहले से कोई कैरेक्टर या लोगो है? अपने कैरेक्टर को असली ऐप स्क्रीन में देखें: स्प्लैश, ऑनबोर्डिंग, लोडिंग और एम्प्टी स्टेट स्क्रीन। यह फ़्री है, आपके ब्राउज़र में चलता है और कुछ भी अपलोड नहीं होता।

Flutter में कौन सा एनिमेशन तरीका इस्तेमाल करें?

टूल इस आधार पर चुनें कि क्या हिल रहा है और आर्टवर्क कहाँ से आ रहा है:

आप एनिमेट करना चाहते हैंइस्तेमाल
स्टेट बदलने के बाद size, color, padding या opacityइम्प्लिसिट विजेट: AnimatedContainer, AnimatedOpacity, TweenAnimationBuilder
लूप, staggered या gesture से चलने वाला मोशनtransition widgets के साथ एक AnimationController
दो स्क्रीन के बीच चलने वाला एलिमेंटHero
डिज़ाइनर का बनाया वेक्टर लूपlottie पैकेज
ऐप की घटनाओं पर रिएक्ट करने वाला कैरेक्टरस्टेट मशीन के साथ rive पैकेज
पहले से रेंडर की गई तय क्लिपvideo_player प्लगइन

Built-in एनिमेशन के लिए न assets चाहिए न अतिरिक्त पैकेज, इसलिए UI feedback के लिए वहीं से शुरू करें। पैकेज तभी जोड़ें जब डिज़ाइनर कोई फ़ाइल दे। Lottie और Rive दोनों iOS, Android, desktop और web पर चलते हैं, इसलिए एक ही asset उन सभी प्लेटफ़ॉर्म पर काम करता है जिन पर आपका Flutter ऐप चलता है।

पैकेज के लिए एक आसान नियम: अगर एनिमेशन शुरू से अंत तक चलता है, तो Lottie या वीडियो काफ़ी है। अगर आपके code को उसे बताना हो कि आगे क्या करना है, जैसे "upload चलने तक लूप करते रहो, फिर जश्न मनाओ", तो Rive इस्तेमाल करें।

Implicit और explicit एनिमेशन में क्या फ़र्क है?

Flutter के docs built-in एनिमेशन को दो तरह में बाँटते हैं:

  • Implicit एनिमेशन आपके लिए बदलाव को एनिमेट करते हैं। AnimatedContainer, AnimatedOpacity या TweenAnimationBuilder को नई value और duration दें, और Flutter पुरानी value से interpolate करता है।
  • Explicit एनिमेशन आपके अपने AnimationController से चलते हैं। शुरू करना, रोकना, दोहराना या उल्टा चलाना, यह आप तय करते हैं।
AnimatedContainer(
  duration: const Duration(milliseconds: 300),
  curve: Curves.easeOut,
  width: selected ? 200 : 120,
  height: 120,
  decoration: BoxDecoration(
    color: selected ? Colors.indigo : Colors.grey,
    borderRadius: BorderRadius.circular(16),
  ),
)

selected को बदलने के लिए setState call करें, और container अपनी width और color को एनिमेट करता है। Implicit widgets से शुरू करें; ज़्यादातर buttons, cards और toggles के लिए ये काफ़ी हैं।

अगर कोई तैयार implicit widget फ़िट न हो, तो TweenAnimationBuilder ऐसी किसी भी value को एनिमेट करता है जिसे आप Tween से बता सकें, जैसे rotation angle, और हर फ़्रेम पर मौजूदा value के साथ अपना builder फिर से build करता है।

छह वन-शॉट रिएक्शन: success, error, yes, no, enter और exit
वन-शॉट रिएक्शन ट्रिगर से फ़ायर होते हैं और खत्म होने पर मौजूदा मूड पर लौट आते हैं।

AnimationController और Hero का इस्तेमाल कैसे करें?

AnimationController एक तय अवधि में 0.0 से 1.0 तक की values देता है और इसे vsync ticker चाहिए, जो आमतौर पर SingleTickerProviderStateMixin से आता है:

class Pulse extends StatefulWidget {
  const Pulse({super.key, required this.child});
  final Widget child;
  @override
  State<Pulse> createState() => _PulseState();
}

class _PulseState extends State<Pulse> with SingleTickerProviderStateMixin {
  late final AnimationController controller = AnimationController(
    vsync: this,
    duration: const Duration(milliseconds: 900),
  )..repeat(reverse: true);

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) =>
      FadeTransition(opacity: controller, child: widget.child);
}

controller को हमेशा dispose करें। FadeTransition, ScaleTransition और SlideTransition जैसे transition widgets सिर्फ़ वही हिस्सा फिर से build करते हैं जिसे वे एनिमेट करते हैं।

routes के बीच shared-element transition के लिए, दोनों स्क्रीन पर एक ही widget को मिलते-जुलते tag के साथ Hero में wrap करें:

Hero(tag: 'avatar', child: CircleAvatar(backgroundImage: image))

जब आप दूसरा route push करते हैं, तो Flutter widget को एक जगह से दूसरी जगह उड़ाते हुए ले जाता है।

Flutter में Lottie file कैसे जोड़ें?

lottie package जोड़ें (लिखते समय 3.6.1)। यह pure Dart में लिखा है, इसलिए जहाँ भी Flutter चलता है, वहाँ यह चलता है। फ़ाइल को asset के रूप में declare करें:

dependencies:
  lottie: ^3.6.1

flutter:
  assets:
    - assets/loading.json

फिर इसे Lottie.asset से चलाएँ, या hosted file के लिए Lottie.network से:

import 'package:lottie/lottie.dart';

Lottie.asset('assets/loading.json', width: 160, height: 160)

डिफ़ॉल्ट रूप से एनिमेशन चलता है और दोहराता रहता है। अगर इसे किसी event पर शुरू करना हो या किसी और चीज़ के साथ sync करना हो, तो controller parameter में अपना AnimationController दें। यह package custom loader के ज़रिए dotLottie (.lottie) फ़ाइलों को भी सपोर्ट करता है।

Lottie उन spinners, success checks और ऑनबोर्डिंग illustrations के लिए अच्छा है जो शुरू से अंत तक चलते हैं। यह उस कैरेक्टर के लिए कम सही है जिसका व्यवहार ऐप की स्टेट के हिसाब से लूप के बीच में बदलना हो।

Flutter में Rive file कैसे जोड़ें और स्टेट कैसे बदलें?

rive package जोड़ें। Rive के docs में rive: ^0.14.11 दिखाया गया है, जो rive_native पर निर्भर है और आपको Rive renderer (Factory.rive) या Flutter renderer (Factory.flutter) चुनने देता है। pubspec.yaml में assets/character.riv declare करें, फिर runtime को एक बार initialize करें:

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await RiveNative.init();
  runApp(const MyApp());
}

file load करें, स्टेट मशीन के लिए controller बनाएँ, और उसे RiveWidget से render करें:

final f = await File.asset('assets/character.riv', riveFactory: Factory.rive);
final controller = RiveWidgetController(
  f!,
  stateMachineSelector: StateMachineSelector.byName('CharacterStateMachine'),
);

// in build():
RiveWidget(controller: controller, fit: Fit.contain)

इनपुट-आधारित फ़ाइल में स्टेट बदलने के लिए, स्टेट मशीन पर इनपुट खोजें और उन्हें set करें:

final mood = controller.stateMachine.number('mood');
final success = controller.stateMachine.trigger('success');
mood?.value = 5;   // loading loop
success?.fire();   // one-shot reaction

loading initState में करें, build में नहीं, और widget हटने पर controller, इनपुट और फ़ाइल को dispose करें। इनपुट अब deprecated हैं और उनकी जगह data binding है, जिसे Rive का Flutter runtime view models वाली फ़ाइलों के लिए सपोर्ट करता है। पूरे widget के लिए Flutter पर Rive गाइड देखें।

video_player से वीडियो को लूप कैसे करें?

तय क्लिप के लिए video_player plugin सामान्य विकल्प है। controller बनाएँ, initialize करें, फिर लूप और play करें:

late final VideoPlayerController video;

@override
void initState() {
  super.initState();
  video = VideoPlayerController.asset('assets/logo_reveal.mp4')
    ..setLooping(true)
    ..setVolume(0)
    ..initialize().then((_) {
      setState(() {});
      video.play();
    });
}

@override
void dispose() {
  video.dispose();
  super.dispose();
}

// in build():
video.value.isInitialized
    ? AspectRatio(aspectRatio: video.value.aspectRatio, child: VideoPlayer(video))
    : const SizedBox.shrink()

cascade (..) initialize() पूरा होने से पहले controller को configure करता है; पहला फ़्रेम उपलब्ध होते ही then callback widget को फिर से build करता है। setVolume(0) से mute करने पर सजावटी लूप बिना आवाज़ के चलता है।

वीडियो फ़ोटोग्राफ़िक और 3D डिटेल अच्छे से संभालता है, लेकिन हर variation एक अलग फ़ाइल होती है और कुछ भी स्टेट पर रिएक्ट नहीं करता। इसे लॉन्च reveal या मार्केटिंग लूप के लिए इस्तेमाल करें, और जिस कैरेक्टर को यूज़र पर रिस्पॉन्ड करना हो उसके लिए Rive file।

Flutter में एनिमेटेड मैस्कॉट के लिए कौन-सा विकल्प सबसे अच्छा है?

ऐप पर रिएक्ट करने वाले मैस्कॉट के लिए Rive व्यावहारिक विकल्प है: एक ही फ़ाइल में हर लूप और रिएक्शन होता है, और स्टेट मशीन transitions संभालती है। आपका code ऐप के events को values से जोड़ता है:

  • स्क्रीन idle: mood 0। network call चल रही है: मूड 5।
  • call पूरी हुई: success या error ट्रिगर फ़ायर करें।
  • यूज़र कुछ देर से दूर है: sleeping लूप।

यह mapping एक ही जगह रखें, जैसे आपके state management (ChangeNotifier, Bloc या Riverpod provider) पर एक listener जो इनपुट अपडेट करे। तब widgets को कभी जानने की ज़रूरत नहीं पड़ती कि कौन-सा एनिमेशन चल रहा है।

Rive स्टेट मशीन का डायग्राम: एक entry स्टेट, मूड नंबर से चुने जाने वाले दस लूप स्टेट, और वन-शॉट ट्रिगर स्टेट जो मौजूदा लूप पर लौटते हैं
Charmotion .riv कैसे जुड़ा है: मूड नंबर लूप चुनता है, ट्रिगर किसी भी स्टेट से वन-शॉट चलाते हैं।

Lottie उस मैस्कॉट के लिए ठीक है जो हर स्क्रीन पर सिर्फ़ एक लूप चलाता है; हर स्टेट के लिए फ़ाइल बदलना संभव है, लेकिन transitions झटके से कटेंगे। वीडियो सबसे भारी और सबसे कम लचीला विकल्प है।

अगर आपके पास कैरेक्टर है लेकिन animator नहीं, तो Charmotion एक अपलोड से 17 एनिमेशन का सेट बनाता है, जिसमें एक .riv शामिल है जिसकी CharacterStateMachine 0 से 9 तक का mood नंबर और success व error जैसे ट्रिगर लेती है, इसलिए ऊपर की Flutter calls बिना बदले काम करती हैं। किन स्टेट की योजना बनानी है, इसके लिए मैस्कॉट एनिमेशन स्टेट देखें।

Flutter एनिमेशन को smooth और accessible कैसे रखें?

ज़्यादातर jank हर फ़्रेम पर बहुत ज़्यादा rebuild करने से आता है। कुछ आदतें मदद करती हैं:

  • transition widgets (FadeTransition, ScaleTransition) या child के साथ AnimatedBuilder इस्तेमाल करें, ताकि सिर्फ़ एनिमेट होने वाला हिस्सा rebuild हो।
  • हर AnimationController, Rive controller और video controller को dispose करें।
  • Lottie और Rive files हर स्क्रीन पर एक बार load करें, कभी भी build के अंदर नहीं।
  • असली device पर profile mode में profile करें; debug mode काफ़ी धीमा होता है।

प्लेटफ़ॉर्म की मोशन कम करें सेटिंग का सम्मान करें। MediaQuery.disableAnimationsOf(context) सबसे नज़दीकी MediaQueryData.disableAnimations value लौटाता है और सिर्फ़ तब rebuild करता है जब वह property बदलती है:

final reduce = MediaQuery.disableAnimationsOf(context);
return reduce
    ? Image.asset('assets/mascot_still.png', width: 220)
    : RiveWidget(controller: controller, fit: Fit.contain);

एनिमेशन जो जानकारी देता है, जैसे "loading", उसे टेक्स्ट में भी उपलब्ध रखें। मोशन कम करें गाइड में WCAG नियम बताए गए हैं।

Flutter ऐप में एनिमेशन जोड़ें

  1. implicit से शुरू करें. आसान UI बदलावों के लिए AnimatedContainer, AnimatedOpacity या TweenAnimationBuilder इस्तेमाल करें।
  2. ज़रूरत हो तो explicit अपनाएँ. लूप और timed sequences के लिए vsync के साथ AnimationController बनाएँ, और उसे dispose करें।
  3. आर्टवर्क के लिए packages जोड़ें. pubspec.yaml में lottie या rive जोड़ें और फ़ाइलों को flutter: assets: के तहत declare करें।
  4. फ़ाइल render करें. Lottie.asset() इस्तेमाल करें, या RiveNative.init() call करें और RiveWidget को RiveWidgetController के साथ render करें।
  5. ऐप स्टेट जोड़ें. स्टेट बदलने पर stateMachine.number('mood').value set करें और ट्रिगर फ़ायर करें।
  6. मोशन सेटिंग का सम्मान करें. MediaQuery.disableAnimationsOf(context) जाँचें और true होने पर स्थिर इमेज दिखाएँ।

अक्सर पूछे जाने वाले सवाल

Flutter में Lottie इस्तेमाल करूँ या Rive?

After Effects में बने linear vector एनिमेशन, जैसे spinners और success checks, के लिए Lottie इस्तेमाल करें। जब एनिमेशन को ऐप स्टेट पर रिएक्ट करना हो, जैसे idle, loading और success के बीच बदलता मैस्कॉट, तो Rive इस्तेमाल करें, क्योंकि इसकी स्टेट मशीन एक ही फ़ाइल के अंदर transitions संभालती है। दोनों packages mobile, desktop और web सपोर्ट करते हैं।

मेरे AnimationController को vsync क्यों चाहिए?

vsync ticker controller को बताता है कि नया फ़्रेम कब आना है और widget न दिखने पर उसे रोक देता है, जिससे बैटरी बचती है। अपनी State class में SingleTickerProviderStateMixin mix करें और vsync के रूप में this पास करें। अगर एक State के कई controllers हों, तो TickerProviderStateMixin इस्तेमाल करें।

क्या RiveNative.init() को बाकी सब से पहले चलाना ज़रूरी है?

इसे कोई भी Rive file load करने से पहले चलना चाहिए। Rive के examples main() में runApp से पहले WidgetsFlutterBinding.ensureInitialized() call करते हैं और फिर RiveNative.init() को await करते हैं। यह call किसी widget के अंदर नहीं, startup पर रखें, ताकि यह ठीक एक बार चले और हर स्क्रीन तुरंत files load कर सके।

इस गाइड से झटपट जवाब

स्रोत

संबंधित गाइड