أثناء استخدام تطبيقك، تظهر معلومات جديدة على الشاشة وتتم إزالة المعلومات القديمة. قد يكون تغيير ما يظهر على الشاشة على الفور أمرًا مزعجًا، وقد يفوت المستخدمين محتوى جديد يظهر فجأة. تعمل الرسوم المتحركة على إبطاء التغييرات وتجذب عين المستخدم بالحركة بحيث تكون التحديثات أكثر وضوحًا.
هناك ثلاثة رسوم متحركة شائعة يمكنك استخدامها لإظهار أو إخفاء عرض: إظهار الرسوم المتحركة، والرسوم المتحركة المتداخلة، والرسوم المتحركة بتنسيق Cardflip.
إنشاء صورة متحركة لتلاشي متقاطع
تتلاشى صورة متلاشية View
أو ViewGroup
تدريجيًا مع إضفاء طابع متلاشٍ عليها، وتُعرف أيضًا باسم التلاشي. هذه الصورة المتحركة مفيدة في الحالات التي تريد فيها تبديل المحتوى أو طرق العرض في تطبيقك. تستخدم الصورة المتحركة المتداخلة التي تظهر هنا ViewPropertyAnimator
، المتاحة لنظام التشغيل Android 3.1 (مستوى واجهة برمجة التطبيقات 12) والإصدارات الأحدث.
فيما يلي مثال على التلاشي المتقاطع من مؤشر التقدم إلى المحتوى النصي:
إنشاء طرق العرض
أنشئ طريقتَي العرض اللذَين تريد تقاطعهما. ينشئ المثال التالي مؤشر تقدم وعرض نص قابل للتمرير:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView style="?android:textAppearanceMedium"
android:lineSpacingMultiplier="1.2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/lorem_ipsum"
android:padding="16dp" />
</ScrollView>
<ProgressBar android:id="@+id/loading_spinner"
style="?android:progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</FrameLayout>
إعداد الحركة المتقاطعة
لإعداد الحركة المتقاطعة، قم بما يلي:
- قم بإنشاء متغيرات الأعضاء لطرق العرض التي تريد أن يتداخل مع بعضها. تحتاج إلى هذه المراجع لاحقًا عند تعديل طرق العرض أثناء الرسوم المتحركة.
- ضبط مستوى رؤية العرض الذي يتم التلاشي للداخل إلى
GONE
. يمنع هذا العرض من استخدام مساحة التخطيط ويحذفه من حسابات التخطيط، مما يؤدي إلى تسريع المعالجة - تخزين خاصية نظام
config_shortAnimTime
مؤقتًا في متغير عضو. تحدد هذه السمة مدة "قصيرة" قياسية للرسوم المتحركة. هذه المدة مثالية للرسوم المتحركة الدقيقة التي تحدث بشكل متكرر. يمكنك أيضًا استخدامconfig_longAnimTime
وconfig_mediumAnimTime
.
في ما يلي مثال على استخدام التنسيق من مقتطف الرمز السابق كطريقة عرض محتوى النشاط:
Kotlin
class CrossfadeActivity : Activity() { private lateinit var contentView: View private lateinit var loadingView: View private var shortAnimationDuration: Int = 0 ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_crossfade) contentView = findViewById(R.id.content) loadingView = findViewById(R.id.loading_spinner) // Initially hide the content view. contentView.visibility = View.GONE // Retrieve and cache the system's default "short" animation time. shortAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime) } ... }
Java
public class CrossfadeActivity extends Activity { private View contentView; private View loadingView; private int shortAnimationDuration; ... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_crossfade); contentView = findViewById(R.id.content); loadingView = findViewById(R.id.loading_spinner); // Initially hide the content view. contentView.setVisibility(View.GONE); // Retrieve and cache the system's default "short" animation time. shortAnimationDuration = getResources().getInteger( android.R.integer.config_shortAnimTime); } ... }
تلاشٍ متقاطع من العروض
وعند إعداد طرق العرض بشكلٍ صحيح، يمكنك جعل طرق العرض متداخلة من خلال إجراء ما يلي:
- بالنسبة إلى العرض الذي يتلاشى للداخل، اضبط قيمة ألفا على 0 ومستوى الظهور على
VISIBLE
من الإعداد الأوليّGONE
. يؤدي هذا إلى جعل العرض مرئي ولكن شفاف. - بالنسبة للعرض الذي يتلاشى للداخل، قم بتحريك قيمة ألفا من 0 إلى 1. بالنسبة للعرض الذي يتلاشى، قم بتحريك قيمة ألفا من 1 إلى 0.
- باستخدام
onAnimationEnd()
فيAnimator.AnimatorListener
، يمكنك ضبط مستوى العرض الذي يتلاشى إلىGONE
. على الرغم من أنّ قيمة ألفا تساوي 0، فإنّ ضبط إذن الوصول لطريقة العرض علىGONE
يمنع العرض من استخدام مساحة التنسيق ويحذفها من حسابات التصميم، ما يؤدي إلى تسريع عملية المعالجة.
توضح الطريقة التالية مثالاً على كيفية القيام بذلك:
Kotlin
class CrossfadeActivity : Activity() { private lateinit var contentView: View private lateinit var loadingView: View private var shortAnimationDuration: Int = 0 ... private fun crossfade() { contentView.apply { // Set the content view to 0% opacity but visible, so that it is // visible but fully transparent during the animation. alpha = 0f visibility = View.VISIBLE // Animate the content view to 100% opacity and clear any animation // listener set on the view. animate() .alpha(1f) .setDuration(shortAnimationDuration.toLong()) .setListener(null) } // Animate the loading view to 0% opacity. After the animation ends, // set its visibility to GONE as an optimization step so it doesn't // participate in layout passes. loadingView.animate() .alpha(0f) .setDuration(shortAnimationDuration.toLong()) .setListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { loadingView.visibility = View.GONE } }) } }
Java
public class CrossfadeActivity extends Activity { private View contentView; private View loadingView; private int shortAnimationDuration; ... private void crossfade() { // Set the content view to 0% opacity but visible, so that it is // visible but fully transparent during the animation. contentView.setAlpha(0f); contentView.setVisibility(View.VISIBLE); // Animate the content view to 100% opacity and clear any animation // listener set on the view. contentView.animate() .alpha(1f) .setDuration(shortAnimationDuration) .setListener(null); // Animate the loading view to 0% opacity. After the animation ends, // set its visibility to GONE as an optimization step so it doesn't // participate in layout passes. loadingView.animate() .alpha(0f) .setDuration(shortAnimationDuration) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { loadingView.setVisibility(View.GONE); } }); } }
إنشاء صورة متحركة لقلب البطاقة
يمكن أن يؤدي قلب البطاقة إلى التبديل بين طرق عرض المحتوى من خلال عرض صورة متحركة تحاكي بطاقة مقلوبة. تستخدم الصورة المتحركة لقلب البطاقات المعروضة هنا
FragmentTransaction
.
في ما يلي الشكل الذي يبدو عليه قلب البطاقة:
إنشاء كائنات الرسوم المتحركة
لإنشاء صورة متحركة لقلب البطاقة، ستحتاج إلى أربعة أدوات صور متحركة. يُستخدم اثنين من الرسوم المتحركة عندما تتحرك واجهة البطاقة للخارج وإلى اليسار وعندما تتحرك للداخل ومن اليسار. يُستخدم اثنين من الرسوم المتحركة الآخرين عندما يتحرك الجزء الخلفي من البطاقة من اليمين وعندما يتحرك للخارج وإلى اليمين.
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Before rotating, immediately set the alpha to 0. -->
<objectAnimator
android:valueFrom="1.0"
android:valueTo="0.0"
android:propertyName="alpha"
android:duration="0" />
<!-- Rotate. -->
<objectAnimator
android:valueFrom="-180"
android:valueTo="0"
android:propertyName="rotationY"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:duration="@integer/card_flip_time_full" />
<!-- Halfway through the rotation, set the alpha to 1. See startOffset. -->
<objectAnimator
android:valueFrom="0.0"
android:valueTo="1.0"
android:propertyName="alpha"
android:startOffset="@integer/card_flip_time_half"
android:duration="1" />
</set>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Rotate. -->
<objectAnimator
android:valueFrom="0"
android:valueTo="180"
android:propertyName="rotationY"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:duration="@integer/card_flip_time_full" />
<!-- Halfway through the rotation, set the alpha to 0. See startOffset. -->
<objectAnimator
android:valueFrom="1.0"
android:valueTo="0.0"
android:propertyName="alpha"
android:startOffset="@integer/card_flip_time_half"
android:duration="1" />
</set>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Before rotating, immediately set the alpha to 0. -->
<objectAnimator
android:valueFrom="1.0"
android:valueTo="0.0"
android:propertyName="alpha"
android:duration="0" />
<!-- Rotate. -->
<objectAnimator
android:valueFrom="180"
android:valueTo="0"
android:propertyName="rotationY"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:duration="@integer/card_flip_time_full" />
<!-- Halfway through the rotation, set the alpha to 1. See startOffset. -->
<objectAnimator
android:valueFrom="0.0"
android:valueTo="1.0"
android:propertyName="alpha"
android:startOffset="@integer/card_flip_time_half"
android:duration="1" />
</set>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Rotate. -->
<objectAnimator
android:valueFrom="0"
android:valueTo="-180"
android:propertyName="rotationY"
android:interpolator="@android:interpolator/accelerate_decelerate"
android:duration="@integer/card_flip_time_full" />
<!-- Halfway through the rotation, set the alpha to 0. See startOffset. -->
<objectAnimator
android:valueFrom="1.0"
android:valueTo="0.0"
android:propertyName="alpha"
android:startOffset="@integer/card_flip_time_half"
android:duration="1" />
</set>
إنشاء طرق العرض
يمثّل كل جانب من البطاقة تنسيقًا منفصلاً يمكن أن يتضمّن أي محتوى تريده، مثل طريقتَي عرض للنص أو صورتان أو أي مجموعة من طرق العرض يمكن التبديل بينها. استخدم التخطيطين في الأجزاء التي تقوم بتحريكها لاحقًا. ينشئ التخطيط التالي جانبًا واحدًا من البطاقة، والذي يعرض النص:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#a6c"
android:padding="16dp"
android:gravity="bottom">
<TextView android:id="@android:id/text1"
style="?android:textAppearanceLarge"
android:textStyle="bold"
android:textColor="#fff"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/card_back_title" />
<TextView style="?android:textAppearanceSmall"
android:textAllCaps="true"
android:textColor="#80ffffff"
android:textStyle="bold"
android:lineSpacingMultiplier="1.2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/card_back_description" />
</LinearLayout>
وينشئ التنسيق التالي الجانب الآخر من البطاقة الذي يعرض علامة ImageView
:
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:src="@drawable/image1"
android:scaleType="centerCrop"
android:contentDescription="@string/description_image_1" />
إنشاء الأجزاء
إنشاء فئات للأجزاء للواجهة الأمامية والخلفية من البطاقة. في فئات الأجزاء، اعرض التنسيقات التي أنشأتها من خلال الإجراء onCreateView()
. يمكنك بعد ذلك إنشاء مثيلات لهذا الجزء في النشاط الرئيسي
حيث تريد عرض البطاقة.
يوضح المثال التالي فئات الأجزاء المتداخلة داخل النشاط الرئيسي الذي تستخدمه:
Kotlin
class CardFlipActivity : FragmentActivity() { ... /** * A fragment representing the front of the card. */ class CardFrontFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View = inflater.inflate(R.layout.fragment_card_front, container, false) } /** * A fragment representing the back of the card. */ class CardBackFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View = inflater.inflate(R.layout.fragment_card_back, container, false) } }
Java
public class CardFlipActivity extends FragmentActivity { ... /** * A fragment representing the front of the card. */ public class CardFrontFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_card_front, container, false); } } /** * A fragment representing the back of the card. */ public class CardBackFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.fragment_card_back, container, false); } } }
تحريك قلب البطاقة
عرض الأجزاء داخل نشاط أحد الوالدَين للقيام بذلك، قم بإنشاء
تخطيط لنشاطك. ينشئ المثال التالي عنصر
FrameLayout
يمكنك إضافة
أجزاء إليه في وقت التشغيل:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
في رمز النشاط، قم بتعيين عرض المحتوى ليكون التخطيط الذي تقوم بإنشائه. من الممارسات الجيدة عرض جزء تلقائي عند إنشاء النشاط. يوضح النشاط التالي كمثال كيفية عرض الجهة الأمامية من البطاقة بشكل افتراضي:
Kotlin
class CardFlipActivity : FragmentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_activity_card_flip) if (savedInstanceState == null) { supportFragmentManager.beginTransaction() .add(R.id.container, CardFrontFragment()) .commit() } } ... }
Java
public class CardFlipActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_activity_card_flip); if (savedInstanceState == null) { getSupportFragmentManager() .beginTransaction() .add(R.id.container, new CardFrontFragment()) .commit(); } } ... }
من خلال ظهور الجزء الأمامي من البطاقة، يمكنك إظهار الجهة الخلفية منها بالرسوم المتحركة للقلب في الوقت المناسب. أنشئ طريقة لإظهار الجانب الآخر من البطاقة الذي يقوم بالأشياء التالية:
- يضبط الصور المتحركة المخصّصة التي أنشأتها لانتقالات الأجزاء.
- يستبدل الجزء المعروض بجزء جديد وتحريك هذا الحدث بالرسوم المتحركة المخصصة التي أنشأتها.
- يضيف الجزء المعروض سابقًا إلى المكدس الخلفي للجزء، بحيث عندما ينقر المستخدم على زر الرجوع، تقلب البطاقة مرة أخرى.
Kotlin
class CardFlipActivity : FragmentActivity() { ... private fun flipCard() { if (showingBack) { supportFragmentManager.popBackStack() return } // Flip to the back. showingBack = true // Create and commit a new fragment transaction that adds the fragment // for the back of the card, uses custom animations, and is part of the // fragment manager's back stack. supportFragmentManager.beginTransaction() // Replace the default fragment animations with animator // resources representing rotations when switching to the back // of the card, as well as animator resources representing // rotations when flipping back to the front, such as when the // system Back button is tapped. .setCustomAnimations( R.animator.card_flip_right_in, R.animator.card_flip_right_out, R.animator.card_flip_left_in, R.animator.card_flip_left_out ) // Replace any fragments in the container view with a fragment // representing the next page, indicated by the just-incremented // currentPage variable. .replace(R.id.container, CardBackFragment()) // Add this transaction to the back stack, letting users press // the Back button to get to the front of the card. .addToBackStack(null) // Commit the transaction. .commit() } }
Java
public class CardFlipActivity extends FragmentActivity { ... private void flipCard() { if (showingBack) { getSupportFragmentManager().popBackStack(); return; } // Flip to the back. showingBack = true; // Create and commit a new fragment transaction that adds the fragment // for the back of the card, uses custom animations, and is part of the // fragment manager's back stack. getSupportFragmentManager() .beginTransaction() // Replace the default fragment animations with animator // resources representing rotations when switching to the back // of the card, as well as animator resources representing // rotations when flipping back to the front, such as when the // system Back button is pressed. .setCustomAnimations( R.animator.card_flip_right_in, R.animator.card_flip_right_out, R.animator.card_flip_left_in, R.animator.card_flip_left_out) // Replace any fragments in the container view with a fragment // representing the next page, indicated by the just-incremented // currentPage variable. .replace(R.id.container, new CardBackFragment()) // Add this transaction to the back stack, letting users press // Back to get to the front of the card. .addToBackStack(null) // Commit the transaction. .commit(); } }
إنشاء رسم متحرك دائري للعرض
توفر الرسوم المتحركة المعروضة للمستخدمين استمرارية مرئية عند إظهار أو إخفاء مجموعة من
عناصر واجهة المستخدم. تتيح لك الطريقة
ViewAnimationUtils.createCircularReveal()
تحريك دائرة الاقتصاص لإظهار أو إخفاء العرض. وتتوفر هذه الصورة المتحركة في فئة ViewAnimationUtils
، وهي متوفّرة للإصدار Android 5.0 (المستوى 21 من واجهة برمجة التطبيقات) والإصدارات الأحدث.
فيما يلي مثال يوضح كيفية إظهار طبقة عرض غير مرئية سابقًا:
Kotlin
// A previously invisible view. val myView: View = findViewById(R.id.my_view) // Check whether the runtime version is at least Android 5.0. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Get the center for the clipping circle. val cx = myView.width / 2 val cy = myView.height / 2 // Get the final radius for the clipping circle. val finalRadius = Math.hypot(cx.toDouble(), cy.toDouble()).toFloat() // Create the animator for this view. The start radius is 0. val anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0f, finalRadius) // Make the view visible and start the animation. myView.visibility = View.VISIBLE anim.start() } else { // Set the view to invisible without a circular reveal animation below // Android 5.0. myView.visibility = View.INVISIBLE }
Java
// A previously invisible view. View myView = findViewById(R.id.my_view); // Check whether the runtime version is at least Android 5.0. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Get the center for the clipping circle. int cx = myView.getWidth() / 2; int cy = myView.getHeight() / 2; // Get the final radius for the clipping circle. float finalRadius = (float) Math.hypot(cx, cy); // Create the animator for this view. The start radius is 0. Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, 0f, finalRadius); // Make the view visible and start the animation. myView.setVisibility(View.VISIBLE); anim.start(); } else { // Set the view to invisible without a circular reveal animation below // Android 5.0. myView.setVisibility(View.INVISIBLE); }
تتطلّب الحركة ViewAnimationUtils.createCircularReveal()
خمس معلَمات.
المعلمة الأولى هي طريقة العرض التي تريد إخفاءها أو إظهارها على الشاشة. المعلمتان التاليتان هما الإحداثيات X وY لمركز دائرة القطع. عادةً ما تكون هذه هي مركز العرض، ولكن يمكنك أيضًا استخدام
النقطة التي ينقر عليها المستخدم بحيث تبدأ الرسوم المتحركة من حيث يحدد. المعلمة الرابعة هي نصف قطر البداية لدائرة الاقتصاص.
في المثال السابق، يتم تعيين نصف القطر الأولي على صفر بحيث يتم إخفاء العرض المعروض بواسطة الدائرة. المعلمة الأخيرة هي نصف القطر النهائي للدائرة. عند عرض طريقة عرض، اجعل نصف القطر النهائي أكبر من العرض بحيث يمكن الكشف عنه بالكامل قبل انتهاء الرسم المتحرك.
لإخفاء طريقة عرض كانت مرئية مسبقًا، قم بما يلي:
Kotlin
// A previously visible view. val myView: View = findViewById(R.id.my_view) // Check whether the runtime version is at least Android 5.0. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Get the center for the clipping circle. val cx = myView.width / 2 val cy = myView.height / 2 // Get the initial radius for the clipping circle. val initialRadius = Math.hypot(cx.toDouble(), cy.toDouble()).toFloat() // Create the animation. The final radius is 0. val anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0f) // Make the view invisible when the animation is done. anim.addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { super.onAnimationEnd(animation) myView.visibility = View.INVISIBLE } }) // Start the animation. anim.start() } else { // Set the view to visible without a circular reveal animation below // Android 5.0. myView.visibility = View.VISIBLE }
Java
// A previously visible view. final View myView = findViewById(R.id.my_view); // Check whether the runtime version is at least Android 5.0. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // Get the center for the clipping circle. int cx = myView.getWidth() / 2; int cy = myView.getHeight() / 2; // Get the initial radius for the clipping circle. float initialRadius = (float) Math.hypot(cx, cy); // Create the animation. The final radius is 0. Animator anim = ViewAnimationUtils.createCircularReveal(myView, cx, cy, initialRadius, 0f); // Make the view invisible when the animation is done. anim.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); myView.setVisibility(View.INVISIBLE); } }); // Start the animation. anim.start(); } else { // Set the view to visible without a circular reveal animation below Android // 5.0. myView.setVisibility(View.VISIBLE); }
في هذه الحالة، يتم تعيين نصف القطر الأولي لدائرة القطع ليكون بحجم العرض
حتى يصبح العرض مرئيًا قبل بدء الرسوم المتحركة. يتم تعيين نصف القطر النهائي على صفر بحيث يكون العرض مخفيًا عند انتهاء الرسوم المتحركة.
أضِف أداة معالجة الحدث إلى الصورة المتحركة ليتم ضبط مستوى العرض الخاص بطريقة العرض على INVISIBLE
عند اكتمال الصورة المتحركة.
مراجع إضافية
- صورة متحركة باستخدام Jetpack Compose
- الإيماءات باستخدام Jetpack Compose.