คู่มือนี้แสดงวิธีใช้ภาพเคลื่อนไหวแบบแตะเพื่อซูม การแตะเพื่อซูมช่วยให้แอปต่างๆ เช่น แกลเลอรีรูปภาพ แสดงภาพจากภาพขนาดย่อเป็นภาพเคลื่อนไหวที่เต็มหน้าจอ
ภาพเคลื่อนไหวแบบแตะเพื่อซูมจะมีลักษณะดังต่อไปนี้เมื่อขยายภาพขนาดย่อให้เต็มหน้าจอ
ดูตัวอย่างการทำงานแบบสมบูรณ์ได้ที่คลาส UIAnimation
จากโปรเจ็กต์ WearSpeakerSample ใน GitHub
สร้างมุมมอง
สร้างไฟล์เลย์เอาต์ที่มีเนื้อหาเวอร์ชันขนาดเล็กและขนาดใหญ่ที่คุณต้องการซูม
ตัวอย่างต่อไปนี้จะสร้าง ImageButton
สำหรับภาพปกรูปภาพที่แตะได้ และ ImageView
ที่แสดงมุมมองแบบขยายของรูปภาพ
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/container"
android:layout_width="match_parent"
androi>d:layo<ut_height="match_parent"
LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
a>ndroid:ori<entation="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="@drawa>ble/th<umb1"
> < android:scaleType="centerC
rop"
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 an>imatio<n, 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="matc>h_<parent">
android:layout_height="match_parent"
android:visibility="invisible"
android:contentDescription="@string/description_zoom_touch_close" /
/FrameLayout
ตั้งค่าภาพเคลื่อนไหวการซูม
เมื่อใช้เลย์เอาต์แล้ว ให้ตั้งค่าตัวแฮนเดิลเหตุการณ์ที่ทริกเกอร์ภาพเคลื่อนไหวการซูม ตัวอย่างต่อไปนี้จะเพิ่ม View.OnClickListener
ลงใน ImageButton
เพื่อแสดงภาพเคลื่อนไหวการซูมเมื่อผู้ใช้แตะปุ่มรูปภาพ
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); } ... }
ซูมมุมมอง
ใช้ภาพเคลื่อนไหวจากมุมมองขนาดปกติไปยังมุมมองที่ซูมเมื่อเหมาะสม โดยทั่วไป คุณจะต้องทำให้ภาพเคลื่อนไหวจากขอบเขตของมุมมองขนาดปกติไปยังขอบเขตของมุมมองขนาดใหญ่ วิธีการต่อไปนี้จะแสดงวิธีใช้ภาพเคลื่อนไหวแบบซูมที่ซูมจากภาพขนาดย่อไปยังมุมมองแบบขยาย โดยให้กำหนดรูปภาพที่มีความละเอียดสูงให้กับ "ภาพซูมเข้า" (ขยาย) ที่ซ่อนอยู่
ImageView
ตัวอย่างต่อไปนี้จะโหลดทรัพยากรรูปภาพขนาดใหญ่ในเธรด UI เพื่อลดความซับซ้อน ให้โหลดไฟล์ดังกล่าวในเทรดแยกต่างหากเพื่อป้องกันการบล็อกในเทรด UI แล้วตั้งค่าบิตแมปบนเทรด UI
โดยทั่วไปบิตแมปจะต้องไม่ใหญ่กว่าขนาดหน้าจอ ถัดไป ให้คำนวณขอบเขตเริ่มต้นและขอบเขตสิ้นสุดของ 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() / final>Bounds.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); }
ทำให้พร็อพเพอร์ตี้การวางตำแหน่งและการปรับขนาด 4 รายการเคลื่อนไหว ซึ่งได้แก่ X
, Y
, SCALE_X
และ SCALE_Y
ในเวลาเดียวกัน จากขอบเขตเริ่มต้นไปจนถึงขอบเขตสิ้นสุด เพิ่มภาพเคลื่อนไหว 4 ภาพนี้ลงใน
AnimatorSet
เพื่อให้เริ่มต้นพร้อมกัน
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; }
ซูมออกโดยเรียกใช้ภาพเคลื่อนไหวที่คล้ายกันกลับหลังเมื่อผู้ใช้แตะหน้าจอขณะที่รูปภาพกำลังซูมเข้า เพิ่ม View.OnClickListener
ลงใน ImageView
เมื่อแตะ ImageView
จะลดขนาดของภาพขนาดย่อและตั้งค่าระดับการมองเห็นเป็น GONE
เพื่อซ่อน
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; } }); }