غالبية الألعاب وكذلك بعض التطبيقات تتطلب تحريك صور وشخصيات رسومية خلال الشاشة، تحريك الشخصيات يتم من خلال رسم الشخصية بحالات الحركة المطلوبة (sprite) ثم عرض تلك الرسومات واحدة تلو الأخرى لتعطي إيحاء الحركة كالصورة التالية: وأما تحريك الخلفيات فغالبا يتم من خلال إنشاء صورة خلفية اكبر من حجم مساحة العرض ثم عرض جزء من الصورة وتغيير احداثياتها لتبدو كأنها تتحرك.
يوفر الاندرويد عنصر للصور (ImageView) لكن ذلك مخصص لعرض الصور الثابتة أو التي لا تتطلب تغييرات سريعة ولا يمكن استخدامه للصور المتحركة لأنه بطيء. للتعامل مع الصور المتحركة نحتاج إلى الرسم مباشرة على canvas (سطح الرسم) لكن توجد طريقة تبسط العملية وهي باستخدام عنصر SurfaceView والذي يمكنك من الرسم المباشر على canvas وبطريقة سهلة. عملية التحريك تحتاج أن تعمل في الخلفية لكي لا تعطل عمل التطبيق (لفهم عمليات الخلفية راجع الشرح السابق). لتنظيم العمل سنجعل النص الخاص بالحركة في كلاس (class) خاص سنسمية AnimGuy:
1 | public class AnimGuy extends SurfaceView implements Runnable { |
عملنا الكلاس كامتداد لكلاس SurfaceView وكذلك واجهة لكلاس Runnable وذلك لتشغيله في الخلفية كما سنرى لاحقا. في البداية نقوم بتحميل الصورة وهي عبارة عن ملف واحد مرسوم به إطارات متساوية في المقاس تمثل مراحل الحركة للشخصية (خطوات الركض). لدينا في تلك الصورة 6 إطارات فنحتاج عرضها واحد تلو الآخر إلى الأخير ثم نعود للإطار الأول وهكذا. الدالة run()
هي التي نستخدمها لتحريك تلك الإطارات وهي تعمل في الخلفية. لنلقي نظرة على تلك الدالة:
1 |
|
نلاحظ أولا أننا نستخدم حلقة while
بمتغير ثنائي animation
للتحكم باستمرارية الحلقة، ونتحكم بقيمة هذا المتغير كما سنرى لاحقا لبدء ووقف التحريك. في داخل الحلقة نتأكد أولا من جاهزية السطح للرسم isValid()
(في بعض الحالات مثل لحظة بدأ التطبيق لا يكون سطح الرسم جاهزاً بعد ومحاولة الرسم عليه ستؤدي إلى فشل التطبيق)، في حال كان جاهزاً نقوم بقفل السطح lockCanvas()
لنظمن أن لا احد غير هذه الدالة يستطيع الرسم عليه. بعد ذلك نقوم بمسح محتوى سطح الرسم لكي لا تبقى به رسومات سابقة. نبدأ الآن بالرسم، أولا نحتاج رقم الإطار المراد رسمة وهو محفوظ في المتغير frame
وفي البداية فيمته صفر فتكون الإحداثيات على المحور س تساوي صفر أي من أول الصورة. بعد ذلك نقوم بنسخ مساحة مستطيلة Rect
من الصورة الأصلية تبدأ من س إلى س+عرض الإطار، أما الارتفاع (محور ص) فهو بكامل ارتفاع الصورة (يبدأ من صفر إلى ارتفاع الصورة). بعد ذلك نحدد المساحة التي سنرسم عليها dst
في المثال هذا وللتبسيط جعلنا مساحة سطح الرسم مساوية لمساحة الإطار الواحد، لذلك فإن منطقة الرسم وحجمها يساوي المساحة الكاملة (يبدأ من الصفر إلى طول وعرض الإطار). نقوم الآن بالرسم بالأمر drawBitmap
إذ نمرر له الصورة الكاملة ومعها إحداثياتها ومساحة الإطار المراد نسخة وكذلك إحداثياتها ومساحة المنطقة المراد الرسم عليها. نقوم بعدها بزيادة رقم الإطار ليكون جاهزا لعملية الرسم التالية (لحساب رقم الإطار نستخدم طريقة المعامل الرياضي فعدد الإطارات هو 6 فعندما يبلغ العداد ذلك الرقم يعود من جديد للعد من 0). في الأخير نقوم بفك القفل ورسم الإطار unlockCanvasAndPost
. للتحكم بسرعة تحريك الإطارات نستخدم Thread.sleep()
والتي تجعل عملية الخلفية تنام (أو تتوقف عن العمل) لمدة زمنية تحسب بأجزاء الثانية، فهذه القيمة تعتمد على عدد الإطارات وكذلك السرعة الحركة المناسبة. لرسم الإطار الذي يليه (رقم 1) نقوم بنفس الخطوات وإحداثيات الإطار تبدأ من نهاية الإطار السابق.
لاستخدام هذا الكلاس نهيئه أولا من الكلاس الرئيسي بالأمر التالي:
1 | animGuy = new AnimGuy(this); |
ويتم تشغيل وايقاف الحركة بالأمرين التاليين:
1 | animGuy.resume(); |
التطبيق الكامل (تجده هنا) فيه تنفيذ المثال المشروح هنا وكذلك تجد فيه كلاس لمشهد تحريك متكامل (كما في الفيديو بالأعلى) يشمل الشخصية السابقة وكذلك أرضية تتحرك بالاتجاه المعاكس وخلفية تتحرك كذلك بالاتجاه المعاكس لكن بسرعة اقل لتعطي إيحاء بالبعد. بالنسبة للأرضية فهي صورة لإطار واحد، نقوم أولا بتكراره إلى أن يملا عرض الشاشة ثم نقوم بتحريكه قليلا في الاتجاه المعاكس لحركة الولد إلى أن يصل الإطار الأول لطرف الشاشة ثم نعيده لموقعة الأول. أما بالنسبة للخلفية فنقوم أولا بتعديل حجمها ليناسب حجم سطح الرسم ثم نقوم بعمل مشابه لما قمنا به للأرضية.
للاستفسار عن هذا الشرح أو التطبيق يمكنك مراسلتي على تويتر