blob: 6a38e7d8c71d4e2249f6bc22712ea207bcf99d19 [file] [log] [blame]
Robert Ly3f532122012-10-03 14:20:26 -07001page.title=Zooming a View
2trainingnavtop=true
3
4@jd:body
5
6 <div id="tb-wrapper">
7 <div id="tb">
8 <h2>
9 This lesson teaches you to:
10 </h2>
11 <ol>
12 <li>
13 <a href="#views">Create the Views</a>
14 </li>
15 <li>
16 <a href="#setup">Set up the Zoom Animation</a>
17 </li>
18 <li>
19 <a href="#animate">Zoom the View</a>
20 </li>
21 </ol>
Roman Nurikfb80c9e2013-01-09 08:35:53 -080022 <h2>
23 Try it out
24 </h2>
25 <div class="download-box">
26 <a href="{@docRoot}shareables/training/Animations.zip" class=
27 "button">Download the sample app</a>
28 <p class="filename">
29 Animations.zip
30 </p>
31 </div>
Robert Ly3f532122012-10-03 14:20:26 -070032 </div>
33 </div>
34 <p>
35 This lesson demonstrates how to do a touch-to-zoom animation, which is useful for apps such as photo
36 galleries to animate a view from a thumbnail to a full-size image that fills the screen.
37 </p>
38 <p>Here's what a touch-to-zoom animation looks like that
39 expands an image thumbnail to fill the screen:
40 </p>
41
42 <div class="framed-galaxynexus-land-span-8">
43 <video class="play-on-hover" autoplay>
44 <source src="anim_zoom.mp4" type="video/mp4">
45 <source src="anim_zoom.webm" type="video/webm">
46 <source src="anim_zoom.ogv" type="video/ogg">
47 </video>
48 </div>
49 <div class="figure-caption">
50 Zoom animation
51 <div class="video-instructions">&nbsp;</div>
52 </div>
53
54 <p>
55 If you want to jump ahead and see a full working example,
56 <a href="{@docRoot}shareables/training/Animations.zip">download</a> and
57 run the sample app and select the
58 Zoom example. See the following files for the code implementation:
59 </p>
60 <ul>
61 <li>
62 <code>src/TouchHighlightImageButton.java</code> (a simple helper class that shows a blue
63 touch highlight when the image button is pressed)
64 </li>
65 <li>
66 <code>src/ZoomActivity.java</code>
67 </li>
68 <li>
69 <code>layout/activity_zoom.xml</code>
70 </li>
71 </ul>
72 <h2 id="views">
73 Create the Views
74 </h2>
75 <p>
76 Create a layout file that contains the small and large version of the content that you want
77 to zoom. The following example creates an {@link android.widget.ImageButton} for clickable image thumbnail
78 and an {@link android.widget.ImageView} that displays the enlarged view of the image:
79 </p>
80 <pre>
81&lt;FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
82 android:id="@+id/container"
83 android:layout_width="match_parent"
84 android:layout_height="match_parent"&gt;
85
86 &lt;LinearLayout android:layout_width="match_parent"
87 android:layout_height="wrap_content"
88 android:orientation="vertical"
89 android:padding="16dp"&gt;
90
91 &lt;ImageButton
92 android:id="@+id/thumb_button_1"
93 android:layout_width="100dp"
94 android:layout_height="75dp"
95 android:layout_marginRight="1dp"
96 android:src="@drawable/thumb1"
97 android:scaleType="centerCrop"
98 android:contentDescription="@string/description_image_1" /&gt;
99
100 &lt;/LinearLayout&gt;
101
102 &lt;!-- This initially-hidden ImageView will hold the expanded/zoomed version of
103 the images above. Without transformations applied, it takes up the entire
104 screen. To achieve the "zoom" animation, this view's bounds are animated
105 from the bounds of the thumbnail button above, to its final laid-out
106 bounds.
107 --&gt;
108
109 &lt;ImageView
110 android:id="@+id/expanded_image"
111 android:layout_width="match_parent"
112 android:layout_height="match_parent"
113 android:visibility="invisible"
114 android:contentDescription="@string/description_zoom_touch_close" /&gt;
115
116&lt;/FrameLayout&gt;
117</pre>
118 <h2 id="setup">
119 Set up the Zoom Animation
120 </h2>
121 <p>
122 Once you apply your layout, set up the event handlers that trigger the zoom animation.
123 The following example adds a {@link android.view.View.OnClickListener} to the {@link
124 android.widget.ImageButton} to execute the zoom animation when the user
125 clicks the image button:
126 </p>
127 <pre>
128public class ZoomActivity extends FragmentActivity {
129 // Hold a reference to the current animator,
130 // so that it can be canceled mid-way.
131 private Animator mCurrentAnimator;
132
133 // The system "short" animation time duration, in milliseconds. This
134 // duration is ideal for subtle animations or animations that occur
135 // very frequently.
136 private int mShortAnimationDuration;
137
138 &#64;Override
139 protected void onCreate(Bundle savedInstanceState) {
140 super.onCreate(savedInstanceState);
141 setContentView(R.layout.activity_zoom);
142
143 // Hook up clicks on the thumbnail views.
144
145 final View thumb1View = findViewById(R.id.thumb_button_1);
146 thumb1View.setOnClickListener(new View.OnClickListener() {
147 &#64;Override
148 public void onClick(View view) {
149 zoomImageFromThumb(thumb1View, R.drawable.image1);
150 }
151 });
152
153 // Retrieve and cache the system's default "short" animation time.
154 mShortAnimationDuration = getResources().getInteger(
155 android.R.integer.config_shortAnimTime);
156 }
157 ...
158}
159</pre>
160 <h2 id="animate">
161 Zoom the View
162 </h2>
163 <p>
164 You'll now need to animate from the normal sized view to the zoomed view
165 when appropriate. In general, you need to animate from the bounds of the normal-sized view to the
166 bounds of the larger-sized view. The following method shows you how to implement a zoom animation that
167 zooms from an image thumbnail to an enlarged view by doing the following things:
168 </p>
169 <ol>
170 <li>Assign the high-res image to the hidden "zoomed-in" (enlarged) {@link
171 android.widget.ImageView}. The following example loads a large image resource on the UI
172 thread for simplicity. You will want to do this loading in a separate thread to prevent
173 blocking on the UI thread and then set the bitmap on the UI thread. Ideally, the bitmap
174 should not be larger than the screen size.
175 </li>
176 <li>Calculate the starting and ending bounds for the {@link android.widget.ImageView}.
177 </li>
178 <li>Animate each of the four positioning and sizing properties <code>{@link
179 android.view.View#X}</code>, <code>{@link android.view.View#Y}</code>, ({@link
180 android.view.View#SCALE_X}, and <code>{@link android.view.View#SCALE_Y}</code>)
181 simultaneously, from the starting bounds to the ending bounds. These four animations are
182 added to an {@link android.animation.AnimatorSet} so that they can be started at the same
183 time.
184 </li>
185 <li>Zoom back out by running a similar animation but in reverse when the user touches the
186 screen when the image is zoomed in. You can do this by adding a {@link
187 android.view.View.OnClickListener} to the {@link android.widget.ImageView}. When clicked, the
188 {@link android.widget.ImageView} minimizes back down to the size of the image thumbnail and
189 sets its visibility to {@link android.view.View#GONE} to hide it.
190 </li>
191 </ol>
192 <pre>
193private void zoomImageFromThumb(final View thumbView, int imageResId) {
194 // If there's an animation in progress, cancel it
195 // immediately and proceed with this one.
196 if (mCurrentAnimator != null) {
197 mCurrentAnimator.cancel();
198 }
199
200 // Load the high-resolution "zoomed-in" image.
201 final ImageView expandedImageView = (ImageView) findViewById(
202 R.id.expanded_image);
203 expandedImageView.setImageResource(imageResId);
204
205 // Calculate the starting and ending bounds for the zoomed-in image.
206 // This step involves lots of math. Yay, math.
207 final Rect startBounds = new Rect();
208 final Rect finalBounds = new Rect();
209 final Point globalOffset = new Point();
210
211 // The start bounds are the global visible rectangle of the thumbnail,
212 // and the final bounds are the global visible rectangle of the container
213 // view. Also set the container view's offset as the origin for the
214 // bounds, since that's the origin for the positioning animation
215 // properties (X, Y).
216 thumbView.getGlobalVisibleRect(startBounds);
217 findViewById(R.id.container)
218 .getGlobalVisibleRect(finalBounds, globalOffset);
219 startBounds.offset(-globalOffset.x, -globalOffset.y);
220 finalBounds.offset(-globalOffset.x, -globalOffset.y);
221
222 // Adjust the start bounds to be the same aspect ratio as the final
223 // bounds using the "center crop" technique. This prevents undesirable
224 // stretching during the animation. Also calculate the start scaling
225 // factor (the end scaling factor is always 1.0).
226 float startScale;
227 if ((float) finalBounds.width() / finalBounds.height()
228 &gt; (float) startBounds.width() / startBounds.height()) {
229 // Extend start bounds horizontally
230 startScale = (float) startBounds.height() / finalBounds.height();
231 float startWidth = startScale * finalBounds.width();
232 float deltaWidth = (startWidth - startBounds.width()) / 2;
233 startBounds.left -= deltaWidth;
234 startBounds.right += deltaWidth;
235 } else {
236 // Extend start bounds vertically
237 startScale = (float) startBounds.width() / finalBounds.width();
238 float startHeight = startScale * finalBounds.height();
239 float deltaHeight = (startHeight - startBounds.height()) / 2;
240 startBounds.top -= deltaHeight;
241 startBounds.bottom += deltaHeight;
242 }
243
244 // Hide the thumbnail and show the zoomed-in view. When the animation
245 // begins, it will position the zoomed-in view in the place of the
246 // thumbnail.
247 thumbView.setAlpha(0f);
248 expandedImageView.setVisibility(View.VISIBLE);
249
250 // Set the pivot point for SCALE_X and SCALE_Y transformations
251 // to the top-left corner of the zoomed-in view (the default
252 // is the center of the view).
253 expandedImageView.setPivotX(0f);
254 expandedImageView.setPivotY(0f);
255
256 // Construct and run the parallel animation of the four translation and
257 // scale properties (X, Y, SCALE_X, and SCALE_Y).
258 AnimatorSet set = new AnimatorSet();
259 set
260 .play(ObjectAnimator.ofFloat(expandedImageView, View.X,
261 startBounds.left, finalBounds.left))
262 .with(ObjectAnimator.ofFloat(expandedImageView, View.Y,
263 startBounds.top, finalBounds.top))
264 .with(ObjectAnimator.ofFloat(expandedImageView, View.SCALE_X,
265 startScale, 1f)).with(ObjectAnimator.ofFloat(expandedImageView,
266 View.SCALE_Y, startScale, 1f));
267 set.setDuration(mShortAnimationDuration);
268 set.setInterpolator(new DecelerateInterpolator());
269 set.addListener(new AnimatorListenerAdapter() {
270 &#64;Override
271 public void onAnimationEnd(Animator animation) {
272 mCurrentAnimator = null;
273 }
274
275 &#64;Override
276 public void onAnimationCancel(Animator animation) {
277 mCurrentAnimator = null;
278 }
279 });
280 set.start();
281 mCurrentAnimator = set;
282
283 // Upon clicking the zoomed-in image, it should zoom back down
284 // to the original bounds and show the thumbnail instead of
285 // the expanded image.
286 final float startScaleFinal = startScale;
287 expandedImageView.setOnClickListener(new View.OnClickListener() {
288 &#64;Override
289 public void onClick(View view) {
290 if (mCurrentAnimator != null) {
291 mCurrentAnimator.cancel();
292 }
293
294 // Animate the four positioning/sizing properties in parallel,
295 // back to their original values.
296 AnimatorSet set = new AnimatorSet();
297 set.play(ObjectAnimator
298 .ofFloat(expandedImageView, View.X, startBounds.left))
299 .with(ObjectAnimator
300 .ofFloat(expandedImageView,
301 View.Y,startBounds.top))
302 .with(ObjectAnimator
303 .ofFloat(expandedImageView,
304 View.SCALE_X, startScaleFinal))
305 .with(ObjectAnimator
306 .ofFloat(expandedImageView,
307 View.SCALE_Y, startScaleFinal));
308 set.setDuration(mShortAnimationDuration);
309 set.setInterpolator(new DecelerateInterpolator());
310 set.addListener(new AnimatorListenerAdapter() {
311 &#64;Override
312 public void onAnimationEnd(Animator animation) {
313 thumbView.setAlpha(1f);
314 expandedImageView.setVisibility(View.GONE);
315 mCurrentAnimator = null;
316 }
317
318 &#64;Override
319 public void onAnimationCancel(Animator animation) {
320 thumbView.setAlpha(1f);
321 expandedImageView.setVisibility(View.GONE);
322 mCurrentAnimator = null;
323 }
324 });
325 set.start();
326 mCurrentAnimator = set;
327 }
328 });
329}
330</pre>