f t G+

نظرة على الرسوم المتحركة في فلاتر - flutter Animation deep dive arabic

الرسوم المتحركة في فلاتر هي فقط طريقة لإعادة بناء الأدوات Widgets في كل إطار

 مقال مترجم بتصرف من مدونة Filip Hracek على medium.



 

قمت في العام الماضي بتسجيل إحدى الحلقات في سلسلة Flutter Animations، وسأقوم في هذا المقال بنشر نفس المحتوى لأولئك الذين يفضلون النص على الفيديو.

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

لنبدأ بشيء بسيط وخفيف:

ماذا تكون الحركة في الحقيقة؟

كما ترى ، الحركة مجرد وهم. انظر إلى هذا:

فيديو لفيليب يلوح بيده.


ما تراه في الواقع هو العديد من الصور الثابتة التي تظهر بتتابع سريع. هذه هي الطريقة التي تعمل بها الأفلام. 

في السينما تسمى كل صور إطارا -ولأن الشاشات الرقمية بنفس الطريقة- يطلق عليها أيضًا الإطارات. تعرض السينما عادة 24 إطارًا في الثانية. وتعرض الأجهزة الرقمية الحديثة 60 إلى 120 إطارًا في الثانية.
 

إذا، قد تسأل؟ إذا كانت الحركة كذبة، فما الذي تفعله كل هذه الأدوات AnimationFoo و FooTransition؟ الإجابة: بالتأكيد ، نظرًا لأنه يلزم إنشاء الإطارات حتى 120 مرة في الثانية ، فطبعا لا يمكن إعادة بناء واجهة المستخدم في كل مرة.

أم أن ذلك ممكن😋؟؟

خذالأمور ببساطة، لأانها بالبساطة التي تتوقعها، الرسوم المتحركة في فلاتر هي فقط طريقة لإعادة بناء الأدوات Widgets في كل إطار، أي إن افترضنا ان التطبيق يعمل بـ 60 إطارا في الثانية، الwidget سيتم إعادة بناءها 60 مرة، بهذه البسطة، لماذا؟ لأن فلاتر سريعة كفاة لتقوم بذلك. جميع الأدوات تعمل بهذه الطريقة، لا يوجد استثناء.

دعونا نلقي نظرة على إحدى الأدوات الأساسية للرسوم المتحركة في فلاتر:  AnimatedBuilder.

هذه القطعة عبارة عن AnimatedWidget، والتي تدعمها _AnimatedState، نستمع إلى الرسوم المتحركة (نقصد بذلك انتظار الحدث) في initState() ، وعندما تغير قيمتها (نتحقق من ذلك عند تلقي الحدث) ، فإننا ننادي الدالة setState() التي ستقوم بإعادة بناء الwidget في الإطار التالي.

استدعاء setState  في كل إطار

وها أنت ذا. الرسوم المتحركة في Flutter هي مجرد تتابع سريع لتحديثات بعض الأدوات، 60 إلى 120 تحديث في الثانية.

ويمكنني إثبات ذلك. المثال التالي يحدث النص من 0 إلى سرعة الضوء في مدة ثانية، على الرغم من أن نص إلى أن الفكرة ذاتها:


 

دعنا نستخدم Flutter لبناء تلك الرسوم المتحركة:

عادة نستعمل  TweenAnimationBuilder  لكن في هذا المثال سنستعمل فقط setState.

Ticker

دعنا نتحدث عن Ticker.

غالبا لن تستعمله مطلقا بشكل مباشر. لكني أعتقد أنه لا يزال من المفيد التحدث عنه - حتى لو كان ذلك فقط لإزالة الغموض عنها.

ببساطة الـTicker يقوم بتنفيذ دالة 60 مرة في ثانية تقريبا. وهو عدد الإطارات.

var ticker = Ticker((elapsed) => print('مرحبًا'));
ticker.start();

في هذه الحالة ، نطبع "مرحبًا" في كل إطار. باعتراف الجميع ، ستون "مرحبًا" في الثانية ليس مفيدًا جدًا 😂.

أيضًا ، لقد نسينا استدعاء ticker.dispose() ، لذا سيستمر Ticker في طبع مرحبًا إلى الأبد ، أوحتى نغلق التطبيق، لهذا السبب تقدم فلاتر SingleTickerProviderStateMixin الذي يقوم بهذا الأمر تلقائيا.

هذا الMixin يقوم بإدارة ال Ticker لذا ليس عليك أن تقلق بشأن أي شيء.  ليس عليك سوى أن تضيفها للstate الخاصة بك:


class _MyWidgetState extends State<MyWidget> with SingleTickerProviderStateMixin<MyWidget> {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

الآن الwidget الخاصة بك لديها Ticker. ليس عليك الآن سوى استخدامه، يمكنك ان تستعمله مع  AnimationController.

class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin<MyWidget> {
  AnimationController _controller;  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this);
  }  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

كما ترى، الـ AnimationController  يحتاج إلى Ticker حتى يعمل.

إذا كنت تستخدم SingleTickerProviderStateMixin أو TickerProviderStateMixin ، فيمكنك فقط تمرير "this" إلىvsync في  AnimationController ،هذا كل شيء.

AnimationController

AnimationController هو ما تستخدمه عادةً لتشغيل الرسوم المتحركة وإيقافها مؤقتًا وعكسها وإيقافها. كذلك نسبة التقدم في الرسوم المتحركة مثلا، 60% من وقت مضى...باختصار فهو يساعد في إدارة الرسوم في فلاتر، بدل القيام بكل تلك الأمور بشكل يدوي.

في العادة ستستعمل AnimationControllerوتقوم بتحويلها باستعمال الCurve وتضعها من خلال Tween، هذا سيجعلها جاهزة للإستخدام على أحد الأدوات الجاهزة مثل  FadeTransition أو TweenAnimationBuilder

كما ذكرت سابقا سنستعمل setState فقط في هذا المثال.

setState

بعد أن نقوم بتركيب AnimationController، يمكننا انتضار حدث منه. عند تلقي حدث سننادي setState.


class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin<MyWidget> {
  AnimationController _controller;  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this);
    _controller.addListener(_update);
  }  void _update() {
    setState(() {
      // TODO
    });
  }  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

الآن نحن بحاجة متغير لنخزن فيه الرقم الذي سيكتب داخل النص، بحيث في كل مرة نقوم بتحديث هذا المتغير ثم نعيد بناء الwidget.


class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin<MyWidget> {
  AnimationController _controller;  int i = 0;  @override
  void initState() {
    super.initState();
    _controller = AnimationController(vsync: this);
    _controller.addListener(_update);
  }  void _update() {
    setState(() {
      i = (_controller.value * 299792458).round();
    });
  }  @override
  Widget build(BuildContext context) {
    return Text('$i m/s');
  }
}

يقوم هذا الرمز بتحديث النص من الصفر إلى سرعة الضوء، اعتمادًا على نسب تقدم الـAnimation.

تشغيل الرسوم المتحركة

الآن سنحدد المدة الزمنية التي ستستغرقها الرسوم المتحركة، ونشغلها.

class _MyWidgetState extends State<MyWidget>
    with SingleTickerProviderStateMixin<MyWidget> {
  AnimationController _controller;  int i = 0;  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this
      duration: const Duration(seconds: 1),
    );
    _controller.addListener(_update);
    _controller.forward();
  }  void _update() {
    setState(() {
      i = (_controller.value * 299792458).round();
    });
  }  @override
  Widget build(BuildContext context) {
    return Text('$i m/s');
  }
}

الرمز forward() يقوم ببدأ تتشغيل الرسوم المتحركة، قمنا بوضعه داخل الinitState بحيث بدأ مباشرة عند بدأ الwidget.

Disposing of the controller

ولا تنسَ التخلص من AnimationController. وإلا سيكون لديك استهلاك غير ضروري للذاكرة في تطبيقك.

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

ربما عليك فقط استعمال الأدوات المدمجة مع فلاتر؟

الهدف من الأدوات المدمجة في فلاتر هو تسهيل العملية.

القيام بكل ذلك بنفسك ليس بالأمر الرائع. يمكن القيام بنفس ما فعلناه باستخدام TweenAnimationBuilder في عدد أقل من الأسطر، ودون الحاجة إلى التوفيق بين AnimationController وsetState.

class MyPragmaticWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return TweenAnimationBuilder(
      tween: IntTween(begin: 0, end: 299792458),
      duration: const Duration(seconds: 1),
      builder: (BuildContext context, int i, Widget child) {
        return Text('$i m/s');
      },
    );
  }
}

ملخص

لقد رأينا ما هو الـ Ticker وماذا يعني حقًا. رأينا كيفية تلقي الحديث يدويًا من AnimationController. ورأينا أن الرسوم المتحركة هي مجرد عمليات إعادة بناء سريعة ومتتالية Widgets.

هذا كل شيء بالتوفيق.

مقالات في هذه السلسلة: