گامی فراتر در انیمیشن‌های اندرویدی با Scene

جمعه ۲۰ اردیبهشت ۹۸ توسط برنا جویا

سلام، من برنا جویا هستم و در این مقاله قصد دارم نحوه کار با Scene‌ در اندروید رو آموزش بدم.
احتمالا شما به عنوان یک توسعه دهنده اندروید تجربه استفاده از لایبرری‌ها با انیمیشن‌های ساده تا انیمیشن‌های پیشرفته‌تر رو داشتید و در این راه شاید سوال‌های زیادی فکر شما رو به خودش مشغول کرده! سوال‌هایی از قبیل اینکه چطور میشه این محدودیت در استفاده از یک لایبری رو کنار گذاشت و خیلی از رفتارهایی رو که دوست داریم در پروژه اعمال کنیم.

Scene یکی از اون ویژگی‌هاییه که محدودیت رو برای طراحی و استفاده از انیمیشن از بین میبره و کار رو سریع‌تر و آسون‌تر جلو می‌بره.

من قصد دارم این مبحث رو یا یک مثال توضیح بدم. خب من در حال کار روی یک پروژه آماری بودم که در اکتیویتی اصلی این پروژه برای اعمال انیمیشن‌ها از Scene استفاده کردم و قصد دارم از همون کدی که نوشتم در این مقاله استفاده کنم. با خوندن این مقاله شما یاد می‌گیرید انیمیشنی مشابه GIF زیر رو پیاده‌سازی کنید:

درابتدای کار اشاره کنم که Scene نیاز به Implement کردن کتابخونه‌ای نداره و از Transition خود اندروید ارث برده. همچنین بد نیست یه اشاره‌ای هم به معنای لغوی Scene داشته باشم که به معنای "صحنه" هست!

خب بریم سراغ پروژه!

به این نکته هم اشاره کنم که با توجه به اینکه چند تا از Layoutهای ما ساختار مشابهی دارن و قرار دادن تمامی کدهای پروژه در این مقاله بی‌جهت به حجم مقاله اضافه می‌کنه و باعث اذیت شدنتون میشه من سورس‌های این مقاله رو در Github قرار دارم که می‌تونید از طریق این لینک به این سورس‌ها دسترسی داشته باشید.

ما برای هر کدام از Sceneها و یا همان صحنه‌ها یک Layout ایجاد کردیم که به ترتیب نام‌های scene1.xml, scene2.xml, scene3.xml, scene4.xml رو به اونها اختصاص دادیم. نکته قابل توجه اینه‌ که این 4 صحنه ساختار مشابهی رو دارن و از کامپوننت‌های یکسانی با کمی تفاوت درون اونها استفاده شده که کد مربوط به اولین صحنه که مربوط به فایل scene1.xml هست رو می‌تونید در زیر مشاهده کنید:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.constraint.ConstraintLayout
        android:id="@+id/chart1"
        android:layout_width="0.1dp"
        android:layout_height="45dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:animateLayoutChanges="false"
        android:background="@color/colorPrimary"
        android:padding="30dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <android.support.constraint.ConstraintLayout
        android:id="@+id/chart2"
        android:layout_width="0.1dp"
        android:layout_height="45dp"
        android:layout_marginStart="-15dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:animateLayoutChanges="true"
        android:background="@color/colorPrimary"
        android:padding="30dp"
        android:paddingStart="5dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/chart1" />

    <android.support.constraint.ConstraintLayout
        android:id="@+id/chart3"
        android:layout_width="0.1dp"
        android:layout_height="45dp"
        android:layout_marginStart="-15dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="8dp"
        android:animateLayoutChanges="true"
        android:background="@color/colorPrimary"
        android:padding="30dp"
        android:paddingStart="5dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/chart2" />

    <android.support.constraint.ConstraintLayout
        android:id="@+id/chart4"
        android:layout_width="0.1dp"
        android:layout_height="45dp"
        android:layout_marginStart="-15dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:animateLayoutChanges="true"
        android:background="@color/colorPrimary"
        android:padding="30dp"
        android:paddingStart="5dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/chart3" />

    <android.support.constraint.ConstraintLayout
        android:id="@+id/chart5"
        android:layout_width="0.1dp"
        android:layout_height="45dp"
        android:layout_marginStart="-15dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="8dp"
        android:animateLayoutChanges="true"
        android:background="@color/colorPrimary"
        android:padding="30dp"
        android:paddingStart="5dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/chart4" />

    <android.support.constraint.ConstraintLayout
        android:id="@+id/chart6"
        android:layout_width="0.1dp"
        android:layout_height="45dp"
        android:layout_marginStart="-15dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:animateLayoutChanges="true"
        android:background="@color/colorPrimary"
        android:padding="30dp"
        android:paddingStart="5dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/chart5" />

    <android.support.constraint.ConstraintLayout
        android:id="@+id/chart7"
        android:layout_width="0.1dp"
        android:layout_height="45dp"
        android:layout_marginStart="-15dp"
        android:layout_marginTop="16dp"
        android:layout_marginEnd="8dp"
        android:animateLayoutChanges="true"
        android:background="@color/colorPrimary"
        android:padding="30dp"
        android:paddingStart="5dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/chart6" />

    <android.support.constraint.ConstraintLayout
        android:id="@+id/chart8"
        android:layout_width="0.1dp"
        android:layout_height="45dp"
        android:layout_marginStart="-15dp"
        android:layout_marginTop="8dp"
        android:layout_marginEnd="8dp"
        android:animateLayoutChanges="true"
        android:background="@color/colorPrimary"
        android:padding="30dp"
        android:paddingStart="5dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/chart7" />

</android.support.constraint.ConstraintLayout>

از طریق سورسی که در Github قرار دادم می‌تونید به کد مربوط به مابقی Sceneها دسترسی داشته باشید.

در شکل زیر به ترتیب از چپ به راست می‌تونید نتیجه بصری Layoutهای مربوط به Sceneها رو مشاهده کنید:

گامی فراتر در انیمیشن‌های اندرویدی با Scene

فایل Layout‌ مربوط به صفحه اصلی برنامه تحت عنوان activity_main.xml رو هم داریم که مطابق کد زیر صحنه اول درونش include شده و همچنین به Root Element این Layout یک id تحت عنوان scene_root رو اختصاص دادیم:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/scene_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:animateLayoutChanges="false"
    tools:context=".MainActivity">

    <include layout="@layout/scene1" />

</android.support.constraint.ConstraintLayout>

خب تا اینجای کار ما 4 تا Layout ساختیم و اولین Scene رو به Layout صفحه اصلی دادیم تا همانند یک Starter کار رو برای ما شروع کنه و درواقع شروع کننده انیمیشن باشه.

برای انیمیشن بین صحنه ها نیاز به یک Transition داریم تا به عنوان مرز بین صحنه‌ها استفاده بشه.

در دایرکتوری res پوشه transition و همچنین یک فایل XML با این اسم رو میسازیم.

و در فایل transition.xml افکت تغییر حالت رو با استفاده از changeBounds با مدت زمان 500 میلی‌ثانیه به صورت زیر مینویسیم:

<?xml version="1.0" encoding="utf-8"?>
<changeBounds xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500" />

حالا کارمون با قسمت دیزاین ماجرا تموم شده و میریم سراغ کدهای Java و فایل MainActivity !

مطابق کد زیر در سطح کلاس MainActivity به ازای 4 تا صحنه‌ای که داریم 4 آبجکت از کلاس Scene می‌سازیم.

یک آبجکت از نوع ViewGroup نیز می‌سازیم که مربوط به همان Root Element فایل activity_main.xml هست که scene_root رو به عنوان id بهش داده‌ بودیم. در اینجا می‌تونستیم به جای ViewGroup‌ یک آبجکت از ConstraintLayout هم بسازیم اما خب ConstraintLayout هم از یک ViewGroup ارث‌بری می‌کنه! در اینجا با مسئله چندریختی یا Polymorphism مواجه هستیم که می‌تونید با شرکت در دوره رایگان آموزش OOP در جاوا ویژه برنامه‌نویسان اندروید آشنایی بیشتری با مبحث چندریختی پیدا کنید. همچنین جهت آشنایی با ConstraintLayout می‌تونید در دوره رایگان آموزش کار با ConstraintLayout در اندروید شرکت کنید.

در نهایت یک آبجکت از Transition و همچنین یک آبجکت از Handler‌ هم در سطح کلاس ایجاد کرده‌ایم که instance‌ مربوط به Handler را همینجا ساختیم و handler‌ رو initialize کردیم. در مورد ساختار و کاربرد Handler در اندروید در دوره قدم یک برنامه‌نویسی اندروید توضیحات بیشتری ارائه داده شده!

private ViewGroup mSceneRoot;
private Scene mScene1;
private Scene mScene2;
private Scene mScene3;
private Scene mScene4;
private Transition transition;
private Handler handler = new Handler();

اولین کاری که در متد onCreate می‌کنیم initialize کردن mSceneRoot هست:

mSceneRoot = findViewById(R.id.scene_root);

در ادامه transition و همینطور صحنه‌ها رو initialize می‌کنیم، صحنه اول رو اجرا می‌کنیم و در نهایت با استفاده از handler با یک delay‌ اولیه برای هر کدام از صحنه‌ها به مقدار دلخواه با پاس دادن صحنه‌های initialize  شده به instance مربوط به transition و با اجرا شدن متد go از صحنه اول انیمیشن ما صحنه به صحنه جلو بره.

// Create the scenes
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            transition = TransitionInflater.from(this).inflateTransition(R.transition.transition);

            mScene1 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene1, this);
            mScene2 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene2, this);
            mScene3 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene3, this);
            mScene4 = Scene.getSceneForLayout(mSceneRoot, R.layout.scene4, this);

            mScene1.enter();

            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                        TransitionManager.go(mScene1);

                }
            }, 1000);


            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    TransitionManager.go(mScene2, transition);
                }
            }, 2000);


            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    TransitionManager.go(mScene3, transition);
                }
            }, 3000);
//
//
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    TransitionManager.go(mScene4, transition);
                }
            }, 3500);

اگر دقت کرده باشید تمامی مراحل انجام انیمیشن قابل customize کردن به شکل دلخواه شماست برای مثال شما میتونید بعد از اجرا شدن صحنه اول برای صحنه دوم با استفاده از handler  زمان مشخصی اختصاص بدید تا در اون زمان صحنه دوم اجرا بشه و به همین صورت برای صحنه های سوم و چهارم.

حالا اگر صحنه چهارم شما صحنه پایانی شماست و میخواید بعد از نمایش اون و متوقف شدن انیمیشن، برای مثال یکی از کامپوننت ها قابلیت کلیک شدن پیدا کنه و با کلیک بر روی اون یک Activity جدید Start‌ بشه برای اینکار کافیه از آبجکت صحنه مورد نظر متد setEnterAction رو فراخوانی کنید و کامپوننت مورد نظر در این Scene رو انتخاب کرده و براش onClickListener بنویسید و Activity‌ مد نظرتون رو اجرا کرده و یا کار مدنظر خودتون رو انجام بدید:

mScene4.setEnterAction(new Runnable() {
                @Override
                public void run() {
                    ((ConstraintLayout) mScene4.getSceneRoot().findViewById(R.id.chart4)).setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View v) {
                            Intent intent = new Intent(MainActivity.this,SecondActivity.class);
                            startActivity(intent);
                        }
                    });
                }


            });

امیدوارم با این قابلیت دنیای اندرویدیتون زیباتر بشه <3


کلیدواژه: اندروید Android java Scene animation انیمیشن

منابع: arcademy.ir

دیدگاه ها:
حسین
۱ هفته قبل
reply
ممنون خیلی جالب بود ... scene از امکانات جدید اندرویده یا قبلا هم بوده؟
MoGa
۱ هفته قبل در پاسخ به حسین
reply
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
این یعنی از کیت کت اضافه شده
برنا جویا
۱ هفته قبل در پاسخ به MoGa
reply
خیلی ممنون از شما
برنا جویا
۱ هفته قبل در پاسخ به حسین
reply
بله همونطوری که دوستمون گفتند خیلی وقته همراه با transition اندروبد تقریبا وجود داشته و از کیت کت به بعد هم ساپورت میشه
حسین
۱ هفته قبل
reply
دقت نکرده بودم ... بازم ممنون، جالب بود
ارسال دیدگاه:
برای ارسال دیگاه باید به سیستم وارد شوید و یا ثبت نام کنید. ثبت نام چند لحظه بیشتر زمان شما را نمیگیرد.