در حین استفاده از برنامه، اطلاعات جدید روی صفحه نمایش داده میشود و اطلاعات قدیمی حذف میشوند. تغییر فوری آنچه روی صفحه نمایش داده میشود میتواند آزاردهنده باشد و کاربران ممکن است محتوای جدیدی را که ناگهان ظاهر میشود، نبینند. انیمیشنها تغییرات را کند میکنند و با حرکت، چشم کاربر را به خود جلب میکنند تا بهروزرسانیها واضحتر باشند.
سه انیمیشن رایج وجود دارد که میتوانید برای نمایش یا پنهان کردن یک نما از آنها استفاده کنید: انیمیشنهای آشکار، انیمیشنهای محو شدن متقاطع و انیمیشنهای ورق زدن کارت.
یک انیمیشن محو متقاطع ایجاد کنید
یک انیمیشن محو متقاطع - که با نام حل شدن نیز شناخته میشود - به تدریج یک View یا ViewGroup را محو میکند و همزمان در نمای دیگر محو میشود. این انیمیشن برای موقعیتهایی که میخواهید محتوا یا نماها را در برنامه خود تغییر دهید مفید است. انیمیشن محو متقاطع نشان داده شده در اینجا از ViewPropertyAnimator استفاده میکند که برای اندروید ۳.۱ (سطح API ۱۲) و بالاتر در دسترس است.
در اینجا مثالی از یک crossfade از یک نشانگر پیشرفت به محتوای متن آورده شده است:
نماها را ایجاد کنید
دو نمایی که میخواهید crossfade شوند را ایجاد کنید. مثال زیر یک نشانگر پیشرفت و یک نمای متنی قابل اسکرول ایجاد میکند:
<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>
انیمیشن کراسفیدینگ را تنظیم کنید
برای تنظیم انیمیشن crossfade، موارد زیر را انجام دهید:
- متغیرهای عضو را برای نماهایی که میخواهید crossfade شوند ایجاد کنید. بعداً هنگام تغییر نماها در طول انیمیشن به این ارجاعات نیاز خواهید داشت.
- قابلیت مشاهدهی نمایی که قرار است کمرنگ شود را روی
GONEتنظیم کنید. این کار مانع از استفادهی نمای مورد نظر از فضای طرحبندی شده و آن را از محاسبات طرحبندی حذف میکند که این امر سرعت پردازش را افزایش میدهد. - ویژگی سیستمی
config_shortAnimTimeرا در یک متغیر عضو ذخیره کنید. این ویژگی یک مدت زمان استاندارد "کوتاه" برای انیمیشن تعریف میکند. این مدت زمان برای انیمیشنهای ظریف یا انیمیشنهایی که مکرراً رخ میدهند، ایدهآل است.config_longAnimTimeوconfig_mediumAnimTimeنیز در دسترس هستند.
در اینجا مثالی از استفاده از طرحبندی قطعه کد قبلی به عنوان نمای محتوای فعالیت آورده شده است:
کاتلین
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) } ... }
جاوا
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); } ... }
نماها را محو کنید
وقتی نماها به درستی تنظیم شدند، با انجام موارد زیر آنها را محو کنید:
- برای نمایی که محو میشود، مقدار آلفا را روی ۰ و قابلیت مشاهده را از مقدار اولیهی
GONEرویVISIBLEتنظیم کنید. این کار باعث میشود نما قابل مشاهده اما شفاف باشد. - برای نمایی که در حال محو شدن است، مقدار آلفای آن را از ۰ به ۱ متحرک کنید. برای نمایی که در حال محو شدن است، مقدار آلفا را از ۱ به ۰ متحرک کنید.
- با استفاده از
onAnimationEnd()درAnimator.AnimatorListener، میزان نمایش نمای محوشونده را رویGONEتنظیم کنید. حتی اگر مقدار آلفا 0 باشد، تنظیم میزان نمایش نمایGONEمانع از استفاده نمای از فضای طرحبندی شده و آن را از محاسبات طرحبندی حذف میکند، که این امر سرعت پردازش را افزایش میدهد.
روش زیر نمونهای از نحوه انجام این کار را نشان میدهد:
کاتلین
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 } }) } }
جاوا
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 استفاده میکند.
این چیزی است که یک تلنگر کارت به نظر می رسد:
اشیاء انیماتور را ایجاد کنید
برای ایجاد انیمیشن برگرداندن کارت، به چهار انیماتور نیاز دارید. دو انیماتور برای زمانی هستند که جلوی کارت به سمت بیرون و چپ و وقتی که به سمت داخل و از چپ حرکت میکند، هستند. دو انیماتور دیگر برای زمانی هستند که پشت کارت به سمت داخل و از راست و وقتی که به سمت بیرون و راست حرکت میکند، هستند.
card_flip_left_in.xml
<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>
card_flip_left_out.xml
<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>
card_flip_right_in.xml
<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>
card_flip_right_out.xml
<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" />
قطعات را ایجاد کنید
کلاسهای fragment را برای جلو و عقب کارت ایجاد کنید. در کلاسهای fragment خود، طرحبندیهایی را که از متد onCreateView() ایجاد کردهاید، برگردانید. سپس میتوانید نمونههایی از این fragment را در activity والد که میخواهید کارت را در آن نمایش دهید، ایجاد کنید.
مثال زیر کلاسهای fragment تو در تو را درون activity والدی که از آنها استفاده میکند، نشان میدهد:
کاتلین
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) } }
جاوا
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); } } }
متحرک سازی برگرداندن کارت
نمایش قطعات (fragment) درون یک activity والد. برای انجام این کار، طرحبندی (layout) مربوط به activity خود را ایجاد کنید. مثال زیر یک 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" />
در کد activity، نمای محتوا را به عنوان طرحبندی که ایجاد میکنید تنظیم کنید. نمایش یک قطعه پیشفرض هنگام ایجاد activity، رویه خوبی است. activity مثال زیر نحوه نمایش پیشفرض جلوی کارت را نشان میدهد:
کاتلین
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() } } ... }
جاوا
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(); } } ... }
با نمایش روی کارت، میتوانید پشت کارت را با انیمیشن برگرداندن در زمان مناسب نشان دهید. روشی برای نمایش طرف دیگر کارت ایجاد کنید که کارهای زیر را انجام دهد:
- انیمیشنهای سفارشی که برای انتقالهای قطعه ایجاد کردهاید را تنظیم میکند.
- قطعه نمایش داده شده را با یک قطعه جدید جایگزین میکند و این رویداد را با انیمیشنهای سفارشی که ایجاد کردهاید، متحرک میکند.
- قطعه نمایش داده شده قبلی را به پشته قطعه اضافه میکند، بنابراین وقتی کاربر دکمه برگشت را لمس میکند، کارت به عقب برمیگردد.
کاتلین
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() } }
جاوا
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(); } }
ایجاد انیمیشن آشکارسازی دایرهای
انیمیشنهای آشکارسازی (Reveal animations) هنگامی که گروهی از عناصر رابط کاربری را نمایش یا پنهان میکنید، پیوستگی بصری را برای کاربران فراهم میکنند. متد ViewAnimationUtils.createCircularReveal() به شما امکان میدهد یک دایره برش را متحرک کنید تا یک نما (view) را آشکار یا پنهان کنید. این انیمیشن در کلاس ViewAnimationUtils ارائه شده است که برای اندروید ۵.۰ (سطح API ۲۱) و بالاتر در دسترس است.
در اینجا مثالی آورده شده است که نشان میدهد چگونه میتوان یک نمای قبلاً نامرئی را آشکار کرد:
کاتلین
// 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 }
جاوا
// 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 برای مرکز دایره برش هستند. معمولاً این مرکز نما است، اما میتوانید از نقطهای که کاربر لمس میکند نیز استفاده کنید تا انیمیشن از جایی که انتخاب میکند شروع شود. پارامتر چهارم، شعاع شروع دایره برش است.
در مثال قبلی، شعاع اولیه روی صفر تنظیم شده است تا نمای نمایش داده شده توسط دایره پنهان شود. آخرین پارامتر، شعاع نهایی دایره است. هنگام نمایش یک نما، شعاع نهایی را بزرگتر از نمای مورد نظر قرار دهید تا نما قبل از اتمام انیمیشن کاملاً آشکار شود.
برای پنهان کردن یک نمای قابل مشاهده قبلی، موارد زیر را انجام دهید:
کاتلین
// 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 }
جاوا
// 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 تنظیم کرد.
