يعد التحريك باستخدام مكتبة React Native من المواضيع الأساسية المطروحة في أقسام الدراسة أو ورشات العمل، ربما لكون عدد من المطورين يجدون تحديًا في استخدامها. في الوقت الذي تركّز فيه الكثير من المدونات والمصادر في الويب على جوانب الأداء عند استخدام React Native، لا يخوض إلا عدد قليل منها في الأساسيات. سنعرض في هذه المقالة لأساسيّات تطبيق التحريكات باستخدام مكتبة React Native.
أولًا سنلقي نظرة على خلفية المكتبة وتاريخها.
بداية التحريكات في React Native وتطوّرها
عند تشغيل برنامج عابر للمنصّات مكتوب بلغة جافاسكريبت تحتاج مكوّنات React Native على هاتفك إلى تبادل المعلومات بواسطة عنصر يسمّى الجسر (Bridge). التبادل عبر هذا العنصر غير متزامن (Asynchronous)، وهو ما يجعل التطبيقات المبنية على إطار العمل React Native تصبح بطيئة لأن الطلبات غير المتزامنة المارّة على الجسر تسدّ المسار أمام شفرة جافاسكريبت التي تتفاعل مع أجزاء إطار العمل.
للحصول على أداء عال يجب أن تصيّر (Render) التحريكات على خيط (Thread) واجهة المستخدم الأصلي في نظام التشغيل، وبما أن البيانات تحتاج للسَّلسَلة (Serialization) عبر الجسر فإن ذلك يؤدّي غالبًا إلى منع خيط جافاسكريبت، ممّا يتسبّب في نقص عدد الإطارات (Frame) على الشاشة. بقي هذا المشكل حاضرًا منذ 2015 حين كانت التحريكات تمثّل واحدة من العقبات الكبرى أمام إطار العمل React Native.
لحسن الحظ، تحسّنت الحالة منذ ذلك الوقت بفضل الدعم الكبير من أعضاء مجتمع المطورين، إذ أصبح من الشائع الآن إنجاز 60 إطارًا في الثانية الواحدة (Frame per second, FPS) في عمليات التحريك ضمن React Native. سهّلت واجهات تطبيقات برمجيّة تصريحيّة (Declarative API) مثل Animated عمليّة تنفيذ التحريكات التفاعلية.
استخدام واجهة التطبيقات البرمجية Animated لتحسين الأداء
يواجه المطورون حتى الآن العديد من القضايا المتعلقة بالأداء خاصة عندما يعملون على رسومات متحركة معقّدة.
كما ذكرنا سابقًا، تتسبب الاختناقات في الأداء التي تحدث عند تنفيذ الرسوم في React Native، بأعباء عمل كبيرة على خيوط جافاسكربت (JavaScript threads)، مما يقلّل من معدل إظهار الإطارات، وبالتالي يتسبّب في البطء عند استخدام البرنامج، وللتغلب على هذه المشكلة نحتاج إلى الحفاظ على معدل 60 إطارًا في الثانية الواحدة.
إن أفضل حل للحفاظ على معدّل الإطارات بالثانية هو استخدام Animated API لأنها تحسّن الزمن المطلوب للسَّلسَلة وعكسها (Serialization/Deserialization). تعمل Animated API عبر استخدام واجهة تطبيقات تصريحية لوصف التحريكات. الفكرة التي تقوم عليها هي التصريح بالتحريك كاملًا مرةً واحدة مقدّمًا لكي تُمكِن سَلسَلة التصريح في جافاسكريبت وإرسالها إلى الجسر. ينفّذ مشغّلٌ (Driver) التحريكاتِ إطارًا بعد الآخر.
تنفيد التحريكات ضمن React Native
توجد طرق عدة لتطبيق التحريك في React Native. في ما يلي الطرق التي أجد أن لديها فائدة أكبر.
القيم المتحرّكة Animated values
يتربّع تحريك القيم على قائمتي بوصفه لبنة أساسيّة للتحريكات في تطبيقات React. تشير هذه القيم عمومًا إلى قيم حقيقة تُحوَّل إلى أعداد حقيقية عند تمريرها مع مكوّن مُحرَّك.
فلننظر إلى المثال التالي.
Animated.timing(this.valueToAnimate, { toValue: 42; duration: 1000; }).start()
نلاحظ في المثال السابق أننا أسندنا القيمة 42 إلى value.ToAnimate
والتي ستنفّذ بعد 1000 ميلي ثانية.
كما يمكن التصريح عن قيم خاصيات التحريك الأخرى مثل الشفافية opacity
أو الموضع position
. في ما يلي مثال تطبيقي على الشفافية بقيم متحرّكة.
<Animated.View style={{ opacity: myAnimatedOpacity }} />
مشغلات التحريك: Animated.timing و Animated.event و Animated.decay
انظر إلى المشغّلات على أنها عُقد في مخطّط بياني (Graph) تغيّر قيمةً متحرّكة مع كل إطار. على سبيل المثال، ستزداد القيمة المسندة إلى Animated.timing
، بينما ستنقص القيمة المسندة إلى Animated.decay
في كل إطار. لنلق نظرة على المثال التالي:
Animated.decay(this.valueToAnimate, { velocity: 2.0, deceleration: 0.9 }).start();
في هذا المثال، ستنفَّذ الرسوم المتحركة انطلاقًا من سرعة محددة بالقيمة 2.0
، ثم تتناقص تدريجيًا خلال الوقت المحدَّد. أصبحت هذه الطريقة شائعة في التطبيقات العابرة للمنصّات مع بداية ظهور التوثيقات عن التصميم المسطّح (Material design). تبدو التحريكات بمظهر جيّد، وتوجد طرق عدّة لجعل التجربة لا تنسى.
يمكن كذلك استخدام Animated.event
لتشغيل قيمة عند تمرير المستخدم:
<ScrollView onScroll={Animated.event( [{nativeEvent: {contentOffset: {y: this.state.scrollY}}}] )} > </ScrollView>
في المثال السابق يُعيد Animated.event
دالّة تعدّل قيمة الخاصيّة nativeEvent.content.offset.y
في المكوّن scrollView
لتأخد قيمة scrollY
الحالية.
عمومًا، يمكن استخدام مشغلّات التحريك مع القيم المتحركة أو مع مشغّلات أخرى.
ملحوظة جانبية: انتبه إلى أنه عندما يحدّث المشغّل كل إطار فإن القيمة الجديدة تحدّث مباشرةً قيمة الخاصيّة View
، وبالتالي يجب أن تكون حذرًا عند التصريح بالمتغيّرات وتنتبه إلى نطاقها أثناء استخدامها.
توابع التحويل Transform methods
تتيح لك توابع التحويل تحويل قيمة متحرّكة إلى قيمة متحرّكة جديدة. يمكنك استخدام توابع مثل Animated.add()
وAnimated.multiply()
وAnimated.interpolate()
لتنفيذ عمليّات تحويل بين القيم. يمكنك تنفيذ عمليّة تحويل على أي عقدة في المخطّط البياني قيد التحريك كالتالي:
newAnimated.Value(55).interpolate(.....) // Transformation operation using Animated.interpolate() method
تحريك الخاصيّات
الخاصيّات المتحرّكة هي عقد خاصّة تربط بين القيم المتحرّكة وخاصيّة في مكوّن. تُولَّد تلك الخاصيّات عند تصيير الخاصيّة Animated.view
وإسناد خاصيّات لها.
فلننظر إلى الشفرة التالية:
Var opacity = new Animated.Value(0.7); <Animated.View style={{ opacity }} />
أضفنا خاصيّة متحرّكة تُحوِّل القيمة 0.7
إلى خاصية. في حال حدّث التابع تلك القيمة، سينعكس التغيير على خاصية View
.
تعمل التوابع الموصوفة أعلاه أساسًا بالتزامن مع الكائنات المتحرّكة (Animated objects) في React، كما أن توابع التحويل تؤدّي دورًا أساسيًّا في عمل تلك الكائنات.
يغيّر مشغّل التحريك (Animated.Timing
أو Animated.Event
أو Animated.Decay
) قيم التحريك في كل إطار ثم تُمرر النتيجة عبر أي تابع من توابع التحويل لتُخزَّن بصيغة خاصية عرض (الشفافية Opacity أو قيمة التحويل).
يسلم مفسّر جافاسكريبت النتيجة إلى Native React حيث تُحدَّث الواجهة من خلال استدعاء التابع setNativeProps
. في الأخير تُمرَّر الخاصيّة إلى iOS أو Android حيث يتسلم المكوّن UIView
أو Android.View
التحديثات.
تنفيذ الرسوم المتحركة باستخدام Animated API و Native Driver
تشرف مشغلات جافاسكريبت منذ ظهور Animated API على عملية تنفيذ التحريك، ولكن هذه الطريقة سببت تباطؤًا في عملية التحريك بسبب قلة عدد الإطارات المعروضة في الثانية ويعود السبب في ذلك أن العمل كله يتم في الخيط (Thread) الخاص بجافاسكريبت.
لتجاوز هذه المشكلة أضيفت مشغلات خاصة بإطار العمل React Native وبالتالي أصبحت قادرة على تنفيذ عملية التحريك كاملةً، إطارًا بعد الآخر، ضمن React Native.
عند استخدام المشغل الخاصة بإطار العمل React native مع Animated API فإن ذلك يسمح بتحديث العرض مباشرة دون الحاجة إلى إعادة حساب القيم مرة أخرى في جافاسكربت.
لاستخدام المشغّل الخاص بإطار العمل React يجب تعيين القيمة true
إلى الخاصيّة useNativeDriver
أثناء ضبط التحريكات.
useNativeDriver: true
استخدام PanResponder للتعامل مع لمس الشاشة في React Native
يمكن لواجهة التطبيقات Animated API تنفيذ غالبية الأعمال الروتينية عند عمل التحريكات في React Native، إلّا أنها محدودة في التعامل مع لمس الشاشة للتفاعل مع التحريكات، فهي غير قادرة على التجاوب مع الحركات التي تحدث خارج المكوّن ScrollView
.
رغم أنه يمكن تنفيذ أشياء كثيرة عن طريق مكوّن ScrollView
بسيط، إلّا أن التطبيقات على الأجهزة الجوّالة تبقى ناقصة بدون لمس الشاشة التي يستعملها المستخدم للتفاعل مع التحريكات عن طريق التمرير أو التدوير على سبيل المثال.
يمكن التعامل مع حركات اللمس في React Native بسلاسة باستخدام PanResponder مع Animated API.
يدمج PanResponder لمسات عدّة في حركة خاصّة، ويجعل لمسة واحدة تتجاوب مع لمسات إضافية لكي تعمل حركات اليد بسلاسة.
افتراضيًّا، يتمثّل PanResponder في مقبض InteractionManager يمنع الأحداث العاملة على خيط جافاسكريبت من تعطيل حركات اللمس.
تحسين زمن التشغيل لانتقالات التصفح البطيئة
عادةً يحتاج أي تحريك يستدعي الانتقال من شاشة إلى أخرى في React Native إلى استخدام مكوّنات التصفّح. تُستخدَم مكوّنات التصفّح مثل React Navigation عادةً للانتقالات أثناء التصفح.
تحدث الانتقالات أثناء التصفّح في React Native عادةً في خيوط جافاسكريبت، ممّا قد يتسبّب في انتقالات بطيئة في الأجهزة الرخيصة أو ذات الذاكرة المحدودة (أجهزة أندرويد أساسًا إذ أن الأجهزة العاملة بنظام iOS تتعامل مع الأمر بفاعلية أكبر). تحدث انتقالات التصفح البطيئة عادة عندما يحاول React Native تصيير شاشة جديدة في حين أنه ما زالت تُنفَّذ تحريكات في الخلفية.
لتفادي حالات مثل تلك المذكورة أعلاه، يسمح مدير التفاعلات (InteractionManager) بجدولة الأنشطة التي تأخذ وقتًا في التنفيذ بجدولتها بعد تنفيذ تحريك أو تفاعل في خيط جافاسكريبت.
تحريك التخطيطات Layouts
تعمل واجهة التطبيقات البرمجية LayoutAnimation على تحريك الشاشة تلقائيًّا إلى الوضعية الموالية عندما يحدث التخطيط (Layout) الموالي. تُنفَّذ واجهة التطبيقات تلك ضمن خيط واجهة المستخدم (UI Thread) وهو ما يجعلها ذات أداء عال.
تُطبَّق التحريكات المعدَّة عن طريق LayoutAnimation، فور استدعائها، على كل المكوّنات؛ بخلاف واجهة Animated التي يمكنك عن طريقها التحكّم في القيم المخصوصة بتحريكها. يمكن لواجهة LayoutAnimation تحريك كل شيء في التصيير القادم، لذا يجب استدعاؤها قبل استدعاء setState
.
يضمن إعداد تحريك للتخطيط قبل استدعاء setState
تحريكات سلسة في خيط Native ويحول دون تأثر التحريكات بتنفيذ شفرة برمجية عند إطلاق دور setState
أخرى (وهو ما يتسبّب - في الظروف العادية - في تهديد تحريكات تطبيقك).
توجد طريقة أخرى لاستخدام LayoutAnimation وتتمثّل في استدعائها داخل التابع WillReceiveProps
. استدع بكل بساطة التابع LayoutAnimation.configureNext
مع تمرير الوسائط المناسبة لإعداد التحريك، كما هو موضَّح أدناه:
LayoutAnimation.configureNext(animationConfiguration, callbackCompletionMethod); this.setState({ stateToChange: newStateValue });
لا تدعم واجهة LayoutAnimation سوى خاصيّتين هما الشفافية (Opacity) وقابلية التكيّف (Scalability).
تتعرّف الواجهة على العروض (Views) باستخدام مفاتيحها الفريدة وحساب وضعيّتها المتوقّعة. علاوة على ذلك، تحرّك تغيّرات الإطارات ما دام العرض يحتفظ بالمفتاح نفسه بين تغيّر الحالات.
تعمل التحريكات المطبَّقة بواسطة LayoutAnimation ضمن الخيط الأصلي لواجهة المستخدم في نظام التشغيل، وهو أمر جيّد من ناحية الأداء، إلّا أنه يمثّل تحديًّا إذا دعت الحاجة لتحريك كل الخاصيّات بين حالات الإطارات.
خاتمة
لا يدخل هذا المقال في تفاصيل التحريكات في React Native ويكتفي بأمور سطحية، إلّا أن هناك قاعدة أساسية عند التعامل مع إطار العمل React Native وهي استخدام واجهة التطبيقات البرمجية Animated API ما دام ذلك ممكنا. بالنسبة لحركات اللمس، استخدم PanResponder بجانب Animated API.
يمكن التغلّب على الكثير من المشاكل التي تواجهها بالاستفادة من مشغّل Native driver إلى جانب واجهة Animated. إنْ استمرّت مشاكل الأداء رغم ذلك فالزم LayoutAnimation.
ترجمة - وبتصرّف - للمقال Getting started with React Native animations لصاحبه Rakshit Soral.
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.