نحوه استفاده از Shared Element در اندروید

Shared Element
Shared Element

شاید تو یک سری از اپلیکشین های اندرویدی مثل نسخه قبلی گوگل پلی، با shared element برخورد کرده باشید.

Shared Element
Shared Element

فک کنم از تصویر بالا کلیات رو فهمیده باشید. Shared Element در واقع یعنی انتقال یک شیء از یک اکتیویتی به یک اکتیویتی دیگر با انیمیشن، در صورتی که اون شیء بین هر دو اکتیویتی مشترک باشه.
عنصر مشترک یا Shared Element در واقع در اندروید ۵ معرفی شد. و فقط گوشی های هوشمند با اندروید ۵ به بالا از اون پشتیبانی می‌کنند.

در ادامه یک پروژه کوچیک با هم‌دیگه انجام می‌دیم که بهتر متوجه بشید.

عنصر مشترک

امیدوارم با کلیات توسعه اندروید آشنا باشید. چون نمی‌خوام زیاد وارد جزئیات بشم. تو این پروژه ما یک اسپلش اسکرین یا صفحه معرفی و یک صفحه اصلی داریم. داخل صفحه معرفی، لوگو برنامه هست که بعد از سه ثانیه صفحه معرفی تموم میشه و صفحه اصلی باز می‌شه. صفحه اصلی شامل لوگو برنامه و یک متن هست. بیاید شروع کنیم.

  • یک پروژه جدید با یک اکتیویتی خالی در اندروید استدیو ایجاد کنید. اسمشم بزارید Shared Element یا هر چی که دوست داشتید.
  • حالا توی پروژه یک اکتیویتی دیگه به اسم SplashScreenActivity ایجاد کنید.
  • توی layout کلاس SplashScreenActivity یک امیج ویو بزارید. مثله کد زیر:
<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    tools:context=".SplashScreenActivity">

    <ImageView
        android:id="@+id/logo"
        android:layout_width="80dp"
        android:layout_height="80dp"
        android:tint="@color/colorAccent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        android:src="@drawable/ic_shop"
        android:transitionName="logo"  <!-- here is the transitionName for shared Element-->
        />

</android.support.constraint.ConstraintLayout>

می‌رسیم به قسمت مهم ماجرا. شما باید به غیر از تعریف ایدی برای ابجکت یا المنت‌تون یک ایدی هم به عنوان اسم عنصر مشترک اختصاص بدید. این ایدی با transitioneName تعریف می‌شه. همون قسمتی که با توضیح مشخص کردم. من transitioneName رو برابر "logo" قرار دادم. شما می‌تونید هر چی دلتون خواست بزارید. خروجی مورد انتظار چیزی شبیه به تصویر پایین باید باشه:

activity_splash_screen.xml
activity_splash_screen.xml

حالا در layout اکتیویتی اول یعنی کلاس MainActivity یک تکست‌ویو و یک امیج‌ویو برای نمایش لوگو و متن ایجاد کنید. شبیه به کد زیر:

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="vertical"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageView
            android:id="@+id/logo"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:layout_marginTop="16dp"
            android:src="@drawable/ic_shop"
            android:tint="@color/colorAccent"
            android:transitionName="logo" /> <!--here is the transition name.  -->

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="This is a project for learn how to use Shared Element."
            android:textAlignment="center"
            android:textColor="@android:color/background_dark"
            android:textSize="18dp" />
    </LinearLayout>
</android.support.constraint.ConstraintLayout>

اینجا هم قسمت مهم همون transitionName هست. در اینجا باید همون اسمی رو اختصاص بدید که در layout کلاس SplashScreenActivity به امیج ویو اختصاص داده بودید. خروجی باید چیزی شبیه به تصویر پایین باشه:

activity_main.xml
activity_main.xml

خوب کارمون با قسمت طراحی تموم شد. برگردیم سراغ اکتیویتی ها. کلاس SplashScreenActivity رو باز کنید. در اینجا می‌خوایم یک تایمر بزاریم که بعد از دو ثانیه اکتیویتی اصلی اجرا بشه.من از ترید(thread) استفاده کردم. ولی شما می‌تونید فقط یک تایمر بزارید. طبق کد زیر پیش برید:

Thread splash = new Thread() {
    @Override
    public void run() {
        try {
            sleep(2000);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    
                }
            });

        } catch (Exception e) {

        }
    }
    };
  • امیج ویویی که لوگو داخل اون قرار داره رو شناسایی کنید:
ImageView imageViewLogo;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_splash_screen);
    imageViewLogo=findViewById(R.id.logo);
}
  • حالا داخل متد run کدهای زیر رو وارد کنید. این کدها برای اجرای اکتیویتی اصلی هستن:
Intent i = new Intent(SplashScreenActivity.this, MainActivity.class);
ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(SplashScreenActivity.this, imageViewLogo, "logo");
startActivity(i, optionsCompat.toBundle());
finish();
  • چیزی که در اینجا مهمه این خط از کد هست:
 ActivityOptionsCompat optionsCompat = ActivityOptionsCompat.makeSceneTransitionAnimation(SplashScreenActivity.this, imageViewLogo, "logo");  

در اینجا ما به اینتنت می‌فهمونیم هم قراره یک اکتیویتی رو اجرا کنه و هم اینکه یک چیزی به اون ارسال کنه. اون چیز در واقع makeSceneTransitionAnimation هست. این تابع در اندروید ۵ معرفی شد. با این تابع هست که امکان Shared Element مهیا می‌شه. البته استفاده های دیگه‌ای هم داره که من دقیقا نمی‌دونم.
قسمت اول این تابع اسم اون اکتیویتی هست که الان داخلش هستیم. قسمت دوم اسم اون المنتی هست که ما می‌خوایم به عنوان عنصر مشترک استفاده کنیم. که در اینجا امیج ویویی هست که لوگوی برنامه رو نشون میده. قسمت سوم اسم transitionName هست که قبلا به هر دو عنصر اختصاص دادیم. در اینجا من از "logo" استفاده کرده بودم. شما اگر ایدی دیگه‌ای نوشتید همون رو وارد کنید.

حالا تایمر یا تردی که نوشتید رو تو قسمت oncreate اکتیوتی اجرا کنید. کدهای کلاس SplashScreenActivity باید چیزی شبیه به تصویر پایین باشه:

SplashScreenActivity.java
SplashScreenActivity.java

حالا طبق تصویر زیر فایل styles.xml رو باز کنید:

styles.xml
styles.xml

و کد زیر رو در داخل بلوک AppTheme وارد کنید:

<item name="android:windowContentTransitions">true</item>

این خط از کد به هر دو اکتیویتی میفهمونه که دارن از shared element استفاده می‌کنند.
حالا برنامه رو اجرا کنید:

عه! پس چرا اینجوری شد؟!!!!!
در واقع هر اکتیویتی بعد از تموم شدن دارای یک انیمیشن هست. حالا این انیمیشن با انیمیشنی که ما درست کردیم تداخل پیدا کرده. (آخرسر اندروید با این دردسرهاش مارو می‌کشه)
برای حل این مشکل یک راه حل ساده داریم. شما باید سه تا کار انجام بدید:
۱. اول اینکه خط کد زیر رو به قسمت onCreate در کلاس SplashScreenActivity اضافه کنید:

getWindow().setExitTransition(null);

فک کنم کد واضح باشه. این خط از کد انیمیشن خروج اکتیویتی رو حذف می‌کنه.

۲. خط کد زیر رو هم به قسمت onCreate در کلاس MainActivity اضافه کنید:

getWindow().setEnterTransition(null);

این کد هم انیمیشن ورود رو حذف می‌کنه.

۳. به جای اینکه بلافاصله بعد از شروع اکتیویتی جدید، اکتیویتی قبلی رو تموم یا finish کنیم، بیایم تو تابع آن_ستاپ اونو finish کنیم. امیدوارم فهمیده باشید. بیاید یک نگاه به تصویر پایین بندازیم:

SplashScreenActivity,java
SplashScreenActivity,java


در کلاس SplashScreenActivity من اومدم تابع آن_ستاپ رو override کردم و در اون اکتیویتی رو finish کردم. یعنی وقتی چرخه اکتیویتی رفت روی آن_ستاپ، بعد از اون بیاد اکتیویتی رو تموم کنه. اینجوری مشکل تداخل هم حل میشه. البته این یک راه حل بود که خودم پیدا کردم. شاید شما راه حل بهتری داشته باشید.
حالا برنامه رو اجرا کنید:

مشکل حل شد.

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

https://github.com/shadmanadman/SharedElement/