با استفاده از انیمیشن ها بین قطعات حرکت کنید

Fragment API دو راه برای استفاده از افکت‌های حرکتی و تبدیل‌ها برای اتصال بصری قطعات در طول مسیریابی ارائه می‌کند. یکی از اینها چارچوب انیمیشن است که از Animation و Animator استفاده می کند. مورد دیگر Transition Framework است که شامل انتقال عناصر مشترک است.

شما می توانید جلوه های سفارشی را برای ورود و خروج قطعات و برای انتقال عناصر مشترک بین قطعات مشخص کنید.

  • یک افکت enter تعیین می کند که چگونه یک قطعه وارد صفحه می شود. به عنوان مثال، می توانید یک افکت ایجاد کنید تا قطعه را از لبه صفحه به داخل بکشید وقتی به سمت آن حرکت می کنید.
  • یک افکت خروج نحوه خروج یک قطعه از صفحه را تعیین می کند. به عنوان مثال، می‌توانید افکتی ایجاد کنید تا قطعه هنگام دور شدن از آن، محو شود.
  • یک انتقال عنصر مشترک تعیین می کند که چگونه یک نمای که بین دو قطعه مشترک است بین آنها حرکت کند. به عنوان مثال، تصویری که در ImageView در قطعه A نمایش داده می شود، هنگامی که B قابل مشاهده شد، به قطعه B تبدیل می شود.

انیمیشن ها را تنظیم کنید

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

ورود و خروج از انیمیشن ها قطعه فعلی محو می شود در حالی که قطعه بعدی از سمت راست به داخل می لغزد.
شکل 1. وارد و خروج از انیمیشن ها شوید. قطعه فعلی محو می شود در حالی که قطعه بعدی از سمت راست به داخل می لغزد.

این انیمیشن ها را می توان در دایرکتوری res/anim تعریف کرد:

<!-- res/anim/fade_out.xml -->
<?xml version="1.0" encoding="utf-8"?>
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:fromAlpha="1"
    android:toAlpha="0" />
<!-- res/anim/slide_in.xml -->
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:fromXDelta="100%"
    android:toXDelta="0%" />

همچنین می‌توانید انیمیشن‌هایی را برای جلوه‌های ورود و خروج که هنگام باز کردن پشته اجرا می‌شوند، مشخص کنید، که ممکن است با ضربه زدن کاربر روی دکمه بالا یا برگشت اتفاق بیفتد. به این انیمیشن‌های popEnter و popExit می‌گویند. به عنوان مثال، هنگامی که کاربر به صفحه قبلی باز می گردد، ممکن است بخواهید قطعه فعلی از لبه سمت راست صفحه خارج شود و قطعه قبلی محو شود.

انیمیشن های popEnter و popExit. قطعه فعلی از صفحه به سمت راست می لغزد در حالی که قطعه قبلی محو می شود.
شکل 2. انیمیشن های popEnter و popExit . قطعه فعلی از صفحه به سمت راست می لغزد در حالی که قطعه قبلی محو می شود.

این انیمیشن ها را می توان به صورت زیر تعریف کرد:

<!-- res/anim/slide_out.xml -->
<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:fromXDelta="0%"
    android:toXDelta="100%" />
<!-- res/anim/fade_in.xml -->
<alpha xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"
    android:interpolator="@android:anim/decelerate_interpolator"
    android:fromAlpha="0"
    android:toAlpha="1" />

هنگامی که انیمیشن های خود را تعریف کردید، با فراخوانی FragmentTransaction.setCustomAnimations() از آنها استفاده کنید و منابع انیمیشن خود را با شناسه منبع آن ها ارسال کنید، همانطور که در مثال زیر نشان داده شده است:

کاتلین

supportFragmentManager.commit {
    setCustomAnimations(
        R.anim.slide_in, // enter
        R.anim.fade_out, // exit
        R.anim.fade_in, // popEnter
        R.anim.slide_out // popExit
    )
    replace(R.id.fragment_container, fragment)
    addToBackStack(null)
}

جاوا

Fragment fragment = new FragmentB();
getSupportFragmentManager().beginTransaction()
    .setCustomAnimations(
        R.anim.slide_in,  // enter
        R.anim.fade_out,  // exit
        R.anim.fade_in,   // popEnter
        R.anim.slide_out  // popExit
    )
    .replace(R.id.fragment_container, fragment)
    .addToBackStack(null)
    .commit();

انتقال ها را تنظیم کنید

شما همچنین می توانید از انتقال برای تعریف جلوه های ورود و خروج استفاده کنید. این انتقال ها را می توان در فایل های منبع XML تعریف کرد. برای مثال، ممکن است بخواهید قطعه فعلی محو شود و قطعه جدید از لبه سمت راست صفحه به داخل اسلاید شود. این انتقال ها را می توان به صورت زیر تعریف کرد:

<!-- res/transition/fade.xml -->
<fade xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"/>
<!-- res/transition/slide_right.xml -->
<slide xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="@android:integer/config_shortAnimTime"
    android:slideEdge="right" />

هنگامی که ترانزیشن های خود را تعریف کردید، با فراخوانی setEnterTransition() در قطعه ورودی و setExitTransition() در قطعه خروجی، آن ها را اعمال کنید، همانطور که در مثال زیر نشان داده شده است، منابع انتقال متورم خود را با شناسه منبع آنها ارسال کنید:

کاتلین

class FragmentA : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val inflater = TransitionInflater.from(requireContext())
        exitTransition = inflater.inflateTransition(R.transition.fade)
    }
}

class FragmentB : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val inflater = TransitionInflater.from(requireContext())
        enterTransition = inflater.inflateTransition(R.transition.slide_right)
    }
}

جاوا

public class FragmentA extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TransitionInflater inflater = TransitionInflater.from(requireContext());
        setExitTransition(inflater.inflateTransition(R.transition.fade));
    }
}

public class FragmentB extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TransitionInflater inflater = TransitionInflater.from(requireContext());
        setEnterTransition(inflater.inflateTransition(R.transition.slide_right));
    }
}

قطعات از انتقال AndroidX پشتیبانی می کنند. در حالی که قطعات از انتقال‌های چارچوب نیز پشتیبانی می‌کنند، ما قویاً استفاده از انتقال‌های AndroidX را توصیه می‌کنیم، زیرا در سطوح API 14 و بالاتر پشتیبانی می‌شوند و حاوی رفع اشکال هستند که در نسخه‌های قدیمی‌تر انتقال‌های چارچوب وجود ندارد.

از انتقال عناصر مشترک استفاده کنید

بخشی از چارچوب انتقال ، انتقال عناصر مشترک تعیین می‌کند که چگونه نماهای متناظر بین دو قطعه در طول انتقال قطعه حرکت می‌کنند. به عنوان مثال، ممکن است بخواهید یک تصویر در ImageView در قطعه A نمایش داده شود تا زمانی که B قابل مشاهده شد، به قطعه B منتقل شود، همانطور که در شکل 3 نشان داده شده است.

انتقال قطعه با یک عنصر مشترک.
شکل 3. یک انتقال قطعه با یک عنصر مشترک.

در سطح بالا، در اینجا نحوه انتقال قطعه با عناصر مشترک است:

  1. یک نام انتقال منحصر به فرد به هر نمای عنصر مشترک اختصاص دهید.
  2. نماهای عناصر مشترک و نام های انتقال را به FragmentTransaction اضافه کنید.
  3. یک انیمیشن انتقال عنصر مشترک را تنظیم کنید.

ابتدا، شما باید یک نام انتقال منحصر به فرد را به هر نمای عنصر مشترک اختصاص دهید تا امکان نگاشت نماها از یک قطعه به قطعه دیگر را فراهم کند. با استفاده از ViewCompat.setTransitionName() که سازگاری با سطوح API 14 و بالاتر را فراهم می کند، روی عناصر به اشتراک گذاشته شده در هر طرح بندی قطعه یک نام انتقال تنظیم کنید. به عنوان مثال، نام انتقال برای ImageView در قطعات A و B را می توان به صورت زیر اختصاص داد:

کاتلین

class FragmentA : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        val itemImageView = view.findViewById<ImageView>(R.id.item_image)
        ViewCompat.setTransitionName(itemImageView, “item_image”)
    }
}

class FragmentB : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        val heroImageView = view.findViewById<ImageView>(R.id.hero_image)
        ViewCompat.setTransitionName(heroImageView, “hero_image”)
    }
}

جاوا

public class FragmentA extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        ...
        ImageView itemImageView = view.findViewById(R.id.item_image);
        ViewCompat.setTransitionName(itemImageView, “item_image”);
    }
}

public class FragmentB extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        ...
        ImageView heroImageView = view.findViewById(R.id.hero_image);
        ViewCompat.setTransitionName(heroImageView, “hero_image”);
    }
}

برای گنجاندن عناصر به اشتراک گذاشته شده شما در انتقال قطعه، FragmentTransaction شما باید بداند که چگونه نماهای هر عنصر مشترک از یک قطعه به قطعه دیگر نگاشت می شود. همانطور که در مثال زیر نشان داده شده است، هر یک از عناصر به اشتراک گذاشته شده خود را با فراخوانی FragmentTransaction.addSharedElement() به FragmentTransaction خود اضافه کنید.

کاتلین

val fragment = FragmentB()
supportFragmentManager.commit {
    setCustomAnimations(...)
    addSharedElement(itemImageView, “hero_image”)
    replace(R.id.fragment_container, fragment)
    addToBackStack(null)
}

جاوا

Fragment fragment = new FragmentB();
getSupportFragmentManager().beginTransaction()
    .setCustomAnimations(...)
    .addSharedElement(itemImageView, “hero_image”)
    .replace(R.id.fragment_container, fragment)
    .addToBackStack(null)
    .commit();

برای تعیین نحوه انتقال عناصر به اشتراک گذاشته شده از یک قطعه به قطعه دیگر، باید یک انتقال ورودی را در قطعه مورد پیمایش تنظیم کنید. همانطور که در مثال زیر نشان داده شده است Fragment.setSharedElementEnterTransition() را در متد onCreate() قطعه فراخوانی کنید:

کاتلین

class FragmentB : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sharedElementEnterTransition = TransitionInflater.from(requireContext())
             .inflateTransition(R.transition.shared_image)
    }
}

جاوا

public class FragmentB extends Fragment {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Transition transition = TransitionInflater.from(requireContext())
            .inflateTransition(R.transition.shared_image);
        setSharedElementEnterTransition(transition);
    }
}

انتقال shared_image به صورت زیر تعریف می شود:

<!-- res/transition/shared_image.xml -->
<transitionSet>
    <changeImageTransform />
</transitionSet>

همه زیر کلاس های Transition به عنوان انتقال عناصر مشترک پشتیبانی می شوند. اگر می خواهید یک Transition سفارشی ایجاد کنید، به ایجاد یک انیمیشن انتقال سفارشی مراجعه کنید. changeImageTransform که در مثال قبلی استفاده شد، یکی از ترجمه‌های از پیش ساخته شده است که می‌توانید از آن استفاده کنید. می‌توانید زیر کلاس‌های Transition اضافی را در مرجع API برای کلاس Transition پیدا کنید.

به‌طور پیش‌فرض، عنصر اشتراک‌گذاری شده enter transition نیز به عنوان انتقال بازگشتی برای عناصر مشترک استفاده می‌شود. انتقال بازگشتی تعیین می‌کند که چگونه عناصر اشتراک‌گذاری شده زمانی که تراکنش قطعه از پشته باز می‌شود، به قطعه قبلی برمی‌گردند. اگر می خواهید یک انتقال بازگشتی متفاوت را مشخص کنید، می توانید این کار را با استفاده از Fragment.setSharedElementReturnTransition() در متد onCreate() قطعه انجام دهید.

سازگاری پیشگویانه

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

  • وارد کردن Transitions 1.5.0 یا بالاتر و Fragments 1.7.0 یا بالاتر.
  • کلاس Animator و زیر کلاس‌ها و کتابخانه AndroidX Transition پشتیبانی می‌شوند.
  • کلاس Animation و کتابخانه Framework Transition پشتیبانی نمی شود.
  • انیمیشن‌های قطعه پیش‌بینی‌کننده فقط روی دستگاه‌هایی کار می‌کنند که دارای Android نسخه ۱۴ یا بالاتر هستند.
  • setCustomAnimations ، setEnterTransition ، setExitTransition ، setReenterTransition ، setReturnTransition ، setSharedElementEnterTransition ، و setSharedElementReturnTransition با پیش بینی بازگشت پشتیبانی می شوند.

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

به تعویق انداختن انتقال

در برخی موارد، ممکن است لازم باشد انتقال قطعه خود را برای مدت کوتاهی به تعویق بیندازید. برای مثال، ممکن است لازم باشد منتظر بمانید تا تمام نماها در قطعه ورودی اندازه گیری و چیده شوند تا Android بتواند به طور دقیق حالت های شروع و پایان آنها را برای انتقال ثبت کند.

علاوه بر این، ممکن است لازم باشد انتقال شما تا بارگیری برخی از داده های ضروری به تعویق بیفتد. برای مثال، ممکن است لازم باشد منتظر بمانید تا تصاویر برای عناصر مشترک بارگیری شوند. در غیر این صورت، اگر یک تصویر در حین یا پس از انتقال بارگذاری به پایان برسد، انتقال ممکن است ناخوشایند باشد.

برای به تعویق انداختن یک انتقال، ابتدا باید مطمئن شوید که تراکنش قطعه اجازه مرتب‌سازی مجدد تغییرات حالت قطعه را می‌دهد. برای اجازه دادن به ترتیب مجدد تغییرات وضعیت قطعه، FragmentTransaction.setReorderingAllowed() را فراخوانی کنید، همانطور که در مثال زیر نشان داده شده است:

کاتلین

val fragment = FragmentB()
supportFragmentManager.commit {
    setReorderingAllowed(true)
    setCustomAnimation(...)
    addSharedElement(view, view.transitionName)
    replace(R.id.fragment_container, fragment)
    addToBackStack(null)
}

جاوا

Fragment fragment = new FragmentB();
getSupportFragmentManager().beginTransaction()
    .setReorderingAllowed(true)
    .setCustomAnimations(...)
    .addSharedElement(view, view.getTransitionName())
    .replace(R.id.fragment_container, fragment)
    .addToBackStack(null)
    .commit();

برای به تعویق انداختن انتقال enter، Fragment.postponeEnterTransition() را در متد onViewCreated() فرگمنت ورودی فراخوانی کنید:

کاتلین

class FragmentB : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        postponeEnterTransition()
    }
}

جاوا

public class FragmentB extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        ...
        postponeEnterTransition();
    }
}

هنگامی که داده ها را بارگیری کردید و برای شروع انتقال آماده شدید، Fragment.startPostponedEnterTransition() را فراخوانی کنید. مثال زیر از کتابخانه Glide برای بارگیری یک تصویر در ImageView مشترک استفاده می‌کند و انتقال مربوطه را تا پایان بارگیری تصویر به تعویق می‌اندازد.

کاتلین

class FragmentB : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        ...
        Glide.with(this)
            .load(url)
            .listener(object : RequestListener<Drawable> {
                override fun onLoadFailed(...): Boolean {
                    startPostponedEnterTransition()
                    return false
                }

                override fun onResourceReady(...): Boolean {
                    startPostponedEnterTransition()
                    return false
                }
            })
            .into(headerImage)
    }
}

جاوا

public class FragmentB extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        ...
        Glide.with(this)
            .load(url)
            .listener(new RequestListener<Drawable>() {
                @Override
                public boolean onLoadFailed(...) {
                    startPostponedEnterTransition();
                    return false;
                }

                @Override
                public boolean onResourceReady(...) {
                    startPostponedEnterTransition();
                    return false;
                }
            })
            .into(headerImage)
    }
}

هنگام برخورد با مواردی مانند اتصال کند اینترنت کاربر، ممکن است نیاز داشته باشید که انتقال به تعویق افتاده پس از مدت زمان مشخصی شروع شود تا اینکه منتظر بارگیری همه داده ها باشید. برای این موقعیت‌ها، می‌توانید Fragment.postponeEnterTransition(long, TimeUnit) را در متد onViewCreated() فرگمنت ورودی فراخوانی کنید و مدت زمان و واحد زمان را رد کنید. پس از سپری شدن زمان مشخص شده، به تعویق افتادن به طور خودکار شروع می شود.

از انتقال عناصر مشترک با RecyclerView استفاده کنید

تا زمانی که تمام نماها در قطعه ورودی اندازه گیری و چیده نشده اند، انتقال های ورودی به تعویق افتاده نباید شروع شوند. هنگام استفاده از RecyclerView ، باید منتظر بمانید تا هر داده بارگیری شود و موارد RecyclerView برای ترسیم آماده شوند قبل از شروع انتقال. در اینجا یک مثال است:

کاتلین

class FragmentA : Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        postponeEnterTransition()

        // Wait for the data to load
        viewModel.data.observe(viewLifecycleOwner) {
            // Set the data on the RecyclerView adapter
            adapter.setData(it)
            // Start the transition once all views have been
            // measured and laid out
            (view.parent as? ViewGroup)?.doOnPreDraw {
                startPostponedEnterTransition()
            }
        }
    }
}

جاوا

public class FragmentA extends Fragment {
    @Override
    public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
        postponeEnterTransition();

        final ViewGroup parentView = (ViewGroup) view.getParent();
        // Wait for the data to load
        viewModel.getData()
            .observe(getViewLifecycleOwner(), new Observer<List<String>>() {
                @Override
                public void onChanged(List<String> list) {
                    // Set the data on the RecyclerView adapter
                    adapter.setData(it);
                    // Start the transition once all views have been
                    // measured and laid out
                    parentView.getViewTreeObserver()
                        .addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                            @Override
                            public boolean onPreDraw(){
                                parentView.getViewTreeObserver()
                                        .removeOnPreDrawListener(this);
                                startPostponedEnterTransition();
                                return true;
                            }
                    });
                }
        });
    }
}

توجه داشته باشید که یک ViewTreeObserver.OnPreDrawListener در والد نمای قطعه تنظیم شده است. این کار برای اطمینان از این است که همه نماهای قطعه اندازه گیری و چیده شده اند و بنابراین آماده ترسیم قبل از شروع انتقال ورودی به تعویق افتاده هستند.

نکته دیگری که باید در هنگام استفاده از انتقال عناصر مشترک با RecyclerView در نظر گرفت این است که نمی‌توانید نام انتقال را در طرح XML مورد RecyclerView تنظیم کنید زیرا تعداد دلخواه آیتم‌ها آن طرح‌بندی را به اشتراک می‌گذارند. یک نام انتقال منحصر به فرد باید اختصاص داده شود تا انیمیشن انتقال از نمای صحیح استفاده کند.

می‌توانید با اختصاص دادن آن‌ها در هنگام محدود شدن ViewHolder ، به عنصر مشترک هر مورد، یک نام انتقال منحصر به فرد بدهید. به عنوان مثال، اگر داده های هر مورد شامل یک شناسه منحصر به فرد باشد، می تواند به عنوان نام انتقال استفاده شود، همانطور که در مثال زیر نشان داده شده است:

کاتلین

class ExampleViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val image = itemView.findViewById<ImageView>(R.id.item_image)

    fun bind(id: String) {
        ViewCompat.setTransitionName(image, id)
        ...
    }
}

جاوا

public class ExampleViewHolder extends RecyclerView.ViewHolder {
    private final ImageView image;

    ExampleViewHolder(View itemView) {
        super(itemView);
        image = itemView.findViewById(R.id.item_image);
    }

    public void bind(String id) {
        ViewCompat.setTransitionName(image, id);
        ...
    }
}

منابع اضافی

برای کسب اطلاعات بیشتر در مورد انتقال قطعه، منابع اضافی زیر را ببینید.

نمونه ها

پست های وبلاگ