本指南說明如何實作輕觸縮放動畫。觸控縮放可讓應用程式 (例如 圖片庫,以動畫呈現每個縮圖的景象,填滿整個畫面。
以下是輕觸縮放的動畫在展開縮圖以填滿 螢幕:
如需完整的有效範例,請參閱UIAnimation
WearSpeakerSample
專案。
建立檢視畫面
建立版面配置檔案,其中包含您要縮放的小型和大型內容。
以下範例會建立
ImageButton
可輕觸
圖片縮圖和
ImageView
,顯示
放大的圖片檢視畫面:
<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>
設定縮放動畫
套用版面配置後,請設定事件處理常式,以觸發縮放動畫。
下列範例會將
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() / 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); }
為四則位置和大小建立動畫
屬性:X
、
Y
,
SCALE_X
,
和
SCALE_Y
—同時,
從起始邊界到結束邊界。將這四個動畫加入
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; }
當使用者輕觸螢幕時,持續執行類似的動畫來縮小圖片
在 ImageView
中新增 View.OnClickListener
。輕觸一下時
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; } }); }