Trong khi bạn dùng ứng dụng, thông tin mới sẽ xuất hiện trên màn hình và trên sẽ bị xoá. Bạn có thể thay đổi nội dung hiển thị trên màn hình ngay lập tức gây khó chịu và người dùng có thể bỏ lỡ nội dung mới xuất hiện đột ngột. Hoạt ảnh chậm chỉnh sửa các thay đổi và thu hút sự chú ý của người dùng bằng chuyển động để nội dung cập nhật rõ ràng hơn.
Sau đây là 3 ảnh động phổ biến mà bạn có thể sử dụng để hiện hoặc ẩn khung hiển thị: hiển thị ảnh động, ảnh động mờ dần và ảnh động dạng thẻ.
Tạo ảnh động mờ dần
Ảnh động mờ dần (còn gọi là hiệu ứng làm tan) mờ dần
một View
hoặc
ViewGroup
khi đồng thời
mờ dần trong một biểu trưng khác. Ảnh động này hữu ích cho các trường hợp mà bạn muốn
chuyển đổi nội dung hoặc chế độ xem trong ứng dụng của bạn. Ảnh động mờ dần xuất hiện ở đây sử dụng
ViewPropertyAnimator
!
API này có sẵn cho Android 3.1 (API cấp 12) trở lên.
Dưới đây là ví dụ về việc chuyển đổi từ chỉ báo tiến trình sang nội dung văn bản:
Tạo chế độ xem
Tạo hai chế độ xem mà bạn muốn kết hợp. Ví dụ sau đây sẽ tạo một chỉ báo tiến trình và chế độ xem văn bản có thể cuộn:
<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>
Thiết lập ảnh động mờ dần
Để thiết lập ảnh động mờ dần, hãy làm như sau:
- Tạo các biến thành phần cho những chế độ xem mà bạn muốn chuyển đổi. Bạn cần các tệp tham chiếu này sau khi sửa đổi khung hiển thị trong ảnh động.
- Thiết lập chế độ hiển thị của thành phần hiển thị đang được làm mờ thành
GONE
. Việc này sẽ ngăn chế độ xem sử dụng không gian bố cục và loại bỏ không gian đó khỏi tính toán bố cục, tức là tốc độ đang xử lý - Lưu
config_shortAnimTime
vào bộ nhớ đệm thuộc tính hệ thống trong một biến thành phần. Thuộc tính này xác định tiêu chuẩn "short" cho ảnh động. Thời lượng này là lý tưởng cho các hoạt ảnh tinh tế hoặc các ảnh động xuất hiện thường xuyên.config_longAnimTime
vàconfig_mediumAnimTime
tại đây cũng có sẵn.
Dưới đây là ví dụ sử dụng bố cục từ đoạn mã trước đó làm chế độ xem nội dung hoạt động:
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); } ... }
Làm mờ các khung hiển thị
Khi các khung hiển thị được thiết lập đúng cách, hãy làm mờ các khung hiển thị đó bằng cách thực hiện như sau:
- Đối với khung hiển thị đang mờ dần, hãy đặt giá trị alpha thành 0 và chế độ hiển thị
đến
VISIBLE
so với tên ban đầu làGONE
. Thao tác này sẽ làm cho chế độ xem có thể nhìn thấy nhưng trong suốt. - Đối với khung hiển thị đang mờ dần, hãy tạo ảnh động cho giá trị alpha từ 0 thành 1. Đối với khung hiển thị mờ dần, tạo ảnh động cho giá trị alpha từ 1 đến 0.
- Sử dụng
onAnimationEnd()
theo phong cáchAnimator.AnimatorListener
, đặt chế độ hiển thị của thành phần hiển thị mờ dần thànhGONE
. Mặc dù giá trị alpha là 0, việc đặt chế độ hiển thị của khung hiển thị thànhGONE
sẽ ngăn khung hiển thị này sử dụng không gian bố cục và loại bỏ không gian đó khỏi tính toán bố cục, tức là tốc độ tiếp tục xử lý.
Phương thức sau đây cho thấy ví dụ về cách thực hiện việc này:
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); } }); } }
Tạo ảnh động lật thẻ
Lật thẻ để chuyển đổi giữa các chế độ xem nội dung bằng cách hiển thị một ảnh động mô phỏng
lật thẻ. Ảnh động lật thẻ hiển thị ở đây sử dụng
FragmentTransaction
.
Sau đây là ví dụ về việc lật thẻ:
Tạo đối tượng trình tạo ảnh động
Để tạo ảnh động lật thẻ, bạn cần có 4 ảnh động. Hai hoạ sĩ hoạt hình đang khi mặt trước của thẻ có hiệu ứng động, ở bên trái và khi thẻ tạo hiệu ứng động ở bên trái và bên trái. Hai ảnh động còn lại là về trường hợp mặt sau của thẻ tạo hiệu ứng động trong và bên phải, cũng như thời điểm xuất hiện hiệu ứng động và ở bên phải.
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>
Tạo chế độ xem
Mỗi bên của thẻ là một bố cục riêng biệt có thể chứa bất kỳ nội dung nào mà bạn mong muốn, chẳng hạn như 2 khung hiển thị văn bản, 2 hình ảnh hoặc bất kỳ tổ hợp khung hiển thị nào để lật ở giữa. Sử dụng 2 bố cục này trong các phân đoạn mà bạn tạo ảnh động sau này. Chiến lược phát hành đĩa đơn bố cục sau đây tạo ra một mặt của thẻ, hiển thị văn bản:
<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>
Bố cục tiếp theo sẽ tạo mặt còn lại của thẻ, hiển thị một
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" />
Tạo mảnh
Tạo các lớp mảnh cho mặt trước và mặt sau của thẻ. Trong mảnh
lớp, hãy trả về bố cục mà bạn đã tạo từ
onCreateView()
. Sau đó, bạn có thể tạo các thực thể của mảnh này trong hoạt động gốc
nơi bạn muốn hiển thị thẻ.
Ví dụ sau đây cho thấy các lớp mảnh lồng nhau bên trong hoạt động gốc sử dụng chúng:
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); } } }
Tạo ảnh động khi lật thẻ
Hiển thị các mảnh bên trong hoạt động gốc. Để thực hiện việc này, hãy tạo bố cục
cho hoạt động của bạn. Ví dụ sau đây sẽ tạo một
FrameLayout
mà bạn có thể thêm
mảnh vào thời gian chạy:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Trong mã hoạt động, đặt khung hiển thị nội dung thành bố cục mà bạn tạo. Bạn nên hiện một mảnh mặc định khi tạo hoạt động. Chiến lược phát hành đĩa đơn hoạt động ví dụ sau cho biết cách hiển thị mặt trước của thẻ bằng mặc định:
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(); } } ... }
Khi mặt trước của thẻ hiển thị, bạn có thể hiển thị mặt sau của thẻ cùng với ảnh động lật vào thời điểm thích hợp. Tạo một phương thức để hiển thị mặt còn lại của để thực hiện những việc sau:
- Đặt ảnh động tuỳ chỉnh mà bạn đã tạo cho hiệu ứng chuyển đổi mảnh.
- Thay thế mảnh được hiển thị bằng một mảnh mới và tạo ảnh động cho sự kiện này bằng các ảnh động tuỳ chỉnh mà bạn đã tạo.
- Thêm mảnh đã hiển thị trước đó vào ngăn xếp lui của mảnh để khi người dùng nhấn vào nút Quay lại, thẻ sẽ lật ngược trở lại.
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(); } }
Tạo ảnh động hiển thị dạng vòng tròn
Ảnh động hiển thị giúp người dùng nhìn thấy liền mạch khi bạn hiện hoặc ẩn một nhóm
phần tử trên giao diện người dùng. Chiến lược phát hành đĩa đơn
ViewAnimationUtils.createCircularReveal()
cho phép bạn tạo ảnh động cho một vòng tròn cắt đoạn để hiển thị hoặc ẩn một khung hiển thị. Chiến dịch này
hoạt ảnh được cung cấp trong
Lớp ViewAnimationUtils
,
Bản phát hành này có sẵn cho Android 5.0 (API cấp 21) trở lên.
Dưới đây là ví dụ minh hoạ cách hiển thị một khung hiển thị bị ẩn trước đó:
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); }
Ảnh động ViewAnimationUtils.createCircularReveal()
có 5 tham số.
Tham số đầu tiên là chế độ xem mà bạn muốn ẩn hoặc hiện trên màn hình. Chiến lược phát hành đĩa đơn
hai tham số tiếp theo là toạ độ X và Y của điểm cắt
vòng kết nối. Thông thường, đây là tâm của chế độ xem, nhưng bạn cũng có thể sử dụng
trỏ người dùng nhấn để ảnh động bắt đầu tại vị trí họ chọn. Chiến lược phát hành đĩa đơn
tham số thứ tư là bán kính bắt đầu của vòng tròn cắt.
Trong ví dụ trước, bán kính ban đầu được đặt là 0 để chế độ xem đang được hiển thị bị vòng tròn ẩn đi. Thông số cuối cùng là bán kính cuối cùng của vòng kết nối. Khi hiển thị khung hiển thị, hãy làm cho bán kính cuối cùng lớn hơn bán kính để khung hiển thị có thể hiển thị hoàn toàn trước khi ảnh động kết thúc.
Để ẩn một chế độ xem đã hiển thị trước đó, hãy làm như sau:
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); }
Trong trường hợp này, bán kính ban đầu của vòng tròn cắt được đặt lớn bằng
để hiển thị khung hiển thị trước khi ảnh động bắt đầu. Trận chung kết
bán kính được đặt thành 0 để chế độ xem bị ẩn khi ảnh động kết thúc.
Thêm trình nghe vào ảnh động để có thể đặt chế độ hiển thị của khung hiển thị thành
INVISIBLE
khi ảnh động
hoàn tất.