تصوير راي هينيسي على Unsplash

كيفية إنشاء الرسوم المتحركة في رفرفة مع Redux؟

مرحبا بعودتك!

سنكتشف اليوم كيفية إنشاء رسوم متحركة في Flutter عندما نستخدم بنية Redux. إذا كنت لا تعرف ماهية Redux ، فإنني أشجعك على قراءة مشاركتي مع البرنامج التعليمي حول Redux - Flutter + Redux - كيفية جعل تطبيق قائمة التسوق

إذا كنت معتادًا على Redux ، فمن المحتمل أن تعلم أنه قد يخفف من حياتك مع بنية التطبيق لديك كثيرًا. إنها أداة رائعة لفصل العرض التقديمي ومنطق العمل. ولكن ماذا عن مزيج من الرفرفة ، واستعادة والرسوم المتحركة؟

دعنا نذهب للتسوق!

في هذه المقالة ، سنستخدم حزمة flutter_redux.

دعنا نفترض حالة بسيطة: لدينا تطبيق تسوق مع عربة ، والتي تبدو كما يلي:

قائمة التسوق التطبيق

يمكننا إضافة وإزالة المنتجات من القائمة.

إذا كنت ترغب في رؤية بقية الشفرة (الإجراءات ، المخفضات ، وما إلى ذلك) ، يمكنك التحقق من المستودع الكامل هنا: pszklarska / flutter_redux_animations_app

ما نود تحقيقه هو إظهار قيمة العربة الحالية في الشريط أسفل الشاشة. لتعقيد الأمور قليلاً ، سنظهرها مع الرسوم المتحركة مثل هذا:

تطبيق قائمة التسوق (مع الرسوم المتحركة!)

كما ترون ، عندما نضيف بعض المنتجات إلى العربة الفارغة ، نود أن نظهر الشريط السفلي مع الشريحة في الرسوم المتحركة. وعندما تتم إزالة جميع المنتجات ، نود أن تختفي.

كيف يمكننا أن نفعل ذلك؟

أولاً - دعنا نتحقق من قيمة سلة التسوق الخاصة بنا

سنبدأ أولاً بإضافة شريط بسيط في أسفل الشاشة لإظهار قيمة العربة الحالية. ألقِ نظرة على الكود الخاص بهذا الشريط:

استيراد "الحزمة: رفرفة / material.dart" ؛

const CART_BAR_HEIGHT = 48.0 ؛

الطبقة CartValueBar يمتد StatelessWidget {
  عربة مزدوجة النهائيالقيمة ؛

  const CartValueBar ({Key key، this.cartValue})
      : السوبر (مفتاح: مفتاح) ؛

  @تجاوز
  إنشاء عنصر واجهة المستخدم (سياق BuildContext) {
    إرجاع cartValue! = 0
        ؟ حاوية(
            الارتفاع: CART_BAR_HEIGHT ،
            اللون: الألوان.
            الطفل: الصف (
              الأطفال:  [
                حشوة(
                  الحشو: const EdgeInsets.all (16.0) ،
                  الطفل: النص (
                    "قيمة السلة $ {_ getCartValue ()} zł" ،
                  )،
                )،
              ]،
            )،
          )
        : حاوية()؛
  }

  String _getCartValue () => cartValue.toStringAsFixed (2)؛
}
تحقق من شفرة المصدر الكاملة لهذا الملف على جيثب هنا.

في هذا الرمز ، لدينا cartValue الممتلكات. بناءً على هذه الخاصية ، نعرض إما شريط العربة السفلية مع قيمة العربة أو حاوية واحدة. في هذه المرحلة ، تبدو شاشتنا كما يلي:

تطبيق قائمة التسوق (بدون رسوم متحركة)

كما ترون ، ليس لدينا الآن أي رسوم متحركة رائعة (في الواقع ليس لدينا أي رسوم متحركة على الإطلاق). لجعلها تعمل ، نحن بحاجة إلى إجراء بعض التغييرات في القطعة لدينا.

أرني بعض الرسوم المتحركة لطيفة!

الأول هو تحويل CartValueBar الخاص بنا إلى StatefulWidget:

الطبقة CartValueBar يمتد StatefulWidget {
  عربة مزدوجة النهائيالقيمة ؛

  const CartValueBar ({Key key، this.cartValue}): super (key: key)؛

  @تجاوز
  _CartValueBarState createState () => _CartValueBarState ()؛
}
class _CartValueBarState يمتد الدولة  {

  @تجاوز
  إنشاء عنصر واجهة المستخدم (سياق BuildContext) {
    // TODO سنقوم بإدخال رمز CartValueBar هنا
    حاوية العودة () ؛
  }

}

نحتاج إلى جعل عنصر واجهة المستخدم مفيدًا ، لأننا في الخطوات اللاحقة لتطبيق الرسوم المتحركة سنحتاج إلى شيء يسمى SingleTickerProviderStateMixin. يمنع هذا المزيج الرسوم المتحركة لـ Flutter من استهلاك الموارد عندما لم تعد هناك حاجة إليها (على سبيل المثال ، لم تعد مرئية).

يمكنك قراءة المزيد حول تنفيذ الرسوم المتحركة بشكل عام في البرنامج التعليمي Flutter Animations.

الآن ، عندما تكون لدينا عنصر واجهة مستخدم ، يمكنك إضافة SingleTickerProviderStateMixin و Animation و AnimationController إلى CartValueBar:

الفئة _CartValueBarState يمتد الدولة 
    مع SingleTickerProviderStateMixin {
  AnimationController _controller؛
  الرسوم المتحركة  _animation؛

  @تجاوز
  باطل initState () {
    super.initState ()؛

    _controller = AnimationController (
      vsync: هذا ،
      المدة: المدة (بالميلي ثانية: 300) ،
    ) .. addListener (() {
      setState (() {}) ؛
    })؛

    _animation = Tween  (
      تبدأ: CART_BAR_HEIGHT ،
      النهاية: 0 ،
    ) .animate (
      CurvedAnimation (
        الأصل: _controller ،
        منحنى: منحنيات.
        الباب الخلفي: منحنيات.
      )،
    )؛
  }

  ...

  @تجاوز
  التخلص من الفراغ () {
    super.dispose ()؛
    _controller.dispose ()؛
  }
}
تحقق من شفرة المصدر الكاملة لهذا الملف على جيثب هنا.

في البداية ، لدينا أولاً AnimationController ، المسؤول بشكل عام عن تشغيل الرسوم المتحركة والتحكم فيها - بدء ، إيقاف ، إيقاف مؤقت ، وما إلى ذلك.

التالي هو كائن الرسوم المتحركة ، الذي يعلن معلومات أكثر تحديداً عن الرسوم المتحركة:

  • ما هي قيم الرسوم المتحركة؟ في حالتنا ، نبدأ من ارتفاع الشريط وينتهي عند الصفر ، حيث سيكون إزاحة شريطنا. سيكون الشريط أولاً تحت الشاشة عندما تكون الإزاحة مساوية لارتفاع الشريط وعند أسفل الشاشة عندما يكون الإزاحة صفرًا
ملاحظة: يبدأ موضع الشاشة في الزاوية العلوية اليسرى ويزيد مع الانتقال إلى الزاوية السفلية اليمنى. لذلك إذا كنا نرغب في إخفاء الشريط السفلي ، فنحن بحاجة إلى تقليل قيمة المركز.
  • ما هو مترجم الرسوم المتحركة؟ في حالتنا ، سنستخدم CurvedAnimation مع Curves.easeOut و Curves.ease في المترجمين ، لذلك ستبدأ الرسوم المتحركة بسرعة وتنتهي ببطء عند إظهار الشريط والعكس عند الإختباء

لتشغيل الرسوم المتحركة ، سنقوم ببساطة بالاتصال بالأمام () على وحدة التحكم. وفي النهاية ، يتعين علينا استدعاء dispos () على AnimationController عندما لم تعد هناك حاجة إليه. إذا كنا نريد تشغيل الرسوم المتحركة في الاتجاه المعاكس ، فسنستخدم طريقة العكسي ().

الآن عندما تتم تهيئة الرسوم المتحركة الخاصة بنا ، نحتاج إلى تعيين قيم الرسوم المتحركة على إزاحة الشريط ، لذلك عندما تنتقل الرسوم المتحركة من ارتفاع الشريط إلى 0 ، سيتم ترجمة شريطنا بهذه القيم. سنستخدم Transform Widget للقيام بذلك:

الفئة _CartValueBarState يمتد الدولة 
    مع SingleTickerProviderStateMixin {

  ...

  @تجاوز
  إنشاء عنصر واجهة المستخدم (سياق BuildContext) {
    return widget.cartValue! = 0
        ؟ محاذاة (
            المحاذاة: Alignment.bottomCenter ،
            تابع: Transform.translate (// تم تغييره
              إزاحة: إزاحة (0 ، _animation.value) ، // تم تغييرها
              الطفل: حاوية (
                اللون: الألوان.
                الطفل: الصف (
                  الأطفال:  [
                    حشوة(
                      الحشو: const EdgeInsets.all (16.0) ،
                      الطفل: النص (
                        "قيمة السلة $ {_ getCartValue ()} zł" ،
                      )،
                    )،
                  ]،
                )،
              )،
            )،
          )
        : حاوية()؛
  }

  ...
}
تحقق من شفرة المصدر الكاملة لهذا الملف على جيثب هنا.

حسنًا ، لدينا الآن مجموعة الرسوم المتحركة الخاصة بنا ، يتم تعيين القيم لإزاحة الترجمة ، لكن ما الذي ينقصنا؟ الرسوم المتحركة الفعلية على التوالي.

أين يجب أن نبدأ الرسوم المتحركة؟

لتحديد أي مكان في الكود ينبغي أن نضع فيه نقطة بداية للرسوم المتحركة ، نحتاج إلى معرفة شيء واحد مهم حول Redux - كلما أرسل المتجر بعض أحداث التغيير ، تتم إعادة إنشاء عناصر واجهة المستخدم الخاصة بنا. لذلك في الواقع مع كل تحديث ، نحن نعرف فقط عن القيم الحالية (في حالتنا ، قيمة العربة الحالية). لا يكفي إنشاء رسم متحرك ، لأنه لجعله صحيحًا ، نحتاج إلى معرفة الحالة السابقة أيضًا:

  • نريد أن نظهر شريط العربة عندما كانت قيمة العربة السابقة صفرًا
  • نريد إخفاء شريط العربة عندما تكون قيمة العربة الحالية صفرًا

لا يمكننا وضع الرسوم المتحركة داخل طريقة initState () ، حيث يتم استدعاؤها فقط عند إنشاء حالة عناصر واجهة التعامل. بعد تغيير cartValue ، يتم تحديث الأداة لدينا ، لذلك نحن بحاجة للبحث عن تحديث رد الاتصال. لحسن الحظ بالنسبة لنا ، لدينا! تسمى الطريقة التي نبحث عنها: didUpdateWidget (). يمكننا أن نقرأ في الوثائق:

إذا تم إعادة إنشاء عنصر واجهة المستخدم الأصل وطلب هذا الموقع في التحديث الشجري لعرض عنصر واجهة مستخدم جديد مع نفس وقت التشغيل ونوع Widget.key ، فسيقوم الإطار بتحديث خاصية عنصر واجهة مستخدم كائن الحالة هذا للإشارة إلى عنصر واجهة المستخدم الجديد ثم استدعاء هذه الطريقة مع القطعة السابقة كحجة.
تجاوز هذه الطريقة للرد عندما تتغير الأداة (على سبيل المثال ، لبدء الرسوم المتحركة الضمنية).

عظيم! يمكننا استخدامه لغرضنا. في هذه الطريقة ، سنحصل على حالة oldWidget ، حتى نتمكن من الرد وفقًا للقيم السابقة. دعونا نلقي نظرة على الكود ثم:

@تجاوز
void didUpdateWidget (CartValueBar oldWidget) {
  super.didUpdateWidget (oldWidget)؛

  إذا (oldWidget.cartValue == 0) {
    _controller.forward ()؛
  }

  if (widget.cartValue == 0) {
    _controller.reverse ()؛
  }
}
تحقق من شفرة المصدر الكاملة لهذا الملف على جيثب هنا.

نحن هنا نتحقق من شرطين. الموقف الأول هو عندما تكون قيمة سلة التسوق الخاصة بنا سابقًا 0 ، وهذا يعني أنه يتعين علينا إظهار شريط سلة التسوق لدينا (لذا ، يرجى استدعاء forward () في وحدة التحكم في الرسوم المتحركة).

الموقف الثاني هو عندما تكون قيمة العربة الجديدة 0 ، لذلك نحتاج إلى إخفاء شريط العربة (استدعاء عكسي () لتشغيل الرسوم المتحركة في الاتجاه المعاكس).

دعونا نتحقق من كيفية عملها!

الرسوم المتحركة الرائعة موجودة تقريبًا ...

عظيم ، لدينا الرسوم المتحركة الآن! لكن ربما لاحظت وجود خلل كبير عند إخفاء شريط السيارات. يحدث هذا لأن النص يتغير إلى "0.00" مباشرة قبل الخروج من الشاشة. يمكننا إصلاح هذا باستخدام عبارة if بسيطة:

حشوة(
  الحشو: const EdgeInsets.symmetric (أفقي: 16.0) ،
  الطفل: النص (
    widget.cartValue! = 0
        ؟ "قيمة السلة $ {_ getCartValue ()} zł"
        : '' ،
  )،
)،

الآن قم بتشغيل الرسوم المتحركة الخاصة بنا مرة أخرى:

التأثير النهائي!

مرحى! هذا هو التأثير الذي أردناه! لدينا الرسوم المتحركة بارد في التطبيق لدينا الآن

رائع ، إنها النهاية! هم .. هل هو؟

في الواقع ، يمكن أن نتوقف هنا وننهي المقالة. لقد حققنا ما أردنا. ولكن مهلا ، نحن مطورو Flutter ، نعلم أن كل شيء عبارة عن عنصر واجهة مستخدم في Flutter ، لذلك ... ربما هناك بالفعل عنصر واجهة مستخدم يمكنه جعل هذه المهمة "didUpdateWidget ()" كاملة بالنسبة لنا؟ بالطبع هو كذلك.

اسمحوا لي أن أقدم لكم AnimatedContainer:

حاوية تغير قيمها تدريجياً على مدار فترة زمنية. سيتم تنشيط AnimatedContainer تلقائيًا بين القيم القديمة والجديدة للخصائص عند تغييرها باستخدام المنحنى والمدة المقدمة.

AnimatedContainer هو ، كما يوحي الاسم ، عنصر واجهة مستخدم حاوية يمكن تحريكه. إذا كنت ترغب في قراءة المزيد حول هذا الموضوع (أو مشاهدة الرسوم المتحركة الرائعة التي يمكن القيام بها معها!) يمكنك التحقق من هذه المقالة الرائعة من Pooja Bhaumik: Flutter Animated Series: Animated Containers. حقيقة أن الأكثر إثارة للاهتمام بالنسبة لنا هو أن AnimatedContainer يستخدم أسلوب didUpdateWidget تحت الغطاء ، لذلك يقوم بكل المهمة بالنسبة لنا. دعونا نحاول ذلك!

سنقوم بشكل أساسي بإزالة جميع الشفرات المسؤولة عن التحكم في الرسوم المتحركة ووضع AnimatedContainer بدلاً من ذلك:

استيراد "الحزمة: رفرفة / material.dart" ؛
const CART_BAR_HEIGHT = 48.0 ؛
الطبقة CartValueBar يمتد StatelessWidget {
  عربة مزدوجة النهائيالقيمة ؛

  const CartValueBar ({Key key، this.cartValue})
      : السوبر (مفتاح: مفتاح) ؛

  @تجاوز
  إنشاء عنصر واجهة المستخدم (سياق BuildContext) {
    إرجاع AnimatedContainer (// تم تغييره
      الارتفاع: CART_BAR_HEIGHT ،
      المدة: المدة (بالميلي ثانية: 300) ، // تم تغييرها
      convert: Matrix4.translationValues ​​(// تغيير
          0 ، cartValue! = 0؟ 0: CART_BAR_HEIGHT ، 0) ، // تم تغييرها
      اللون: الألوان.
      الطفل: الصف (
        الأطفال:  [
          حشوة(
            الحشو: const EdgeInsets.symmetric (
                أفقي: 16.0) ،
            الطفل: النص (
              cartValue! = 0
                  ؟ "قيمة السلة $ {_ getCartValue ()} zł"
                  : '' ،
            )،
          )،
        ]،
      )،
    )؛
  }

  String _getCartValue () => cartValue.toStringAsFixed (2)؛
}
تحقق من شفرة المصدر الكاملة لهذا الملف على جيثب هنا.

كما ترى ، نحتاج إلى إضافة معلمتين إلى AnimatedContainer الذي تم إنشاؤه حديثًا - مدة الرسوم المتحركة وتحويل القيمة مع الترجمة. إذا كانت قيمة العربة مختلفة عن الصفر ، تكون قيمة الترجمة صفراً (الحاوية في أسفل الشاشة) ، إذا كانت قيمة العربة أكبر من الصفر ، فإن قيمة الترجمة تساوي ارتفاع الشريط (الحاوية أسفل الشاشة). كلما تم تحديث هذه الأداة (عن طريق إرسال حدث متجر جديد بواسطة Redux) ، فإن AnimatedContainer يغير قيم التحويل باستخدام رسوم متحركة لطيفة!

انها النهاية!

حسنًا ، هذه هي النهاية الحقيقية لهذه المقالة.

خلاصة القول ، نحن نعرف الآن كيفية إنشاء رسوم متحركة بسيطة يمكن أن تكون مفيدة عندما يكون لديك Redux architecture. نحن نعرف أيضًا المزيد عن AnimatedContainer. الآن حان دورك لتحويل هذه المعرفة إلى رسوم متحركة رائعة مع Flutter!

إذا كنت تريد التحقق من رمز التطبيق الكامل ، فيمكنك العثور عليه هنا:

هذا كل شئ! شكرا لقراءة مقالي