In dieser Anleitung wird beschrieben, wie eine Animation zum Zoomen durch Tippen implementiert wird. Durch Tippen und Zoomen können Apps wie z. B. Fotogalerien eine Ansicht von einer Miniaturansicht animieren, sodass sie den gesamten Bildschirm ausfüllt.
So sieht eine Animation zum Zoomen durch Tippen aus, wenn eine Miniaturansicht maximiert wird, damit sie den Bildschirm ausfüllt:
Ein vollständig funktionierendes Beispiel findest du in der Klasse UIAnimation
aus dem WearSpeakerSample-Projekt auf GitHub.
Ansichten erstellen
Erstellen Sie eine Layoutdatei mit der kleinen und großen Version des Inhalts, den Sie zoomen möchten.
Im folgenden Beispiel wird ein ImageButton
für die Miniaturansicht eines antippbaren Bilds und ein ImageView
für die vergrößerte Ansicht des Bildes erstellt:
<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>
Zoomanimation einrichten
Nachdem Sie das Layout angewendet haben, richten Sie die Event-Handler ein, die die Zoomanimation auslösen. Im folgenden Beispiel wird ein View.OnClickListener
zum ImageButton
hinzugefügt, um die Zoomanimation auszuführen, wenn der Nutzer auf die Bildschaltfläche tippt:
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); } ... }
Ansicht vergrößern
Animieren Sie gegebenenfalls von der normalen Ansicht zur vergrößerten Ansicht. Im Allgemeinen müssen Sie von den Grenzen der normalen Ansicht zu den Begrenzungen der größeren Ansicht bewegen. Die folgenden Methoden zeigen, wie eine Zoomanimation implementiert wird, die von einer Miniaturansicht zu einer vergrößerten Ansicht heranzoomt. Weisen Sie dazu das hochauflösende Bild dem ausgeblendeten (vergrößerten) ImageView
-Objekt zu.
Im folgenden Beispiel wird der Einfachheit halber eine große Bildressource im UI-Thread geladen. Laden Sie ihn in einen separaten Thread, um eine Blockierung des UI-Threads zu verhindern, und legen Sie dann die Bitmap für den UI-Thread fest.
Im Allgemeinen darf die Bitmap nicht größer als die Bildschirmgröße sein. Berechnen Sie als Nächstes die Start- und Endgrenzen für 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); }
Animieren Sie die vier Positionierungs- und Größeneigenschaften X
, Y
, SCALE_X
und SCALE_Y
gleichzeitig von den Start- bis Endgrenzen. Fügen Sie diese vier Animationen einem AnimatorSet
hinzu, damit sie gleichzeitig starten.
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; }
Zoomen Sie heraus, indem Sie rückwärts eine ähnliche Animation ausführen, wenn der Nutzer auf den Bildschirm tippt, während das Bild herangezoomt ist. Füge der ImageView
einen View.OnClickListener
hinzu. Beim Antippen wird ImageView
auf die Größe der Miniaturansicht des Bildes minimiert und die Sichtbarkeit auf GONE
gesetzt, um es auszublenden.
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; } }); }