
مقدمة
إن وجود الحركة يضيف إلى تطبيق أندرويد جمالية خاصة ويساعد في تحسين تجربة المستخدم، ويمكن استخدامها في بعض الألعاب البسيطة (لبناء الألعاب الكبيرة استخدم محركات الألعاب مثل Unity).
ماذا نقصد بالحركة في تطبيقات أندرويد؟
تحريك أحد العناصر الموجودة على واجهة المستخدم مثل جعل صورة أو نص يتحرك من اليسار إلى اليمين.
ماهي الحركات المتوفرة في Android SDK
يتم تنفيذ الحركات باستخدام التحويلات الهندسية وهي:
الانسحاب Translation
هو نقل عنصر معين في واجهة المستخدم من نقطة إلى نقطة أخرى ويكون إما على المحور X أو المحور Y أو المحورين XY معاً (أيضاً يمكن الانسحاب على المحور Z ولكن لم أقم بشرح ذلك).
الدوران Rotation
هو دوران العنصر حول مركزه بزاوية معينة.
تغيير القياس Scaling
هي زيادة أو إنقاص حجم العنصر بنسبة معينة، مثلاً زيادة حجم العنصر بمقدار مرة ونصف.
التقنيات المستخدمة في تحقيق الحركة
View Animations
هذا النوع من الحركات يمكن تطبيقه على أي كائن من نمط (View)، يشمل تغير موقع الكائن(انسحاب) وتدوير الكائن وتغيير الحجم والشفافية، يتم تحقيقه باستخدام ملفات XML وكود الجافا (Google, 2017).
Property Animations
تعتمد هذه التقنية على تغيير خصائص الكائن عبر الزمن بشكل يسبب الحركة، وهي ليست خاصة بالكائنات من نمط (View)، تم إضافتها في نسخة أندرويد 3.0 (Honeycomb) (Google, 2017).
Frame Animations
يستخدم طريقة الحركة التقليدية والتي تعتمد على وجود مجموعة من الصور التي تظهر بتسلسل معين وبسرعة معينة بحيث تبدو وكأنها تتحرك (مثل أفلام الكرتون)، مدعومة في جميع إصدارات أندرويد (Google, 2017).
Transition Animations
تم إضافتها في نسخة أندرويد 4.4 (Kitkat)، يتم تعريف الحركات من خلال ملفات XML واستدعائها من خلال كود الجافا، يمكن استخدام نفس الحركة مع أكثر من كائن (إعادة استخدام)، يتم تطبيقها فقط على الكائنات من نمط (Google, 2017)View.
في هذه المقالة سأشرح كيفية تطبيق View Animations من خلال كود جافا فقط وبدون استخدام ملفات XML نظراً لسهولة استخدام هذه الطريقة ودعمها لمعظم أنظمة أندرويد.
View Animation
إن هذا النوع من الحركة يمكن تطبيقه على الكائنات من نمط View والتي قد تكون ImageView أو TextView أو أي View آخر.
سأشرح هذه الفكرة من خلال تطبيق الحركة على صورة (سيارة) باستخدام كائن من نمط ImageView.
الصف Animator
هو صف يستخدم لتحريك كائن من نمط View من خلال مجموعة من الدوال أذكر منها:
tranlsateX,translateY
تستخدم هاتان الدالاتان لتطبيق انسحاب على أي كائن من نمط View حيث الأولى للانسحاب على المحور X والثانية للانسحاب على المحور Y.
يوجد دالة للانسحاب على المحور Z لكنها تحتاج نسخة أندرويد Lollipop على الأقل.
تأخذ جميع هذه الدوال وسيط واحد يعبر عن مقدار الانسحاب بالبيكسل.
scaleX, scaleY
من اسم الدالة واضح أنها تستخدم لتغير القياس وتأخذ وسيط وحيد يعبر عن مقدار التغير، مثلاً القيمة 1.5 تعني أن حجم العنصر يصبح نفس حجمه الحالي مضروباً بالقيمة 1.5، ولتصغير العنصر يستخدم قيمة سالبة.
rotate
لتدوير العنصر بزاوية معينة، مثلاً 180 يعني دوران العنصر دورة كاملة.
setAlhpa
لتغيير شفافية العنصر بمقدار إلى مقدار معين يتم تمريره كوسيط، القيمة 0 تجعل الكائن غير ظاهر، والقيمة 1 تعني أن العنصر ظاهر بشكل كامل (بدون شفافية).
setDuration
تستخدم هذه الدالة مع الدوال السابقة لتحديد مدة الحركة مقدرة بالميلي ثانية، مثلاً استدعاء الدالة rotate وتمرير القيمة 180 واستدعاء الدالة setDuration وتمرير القيمة 2000 يعني أن يستغرق العنصر ثانيتين كاملتين للدوران حول مركزه بزاوية 180 درجة.
كيف يتم استخدام الصف Animator
يتم استخدام هذا الصف من خلال استدعاء الدالة Animate الموجودة في الصف View، على سبيل المثال لتحريك عنصر من نمط ImageView وليكن iv، نقوم باستدعاء الدالة iv.animate فنحصل على كائن من نمط Animator وليكن an
نقوم بعدها باستدعاء دوال الحركة مثل an.scaleX وan.translateX …
مثال عملي
من خلال برنامج أندرويد ستوديو قم بإنشاء الواجهة التالية

استخدم أي صورة تناسبك… ويكون ملف activity_main.xml بالشكل التالي
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/cartIV"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:src="@drawable/car"
android:text="Hello World!" />
<LinearLayout android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/button"
android:layout_width="110dp"
android:layout_height="wrap_content"
android:onClick="runTranslate"
android:text="translate" />
<Button
android:id="@+id/button2"
android:layout_width="110dp"
android:layout_height="wrap_content"
android:onClick="runScale"
android:text="scale" />
</LinearLayout>
<LinearLayout android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/button1"
android:layout_width="110dp"
android:layout_height="wrap_content"
android:onClick="runTotate"
android:text="rotate" />
<Button
android:id="@+id/button3"
android:layout_width="110dp"
android:layout_height="wrap_content"
android:onClick="runFade"
android:text="fade" />
</LinearLayout>
</LinearLayout>
الانسحاب
لتطبيق الانسحاب على الصورة iv يمكن استخدم الكود التالي:
im.animate()
.translationX(500)
.translationY(500)
.setDuration(duration)
.setListener(listener)
.start();
الكود السابق يؤدي لانسحاب على المحور X بمقدار 500 بيكسل والمحول Y بنفس المقدار، المتحول duration يحدد مدة الحركة وفي مثالي استخدمت القيمة 2000 أي ثانيتين.
المتحول listener سأقوم بشرحه لاحقاً.
الدوران
im.animate()
.rotation(180)
.setDuration(duration)
.setListener(listener)
.start();
الكود السابق ينفذ دوران 180 درجة
تغيير القياس
im.animate()
.scaleX(2f)
.scaleY(2f)
.setDuration(duration)
.setListener(listener)
.start();
تغير قياس الصورة إلى الضعف.
تغيير الشفافية
im.animate()
.alpha(0.2f)
.setDuration(duration)
.setListener(listener)
.start();
ملاحظة
الدالة start يتم استدعاءها لبدء الحركة.
تتبع مراحل الحركة
إن الشيفرات السابقة تقوم فقط بتنفيذ الحركة، لكن في بعض الأحيان يحتاج المبرمج إلى تتبع حالة الحركة وتنفيذ أمور معينة في كل حالة.
على سبيل المثال أرغب في إعادة صورة السيارة إلى مكان الانطلاق بعد انتهاء الحركة، هذا الأمر لن يتم بشكل تلقائي بل أن بحاجة إلى استخدام واجهة خاصة تسمى AnimatorListener.
الواجهة AnimatorListener
تملك هذه الواجهة عدة دوال لتتبع حالة الحركة هي
- onAnimationStart يتم تنفيذ هذه الدالة عند بداية الحركة.
- onAnimationEnd يتم تنفيذ هذه الدالة عند نهاية الحركة وقد قمت باستخدامها في مثالي لإعادة العنصر إلى وضعه الأصلي بعد انتهاء الحركة.
- onAnimationCancel يتم تنفيذ هذه الدالة عن إلغاء الحركة باستخدام الدالة cancel الموجودة في الصف Animator.
- onAnimationRepeat يتم تنفيذ هذه الدالة عند تكرار الحركة إذا كان تكرار الحركة مدعوما من قبل الصف الخاص بالحركة، في مثالي استخدمت الصف ViewPropertyAnimator وهو لا يدعم تكرار الحركة بشكل افتراضي.
وبالاستفادة من هذه الواجهة قمت بكتابة الكود التالي بهدف إعادة العنصر إلى مكانه فور انتهاء الحركة
private Animator.AnimatorListener listener = new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
im.setY(20);
im.setX(20);
im.setRotation(0);
im.setScaleX(1);
im.setScaleY(1);
im.setAlpha(1f);
}
ويصبح الملف MainActivity.java بالشكل التالي:
package com.example.mutasem.animexample;
import android.animation.Animator;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;
public class MainActivity extends AppCompatActivity {
ImageView im;
final int duration=2000;
private Animator.AnimatorListener listener = new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animator) {
}
@Override
public void onAnimationEnd(Animator animator) {
im.setY(20);
im.setX(20);
im.setRotation(0);
im.setScaleX(1);
im.setScaleY(1);
im.setAlpha(1f);
}
@Override
public void onAnimationCancel(Animator animator) {
}
@Override
public void onAnimationRepeat(Animator animator) {
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
im = (ImageView) findViewById(R.id.cartIV);
im.setY(20);
im.setX(20);
}
public void runScale(View view) {
im.animate()
.scaleX(2f)
.scaleY(2f)
.setDuration(duration)
.setListener(listener)
.start();
}
public void runTranslate(View view) {
im.animate()
.translationX(500)
.translationY(500)
.setDuration(duration)
.setListener(listener)
.start();
}
public void runTotate(View view) {
im.animate()
.rotation(180)
.setDuration(duration)
.setListener(listener)
.start();
}
public void runFade(View view){
im.animate()
.alpha(0.2f)
.setDuration(duration)
.setListener(listener)
.start();
}
}
قم بتغيير الأرقام الموجودة في المثال ولاحظ الفروق التي تظهر في الحركة
رابط المشروع على GitHub
https://github.com/mutasemhajhasan/Anroid-Animation-01
المراجع
Google. (2017). Android Developers. Retrieved from Android Developers: developer.android.com