Hướng dẫn này minh hoạ cách triển khai ảnh động nhấn để thu phóng. Tính năng nhấn để thu phóng cho phép các ứng dụng, chẳng hạn như thư viện ảnh, tạo ảnh động cho một chế độ xem từ hình thu nhỏ để lấp đầy màn hình.
Dưới đây là ảnh động khi nhấn để thu phóng sẽ mở rộng một hình thu nhỏ để lấp đầy màn hình:
Để xem ví dụ đầy đủ về cách hoạt động, hãy xem lớp UIAnimation
trong dự án WearSpeakerSample trên GitHub.
Tạo chế độ xem
Tạo tệp bố cục chứa phiên bản nhỏ và lớn của nội dung mà bạn muốn thu phóng.
Ví dụ sau đây sẽ tạo một ImageButton
cho hình thu nhỏ của hình ảnh có thể nhấn vào và một ImageView
hiển thị khung hiển thị phóng to của hình ảnh đó:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<ImageButton
android:id="@+id/thumb_button_1"
android:layout_width="100dp"
android:layout_height="75dp"
android:layout_marginRight="1dp"
android:src="@drawable/thumb1"
android:scaleType="centerCrop"
android:contentDescription="@string/description_image_1" />
</LinearLayout>
<!-- This initially hidden ImageView
holds the zoomed version of
the preceding images. Without transformations applied, it fills the entire
screen. To achieve the zoom animation, this view's bounds are animated
from the bounds of the preceding thumbnail button to its final laid-out
bounds.
-->
<ImageView
android:id="@+id/expanded_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="invisible"
android:contentDescription="@string/description_zoom_touch_close" />
</FrameLayout>
Thiết lập ảnh động thu phóng
Sau khi áp dụng bố cục, hãy thiết lập trình xử lý sự kiện kích hoạt ảnh động thu phóng. Ví dụ sau đây thêm View.OnClickListener
vào ImageButton
để thực thi ảnh động thu phóng khi người dùng nhấn vào nút hình ảnh:
Kotlin
class ZoomActivity : FragmentActivity() { // Hold a reference to the current animator so that it can be canceled // midway. private var currentAnimator: Animator? = null // The system "short" animation time duration in milliseconds. This duration // is ideal for subtle animations or animations that occur frequently. private var shortAnimationDuration: Int = 0 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_zoom) // Hook up taps on the thumbnail views. binding.thumbButton1.setOnClickListener { zoomImageFromThumb(thumb1View, R.drawable.image1) } // Retrieve and cache the system's default "short" animation time. shortAnimationDuration = resources.getInteger(android.R.integer.config_shortAnimTime) } ... }
Java
public class ZoomActivity extends FragmentActivity { // Hold a reference to the current animator so that it can be canceled // mid-way. private Animator currentAnimator; // The system "short" animation time duration in milliseconds. This duration // is ideal for subtle animations or animations that occur frequently. private int shortAnimationDuration; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_zoom); // Hook up taps on the thumbnail views. binding.thumbButton1.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { zoomImageFromThumb(thumb1View, R.drawable.image1); } }); // Retrieve and cache the system's default "short" animation time. shortAnimationDuration = getResources().getInteger( android.R.integer.config_shortAnimTime); } ... }
Thu phóng chế độ xem
Tạo ảnh động từ chế độ xem có kích thước bình thường sang chế độ xem thu phóng khi thích hợp. Nói chung, bạn cần tạo ảnh động từ ranh giới của khung hiển thị có kích thước bình thường đến giới hạn của khung hiển thị có kích thước lớn hơn. Các phương thức sau đây cho biết cách triển khai ảnh động thu phóng từ hình thu nhỏ thành chế độ xem phóng to. Để làm như vậy, hãy gán hình ảnh có độ phân giải cao cho ImageView
"đã phóng to" (đã phóng to) ẩn.
Để đơn giản, ví dụ sau đây sẽ tải một tài nguyên hình ảnh lớn trên luồng giao diện người dùng. Tải tệp này trong một luồng riêng để tránh chặn luồng giao diện người dùng, sau đó đặt bitmap trên luồng giao diện người dùng.
Nhìn chung, bitmap không được lớn hơn kích thước màn hình. Tiếp theo, hãy tính toán giới hạn bắt đầu và kết thúc cho ImageView
.
Kotlin
private fun zoomImageFromThumb(thumbView: View, imageResId: Int) { // If there's an animation in progress, cancel it immediately and // proceed with this one. currentAnimator?.cancel() // Load the high-resolution "zoomed-in" image. binding.expandedImage.setImageResource(imageResId) // Calculate the starting and ending bounds for the zoomed-in image. val startBoundsInt = Rect() val finalBoundsInt = Rect() val globalOffset = Point() // The start bounds are the global visible rectangle of the thumbnail, // and the final bounds are the global visible rectangle of the // container view. Set the container view's offset as the origin for the // bounds, since that's the origin for the positioning animation // properties (X, Y). thumbView.getGlobalVisibleRect(startBoundsInt) binding.container.getGlobalVisibleRect(finalBoundsInt, globalOffset) startBoundsInt.offset(-globalOffset.x, -globalOffset.y) finalBoundsInt.offset(-globalOffset.x, -globalOffset.y) val startBounds = RectF(startBoundsInt) val finalBounds = RectF(finalBoundsInt) // Using the "center crop" technique, adjust the start bounds to be the // same aspect ratio as the final bounds. This prevents unwanted // stretching during the animation. Calculate the start scaling factor. // The end scaling factor is always 1.0. val startScale: Float if ((finalBounds.width() / finalBounds.height() > startBounds.width() / startBounds.height())) { // Extend start bounds horizontally. startScale = startBounds.height() / finalBounds.height() val startWidth: Float = startScale * finalBounds.width() val deltaWidth: Float = (startWidth - startBounds.width()) / 2 startBounds.left -= deltaWidth.toInt() startBounds.right += deltaWidth.toInt() } else { // Extend start bounds vertically. startScale = startBounds.width() / finalBounds.width() val startHeight: Float = startScale * finalBounds.height() val deltaHeight: Float = (startHeight - startBounds.height()) / 2f startBounds.top -= deltaHeight.toInt() startBounds.bottom += deltaHeight.toInt() } // Hide the thumbnail and show the zoomed-in view. When the animation // begins, it positions the zoomed-in view in the place of the // thumbnail. thumbView.alpha = 0f animateZoomToLargeImage(startBounds, finalBounds, startScale) setDismissLargeImageAnimation(thumbView, startBounds, startScale) }
Java
private void zoomImageFromThumb(final View thumbView, int imageResId) { // If there's an animation in progress, cancel it immediately and // proceed with this one. if (currentAnimator != null) { currentAnimator.cancel(); } // Load the high-resolution "zoomed-in" image. binding.expandedImage.setImageResource(imageResId); // Calculate the starting and ending bounds for the zoomed-in image. final Rect startBounds = new Rect(); final Rect finalBounds = new Rect(); final Point globalOffset = new Point(); // The start bounds are the global visible rectangle of the thumbnail, // and the final bounds are the global visible rectangle of the // container view. Set the container view's offset as the origin for the // bounds, since that's the origin for the positioning animation // properties (X, Y). thumbView.getGlobalVisibleRect(startBounds); findViewById(R.id.container) .getGlobalVisibleRect(finalBounds, globalOffset); startBounds.offset(-globalOffset.x, -globalOffset.y); finalBounds.offset(-globalOffset.x, -globalOffset.y); // Using the "center crop" technique, adjust the start bounds to be the // same aspect ratio as the final bounds. This prevents unwanted // stretching during the animation. Calculate the start scaling factor. // The end scaling factor is always 1.0. float startScale; if ((float) finalBounds.width() / finalBounds.height() > (float) startBounds.width() / startBounds.height()) { // Extend start bounds horizontally. startScale = (float) startBounds.height() / finalBounds.height(); float startWidth = startScale * finalBounds.width(); float deltaWidth = (startWidth - startBounds.width()) / 2; startBounds.left -= deltaWidth; startBounds.right += deltaWidth; } else { // Extend start bounds vertically. startScale = (float) startBounds.width() / finalBounds.width(); float startHeight = startScale * finalBounds.height(); float deltaHeight = (startHeight - startBounds.height()) / 2; startBounds.top -= deltaHeight; startBounds.bottom += deltaHeight; } // Hide the thumbnail and show the zoomed-in view. When the animation // begins, it positions the zoomed-in view in the place of the // thumbnail. thumbView.setAlpha(0f); animateZoomToLargeImage(startBounds, finalBounds, startScale); setDismissLargeImageAnimation(thumbView, startBounds, startScale); }
Tạo ảnh động đồng thời cho 4 thuộc tính vị trí và kích thước – X
, Y
, SCALE_X
và SCALE_Y
từ giới hạn bắt đầu đến giới hạn kết thúc. Thêm 4 ảnh động này vào AnimatorSet
để chúng bắt đầu cùng lúc.
Kotlin
private fun animateZoomToLargeImage(startBounds: RectF, finalBounds: RectF, startScale: Float) { binding.expandedImage.visibility = View.VISIBLE // Set the pivot point for SCALE_X and SCALE_Y transformations to the // top-left corner of the zoomed-in view. The default is the center of // the view. binding.expandedImage.pivotX = 0f binding.expandedImage.pivotY = 0f // Construct and run the parallel animation of the four translation and // scale properties: X, Y, SCALE_X, and SCALE_Y. currentAnimator = AnimatorSet().apply { play( ObjectAnimator.ofFloat( binding.expandedImage, View.X, startBounds.left, finalBounds.left) ).apply { with(ObjectAnimator.ofFloat(binding.expandedImage, View.Y, startBounds.top, finalBounds.top)) with(ObjectAnimator.ofFloat(binding.expandedImage, View.SCALE_X, startScale, 1f)) with(ObjectAnimator.ofFloat(binding.expandedImage, View.SCALE_Y, startScale, 1f)) } duration = shortAnimationDuration.toLong() interpolator = DecelerateInterpolator() addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { currentAnimator = null } override fun onAnimationCancel(animation: Animator) { currentAnimator = null } }) start() } }
Java
private void animateZoomToLargeImage(Rect startBounds, Rect finalBounds, Float startScale) { binding.expandedImage.setVisibility(View.VISIBLE); // Set the pivot point for SCALE_X and SCALE_Y transformations to the // top-left corner of the zoomed-in view. The default is the center of // the view. binding.expandedImage.setPivotX(0f); binding.expandedImage.setPivotY(0f); // Construct and run the parallel animation of the four translation and // scale properties: X, Y, SCALE_X, and SCALE_Y. AnimatorSet set = new AnimatorSet(); set .play(ObjectAnimator.ofFloat(binding.expandedImage, View.X, startBounds.left, finalBounds.left)) .with(ObjectAnimator.ofFloat(binding.expandedImage, View.Y, startBounds.top, finalBounds.top)) .with(ObjectAnimator.ofFloat(binding.expandedImage, View.SCALE_X, startScale, 1f)) .with(ObjectAnimator.ofFloat(binding.expandedImage, View.SCALE_Y, startScale, 1f)); set.setDuration(shortAnimationDuration); set.setInterpolator(new DecelerateInterpolator()); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { currentAnimator = null; } @Override public void onAnimationCancel(Animator animation) { currentAnimator = null; } }); set.start(); currentAnimator = set; }
Thu nhỏ bằng cách chạy ảnh động tương tự theo chiều ngược lại khi người dùng nhấn vào màn hình trong khi hình ảnh đang được phóng to. Thêm View.OnClickListener
vào ImageView
. Khi được nhấn, ImageView
sẽ thu nhỏ về kích thước của hình thu nhỏ của hình ảnh và đặt chế độ hiển thị thành GONE
để ẩn hình ảnh.
Kotlin
private fun setDismissLargeImageAnimation(thumbView: View, startBounds: RectF, startScale: Float) { // When the zoomed-in image is tapped, it zooms down to the original // bounds and shows the thumbnail instead of the expanded image. binding.expandedImage.setOnClickListener { currentAnimator?.cancel() // Animate the four positioning and sizing properties in parallel, // back to their original values. currentAnimator = AnimatorSet().apply { play(ObjectAnimator.ofFloat(binding.expandedImage, View.X, startBounds.left)).apply { with(ObjectAnimator.ofFloat(binding.expandedImage, View.Y, startBounds.top)) with(ObjectAnimator.ofFloat(binding.expandedImage, View.SCALE_X, startScale)) with(ObjectAnimator.ofFloat(binding.expandedImage, View.SCALE_Y, startScale)) } duration = shortAnimationDuration.toLong() interpolator = DecelerateInterpolator() addListener(object : AnimatorListenerAdapter() { override fun onAnimationEnd(animation: Animator) { thumbView.alpha = 1f binding.expandedImage.visibility = View.GONE currentAnimator = null } override fun onAnimationCancel(animation: Animator) { thumbView.alpha = 1f binding.expandedImage.visibility = View.GONE currentAnimator = null } }) start() } } }
Java
private void setDismissLargeImageAnimation(View thumbView, Rect startBounds, Float startScale) { // When the zoomed-in image is tapped, it zooms down to the original // bounds and shows the thumbnail instead of the expanded image. final float startScaleFinal = startScale; binding.expandedImage.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (currentAnimator != null) { currentAnimator.cancel(); } // Animate the four positioning and sizing properties in // parallel, back to their original values. AnimatorSet set = new AnimatorSet(); set.play(ObjectAnimator .ofFloat(binding.expandedImage, View.X, startBounds.left)) .with(ObjectAnimator .ofFloat(binding.expandedImage, View.Y,startBounds.top)) .with(ObjectAnimator .ofFloat(binding.expandedImage, View.SCALE_X, startScaleFinal)) .with(ObjectAnimator .ofFloat(binding.expandedImage, View.SCALE_Y, startScaleFinal)); set.setDuration(shortAnimationDuration); set.setInterpolator(new DecelerateInterpolator()); set.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { thumbView.setAlpha(1f); binding.expandedImage.setVisibility(View.GONE); currentAnimator = null; } @Override public void onAnimationCancel(Animator animation) { thumbView.setAlpha(1f); binding.expandedImage.setVisibility(View.GONE); currentAnimator = null; } }); set.start(); currentAnimator = set; } }); }